| name | kuikly-expand-api |
| description | Kuikly 自定义 Module 开发助手。指导如何创建自定义 Module,扩展原声API。覆盖完整开发流程:Kuikly 侧 Module 定义、Pager/ComposeContainer 注册、各平台 Native 侧实现、Module 注册,及使用。当用户需要自定义 Module、扩展原生 API、实现 Kuikly 与 Native 双向通信、在各平台实现 Module 的 Native 侧逻辑时使用。 |
Kuikly 自定义 Module 开发
Contents
Core Guidelines
- Module 是平台 API 的统一接口: Kuikly 本身不具备平台相关能力,通过 Module 机制将 Native API 暴露给 Kuikly 侧调用。
- 自定义 Module 需双端实现: Kuikly 侧定义 API 接口,Native 侧(Android/iOS/鸿蒙/H5/小程序)实现具体逻辑,通过
moduleName 关联。
- 通信方式分同步和异步: 同步调用(
syncToNativeMethod)在 Kuikly 线程执行,异步调用(asyncToNativeMethod)在主线程执行。同步调用避免耗时操作。
- Module 名字必须全端一致: Kuikly 侧
moduleName() 返回值必须与 Native 侧注册的名字完全一致。
Workflow: Creating Module (Kuikly Side)
Use this workflow to create a custom Module on the Kuikly (Kotlin) side.
步骤:
- 新建 Module 类继承
Module,实现 moduleName() 方法。
- 编写业务 API 方法,通过
toNative / syncToNativeMethod / asyncToNativeMethod 与 Native 通信。
- 在 Pager 的
createExternalModules() 中注册 Module。
- 在业务代码中通过
acquireModule 获取并使用。
Step 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"
}
}
Step 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,
"registerListener",
null,
callbackFn,
false
)
return result.callbackRef!!
}
fun unregisterListener(callbackRef: CallbackRef) {
removeCallback(callbackRef)
}
Step 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()
)
}
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() {
override fun createExternalModules(): Map<String, Module>? {
return mapOf(
MyLogModule.MODULE_NAME to MyLogModule()
)
}
override fun willInit() {
super.willInit()
setContent {
MyScreen()
}
}
}
Step 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
}
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
}
val result = myLogModule.syncLog("sync log")
}
Workflow: Implementing Native Side Module
Use this workflow to implement the Native side of a custom Module.
前提: 只需在业务需要支持的平台上实现 Native 侧 Module,无需实现所有平台。
步骤:
- 确定需要支持的目标平台(Android/iOS/鸿蒙/H5/小程序)。
- 在目标平台宿主工程中创建对应的 Module 类。
- 实现
call 方法,根据 method 分发处理。
- 将 Module 注册到 Kuikly 框架。
💡 鸿蒙平台备注: 鸿蒙提供 ArkTS 和 C 两种实现方式,一般情况只需选择 ArkTS 实现即可。
各平台实现详见 MODULE_IMPLEMENT.md
速查:各平台 Module 基类与注册方式
| 平台 | 基类 | 注册方式 | 注意事项 |
|---|
| Android | KuiklyRenderBaseModule() | registerExternalModule 中 moduleExport(name) { Module() } | 重写 call(method, params: Any?, callback) 或 call(method, params: String?, callback) |
| iOS | KRBaseModule | 类名必须与 Kuikly 侧 moduleName 一致(运行时动态创建) | 方法名与 Kuikly 侧 methodName 一致,参数固定为 NSDictionary,Swift 需 @objc 注解 |
| 鸿蒙 (ArkTS) | KuiklyRenderBaseModule | getCustomRenderModuleCreatorRegisterMap 中注册 | 需实现 syncMode() 指定同步/异步模式 |
| 鸿蒙 (C) | KRRenderModuleRegisterV2 | InitKuikly 中注册 | 返回值 KRAnyData 由框架释放 |
| H5 | KuiklyRenderBaseModule() | registerExternalModule 中 moduleExport(name) { Module() } | 同 Android |
| 小程序 | KuiklyRenderBaseModule() | registerExternalModule 中 moduleExport(name) { Module() } | 可通过 NativeApi.plat 访问 wx 对象 |
Native 侧支持的数据类型
| 平台 | 支持的数据类型 |
|---|
| Android | String Int Long Float Double Boolean ByteArray Map List JSONObject |
| iOS | NSString 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 | 在 created() 或之后获取 Module(Kuikly DSL);在 willInit 的 setContent 之后获取(Compose DSL) |
| 同步调用中执行耗时操作 | 同步调用在 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 中忘记 import LocalActivity | 需 import com.tencent.kuikly.compose.ui.platform.LocalActivity |