mcpField
K
kuikly-expand-api

kuikly-expand-api

github.com/tencent-tds開発134441 回インストール11 週間前に更新
リポジトリ

kuikly-expand-api について

kuikly-expand-apiは、Kuikly開発者向けのカスタムModule開発ガイドです。Kuikly側APIの定義、ネイティブ側の実装と登録、同期・非同期呼び出しやデータ転送を扱います。

主な機能

  1. 01Kuikly側Module APIの定義
  2. 02ネイティブ側Moduleの実装と登録
  3. 03コールバック付き同期・非同期呼び出し
  4. 04複数プラットフォームでの実装手順
  5. 05パラメーター型とシリアライズ規則

活用シーン

  1. 01プラットフォームAPIをKuiklyコードに公開する
  2. 02対象プラットフォーム向けのModuleを実装する
  3. 03Kuikly DSLまたはCompose DSLでModuleを登録・呼び出す
  4. 04コールバック結果やバイナリデータを転送する

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/tencent-tds/kuiklyui-ai --skill kuikly-expand-api

インストール後、Claude Code で/kuikly-expand-apiを入力して読み込まれたか確認します。

同梱ファイルreferences/MODULE_IMPLEMENT.md

詳細

Kuikly カスタム Module の開発

目次

コアガイドライン

  • Module はプラットフォーム API の統一インターフェースです: Kuikly 自体はプラットフォーム固有の機能を持たず、Module の仕組みを通じて Native API を Kuikly 側に公開し、呼び出せるようにします。
  • カスタム Module は両側で実装する必要があります: Kuikly 側で API インターフェースを定義し、Native 側(Android/iOS/鸿蒙/H5/小程序)で具体的なロジックを実装します。両者は moduleName で関連付けられます。
  • 通信方式には同期と非同期があります: 同期呼び出し(syncToNativeMethod)は Kuikly スレッドで実行され、非同期呼び出し(asyncToNativeMethod)はメインスレッドで実行されます。同期呼び出しでは時間のかかる処理を避けてください。
  • Module 名はすべてのプラットフォームで一致させる必要があります: Kuikly 側の moduleName() の戻り値は、Native 側で登録した名前と完全に一致している必要があります。

ワークフロー:Module の作成(Kuikly 側)

このワークフローでは、Kuikly(Kotlin)側でカスタム Module を作成します。

手順:

  1. Module を継承する Module クラスを新規作成し、moduleName() メソッドを実装します。
  2. ビジネス API メソッドを記述し、toNative / syncToNativeMethod / asyncToNativeMethod を使って Native と通信します。
  3. Pager の createExternalModules() で Module を登録します。
  4. ビジネスコードで acquireModule を使って取得し、使用します。
ステップ 1:Module クラスを定義する
import com.tencent.kuikly.core.module.Module
import com.tencent.kuikly.core.module.CallbackFn
import com.tencent.kuikly.core.nvi.serialization.json.JSONObject

class MyLogModule : Module() {

    override fun moduleName(): String = "KRMyLogModule"

    companion object {
        const val MODULE_NAME = "KRMyLogModule"
    }
}
ステップ 2:API メソッドを記述する

Module は次の通信メソッドを提供します:

メソッド呼び出し方式Native の実行スレッドパラメーター型コールバック型
syncToNativeMethod(methodName, JSONObject?, CallbackFn?)同期Kuikly スレッドJSONObject(JSON 文字列にシリアライズ)CallbackFn(JSONObject?)
syncToNativeMethod(methodName, Array<Any>, AnyCallbackFn?)同期Kuikly スレッド基本型の配列(String/Int/Float/ByteArray)AnyCallbackFn(Any?)
asyncToNativeMethod(methodName, JSONObject?, CallbackFn?)非同期メインスレッドJSONObject(JSON 文字列にシリアライズ)CallbackFn(JSONObject?)
asyncToNativeMethod(methodName, Array<Any>, AnyCallbackFn?)非同期メインスレッド基本型の配列(String/Int/Float/ByteArray)AnyCallbackFn(Any?)
toNative(keepCallbackAlive, methodName, param, callback, syncCall)汎用の低レベルメソッドsyncCall によるAny?CallbackFn(JSONObject?)

非同期呼び出し(戻り値なし):

fun log(content: String) {
    asyncToNativeMethod(
        "log",
        JSONObject().apply { put("content", content) },
        null
    )
}

非同期呼び出し(コールバックあり):

fun logWithCallback(content: String, callbackFn: CallbackFn) {
    asyncToNativeMethod(
        "logWithCallback",
        JSONObject().apply { put("content", content) },
        callbackFn
    )
}

同期呼び出し(戻り値あり):

fun syncLog(content: String): String {
    return syncToNativeMethod(
        "syncLog",
        JSONObject().apply { put("content", content) },
        null
    )
}

バイナリデータの転送(Array パラメーターを使用):

fun uploadData(bytes: ByteArray): Any? {
    return syncToNativeMethod(
        "uploadData",
        arrayOf<Any>(bytes),
        null
    )
}

継続的なコールバック(keepCallbackAlive = true):

fun registerListener(callbackFn: CallbackFn): CallbackRef {
    val result = toNative(
        true,           // keepCallbackAlive: callback 不会被自动销毁
        "registerListener",
        null,
        callbackFn,
        false
    )
    return result.callbackRef!!
}

// 手动移除回调
fun unregisterListener(callbackRef: CallbackRef) {
    removeCallback(callbackRef)
}
ステップ 3:Module を登録する

カスタム Module は、Pager または ComposeContainer の createExternalModules() で登録する必要があります。どちらの DSL でも登録方法は同じです。

Kuikly DSL での登録:

import com.tencent.kuikly.core.annotations.Page
import com.tencent.kuikly.core.module.Module

@Page("MyPage")
internal class MyPage : Pager() {

    override fun createExternalModules(): Map<String, Module>? {
        return mapOf(
            MyLogModule.MODULE_NAME to MyLogModule()
        )
    }

    // 如果有基类 BasePager 已注册了其他 Module,需要合并:
    // override fun createExternalModules(): Map<String, Module>? {
    //     val modules = (super.createExternalModules() as? HashMap) ?: hashMapOf()
    //     modules[MyLogModule.MODULE_NAME] = MyLogModule()
    //     return modules
    // }

    override fun body(): ViewBuilder { ... }
}

Compose DSL での登録:

import com.tencent.kuikly.compose.ComposeContainer
import com.tencent.kuikly.compose.setContent
import com.tencent.kuikly.core.annotations.Page
import com.tencent.kuikly.core.module.Module

@Page("MyComposePage")
class MyComposePage : ComposeContainer() {

    // 注册方式与 Kuikly DSL 完全一致
    override fun createExternalModules(): Map<String, Module>? {
        return mapOf(
            MyLogModule.MODULE_NAME to MyLogModule()
        )
    }

    override fun willInit() {
        super.willInit()
        setContent {
            MyScreen()
        }
    }
}
ステップ 4:Module を使用する

Kuikly DSL での使用:

override fun created() {
    super.created()

    val myLogModule = acquireModule<MyLogModule>(MyLogModule.MODULE_NAME)

    // 异步调用
    myLogModule.log("test log")

    // 异步调用带回调
    myLogModule.logWithCallback("log with callback") { data ->
        val result = data  // Native 侧返回的 JSONObject
    }

    // 同步调用
    val result = myLogModule.syncLog("sync log")
}

Compose DSL での使用:

import androidx.compose.runtime.Composable
import com.tencent.kuikly.compose.ui.platform.LocalActivity
import com.tencent.kuikly.core.pager.Pager

@Composable
fun MyScreen() {
    val pager = LocalActivity.current.getPager() as Pager
    val myLogModule = pager.acquireModule<MyLogModule>(MyLogModule.MODULE_NAME)

    // 异步调用
    myLogModule.log("test log")

    // 异步调用带回调
    myLogModule.logWithCallback("log with callback") { data ->
        val result = data  // Native 侧返回的 JSONObject
    }

    // 同步调用
    val result = myLogModule.syncLog("sync log")
}

ワークフロー:Native 側 Module の実装

このワークフローでは、カスタム Module の Native 側を実装します。

前提条件: ビジネス上サポートが必要なプラットフォームでのみ、Native 側 Module を実装します。すべてのプラットフォームで実装する必要はありません。

手順:

  1. サポートする対象プラットフォーム(Android/iOS/鸿蒙/H5/小程序)を決定します。
  2. 対象プラットフォームのホストプロジェクトに、対応する Module クラスを作成します。
  3. call メソッドを実装し、method に基づいて処理を振り分けます。
  4. Module を Kuikly フレームワークに登録します。

💡 鸿蒙プラットフォームに関する注記: 鸿蒙では ArkTS と C の 2 種類の実装方式が提供されています。通常は ArkTS の実装を選択するだけで十分です。

各プラットフォームでの実装の詳細は MODULE_IMPLEMENT.md を参照してください。

クイックリファレンス:各プラットフォームの Module 基底クラスと登録方法

プラットフォーム基底クラス登録方法注意事項
AndroidKuiklyRenderBaseModule()registerExternalModule 内の moduleExport(name) { Module() }call(method, params: Any?, callback) または call(method, params: String?, callback) をオーバーライドします
iOSKRBaseModuleクラス名は Kuikly 側の moduleName と一致させる必要があります(実行時に動的に作成)メソッド名は Kuikly 側の methodName と一致させます。パラメーターは NSDictionary 固定で、Swift では @objc アノテーションが必要です
鸿蒙 (ArkTS)KuiklyRenderBaseModulegetCustomRenderModuleCreatorRegisterMap で登録同期/非同期モードを指定する syncMode() の実装が必要です
鸿蒙 (C)KRRenderModuleRegisterV2InitKuikly で登録戻り値 KRAnyData はフレームワークによって解放されます
H5KuiklyRenderBaseModule()registerExternalModule 内の moduleExport(name) { Module() }Android と同じです
小程序KuiklyRenderBaseModule()registerExternalModule 内の moduleExport(name) { Module() }NativeApi.plat を通じて wx オブジェクトにアクセスできます
Native 側でサポートされるデータ型
プラットフォームサポートされるデータ型
AndroidString Int Long Float Double Boolean ByteArray Map List JSONObject
iOSNSString NSNumber BOOL NSData NSDictionary NSArray
鸿蒙String Int Long Float Double Bool ByteArray Array Map/Record
H5/小程序String Int Long Float Double Boolean Array Map List JSONObject JSONArray
データのシリアライズ規則
カテゴリシリアライズ方式該当する型
基本型そのまま渡すString Int Float Double Boolean NSNumber
バイナリデータそのまま渡すByteArray NSData ArrayBuffer
JSON データJSON 文字列JSONObject JSONArray
コレクション型JSON 文字列Map/Record List NSDictionary NSArray Array
特殊規則そのまま渡すArray に ByteArray/NSData が含まれる場合

⚠️ syncToNativeMethod と asyncToNativeMethod に JSONObject パラメーターを渡すと、JSON 文字列にシリアライズされます。ByteArray のバイナリデータには対応していません。バイナリデータを転送する場合は、Array<Any> パラメーターを受け取るオーバーロードメソッドを使用してください。


よくある落とし穴と正しい対処法

❌ 誤った方法✅ 正しい方法
Module 名が Kuikly 側と Native 側で一致していないmoduleName() の戻り値を Native の登録名と完全に一致させてください
created より前に Module を取得するKuikly DSL では created() 以降に Module を取得してください。Compose DSL では willInit の setContent の後に取得してください
同期呼び出しで時間のかかる処理を実行する同期呼び出しは Kuikly スレッドで実行されるため、レンダリングをブロックする時間のかかる処理は避けてください
createExternalModules でカスタム Module の登録を忘れるカスタム Module は Pager の createExternalModules() で登録する必要があります
JSONObject パラメーターでバイナリデータを転送するArray<Any> パラメーターを受け取る syncToNativeMethod/asyncToNativeMethod を使用して ByteArray を転送してください
iOS 側の Module クラス名が Kuikly 側と一致していないiOS ではクラス名を使って Module を動的に作成するため、クラス名を moduleName() と一致させる必要があります
iOS 側で Swift を使って実装し、@objc アノテーションを付け忘れるSwift で実装した Module には @objc または @objcMembers を付ける必要があります
常駐コールバックを手動で削除し忘れるkeepCallbackAlive=true のコールバックは、ページが破棄される前に removeCallback を呼び出す必要があります
Compose DSL で acquireModule を直接呼び出す@Composable 関数内では、LocalActivity.current.getPager().acquireModule を使って取得する必要があります
Compose DSL で LocalActivity の import を忘れるimport com.tencent.kuikly.compose.ui.platform.LocalActivity が必要です

コメント