普通视图

发现新文章,点击刷新页面。
昨天 — 2025年10月28日首页

从0使用Kuikly框架写一个小红书Demo-Day6

2025年10月27日 19:38

拓展Kuikly原生API的能力

在Kuikly开发中, 经常会有需要调用平台API的诉求, Kuikly是一个跨端的UI框架,本身不具备平台相关的能力,但是Kuikly提供了Module机制,方便你调用平台的API。通过扩展原生API来自定义Module, 将更多的宿主平台API暴露给Kuikly侧使用。根据Kuikly详细的官方文档扩展原生API,下面我们以打印日志作为例子,来看Kuikly如何通过Module机制来访问平台的API。

 

要想将Native的API暴露给Kuikly使用,需要做以下工作:

Kuikly侧:

1、新建XXXModule类并继承Module,编写API暴露给业务方使用

2、在Pager的子类中,注册新创建的Module

 

Native侧(以iOS为例):

新建XXXModule(类名必须与kuikly侧注册的module名字一致)并继承KRBaseModule, 编写API的具体实现代码

 

6.1 Kuikly侧

首先新建一个类,然后继承Module类,并重写moduleName方法,用于给Native侧和Kuikly侧标识module

    class MyLogModule : Module() {
        override fun moduleName(): String = "KRMyLogModule"
    }

 

在实现 MyLogModule 的方法之前,先来了解一下它的父类 Module 里的 toNative 方法。toNative是Kuikly侧调用Native侧对应方法时触发的一个方法。

/**
 * 通用的与Native Module通信方法
 */
fun toNative(
    keepCallbackAlive: Boolean = false,
    methodName: String,
    param: Any?,
    callback: CallbackFn? = null,
    syncCall: Boolean = false
): ReturnValue {
    var nativeCallback : AnyCallbackFn? = null
    callback?.also {
        nativeCallback = { res ->
            var dataJSONObject : JSONObject? = null
            if (res != null && res is String) {
                dataJSONObject = JSONObject(res)
            } else if (res != null && res is JSONObject) {
                dataJSONObject = res
            }
            callback(dataJSONObject)
        }
    }
    return innerToNative(keepCallbackAlive, methodName, param, nativeCallback, syncCall)
}
 
private fun innerToNative(
    keepCallbackAlive: Boolean = false,
    methodName: String,
    param: Any?,
    callback: AnyCallbackFn? = null,
    syncCall: Boolean = false
): ReturnValue {
    var callbackRef: CallbackRef? = null
    callback?.also { cb ->
        callbackRef = GlobalFunctions.createFunction(pagerId) { res ->
            cb(res?.toKotlinObject())
            keepCallbackAlive
        }
    }
    val returnValue = BridgeManager.callModuleMethod(
        pagerId,
        moduleName(),
        methodName,
        param,
        callbackRef,
        convertSyncCall(syncCall, keepCallbackAlive)
    )
    return ReturnValue(callbackRef, returnValue)
}

主要是这5个参数:

1、keepCallbackAlive: callback回调是否常驻,如果为false的话,callback被回调一次后,会被销毁掉;如果为true的话,callback会一直存在内存中,直到页面销毁

2、methodName: 调用Native Module对应的方法名字

3、param: 传递给Native Module方法的参数,支持基本类型、数组、字符串(特别指出,Json不属于基本类型,需要先序列化为Json字符串)

4、callback: 用于给Native Module将处理结果回调给kuikly Module侧的callback

5、  syncCall: 是否为同步调用。kuikly的代码是运行在一条单独的线程,默认与Native Module是一个异步的通信。如果syncCall指定为true时,可强制kuikly Module与Native Module同步通信

 

接着新增log方法用于打印日志,供业务方调用

class MyLogModule : Module() {
    /**
     * 打印日志
     * @param content 日志内容
     */
    fun log(content: String) {
        toNative(
            false,
            "log",
            content,
            null,
            false
        )
    }
    override fun moduleName(): String = "KRMyLogModule"
}

 

6.2 获取Native侧的返回值

在log方法中,我们调用了toNative方法来完成对Native Module的调用。这个log方法是没有返回值的。但是实际业务场景中,往往是有需要返回值的需求,那 module中的api如何获取原生侧的返回值呢?

Kuikly调用原生API时,可以有两种方式获取原生侧的返回值

异步获取: 这种方式是在调用toNative方法时,传递CallbackFn参数,让原生侧将结果已json字符串的形式传递给CallbackFn

同步获取: 这种方式是在Kuikly当前线程(非UI线程)中调用原生侧的API方法,原生侧的API方法将结果以String的格式返回

 

class MyLogModule : Module() {
 
    /**
     * 打印日志
     * @param content 内容
     * @param callbackFn 结果回调
     */
    fun logWithCallback(content: String, callbackFn: CallbackFn) {
        toNative(
            false,
            "logWithCallback",
            content,
            callbackFn,
            false
        )
    }
    
    /**
     * 同步调用打印日志
     * @param content
     */
    fun syncLog(content: String): String {
        return toNative(
            false,
            "syncLog",
            content,
            null,
            true
        ).toString()
    }
    override fun moduleName(): String = "KRMyLogModule"
}

 

实现完Kuikly侧的module后,需要注册MyLogModule,让Kuikly框架感知到这个module的存在,方式是通过在Pager的子类中重写createExternalModules

internal class TestPage : Pager() {
    override fun body(): ViewBuilder {
    }
 
    override fun createExternalModules(): Map<String, Module>? {
        return mapOf(
            "KRMyLogModule" to MyLogModule()
        )
    }
}

6.3 Native侧(iOS系统为例)

在iOS宿主壳工程中新建一个类并继承KRBaseModule类

// .h
#import <Foundation/Foundation.h>
#import "KRBaseModule.h"
 
NS_ASSUME_NONNULL_BEGIN
 
@interface KRMyLogModule : KRBaseModule
 
@end
 
NS_ASSUME_NONNULL_END
 
// .m
#import "KRMyLogModule.h"
 
@implementation KRMyLogModule
 
@end

 

实现log方法

#import "KRMyLogModule.h"
 
@implementation KRMyLogModule
 
-(void)log:(NSDictionary *)args {
    NSString *content = args[HR_PARAM_KEY]; // 获取log内容
    NSLog(@"log:%@", content);
}
 
- (void)logWithCallback:(NSDictionary *)args {
    NSString *content = args[HR_PARAM_KEY]; // 1.获取log内容
    NSLog(@"log:%@", content); // 2.打印日志
    
    KuiklyRenderCallback callback = args[KR_CALLBACK_KEY]; // 3.获取kuikly侧传递的callbackFn
    callback(@{
        @"result": @1
    }); // 4.回调给kuikly侧
    
}
 
 
- (id)syncLog:(NSDictionary *)args {
    NSString *content = args[HR_PARAM_KEY]; // 1.获取log内容
    NSLog(@"log:%@", content); // 2.打印日志
    
    return @"success"; // 3.同步返回给kuikly侧
}
 
 
@end
 

iOS侧的Module中的方法名字必须与kuikly侧toNative方法传递的方法名字一致,这样才能在运行时找到并调用方法

 

6.4 Module的使用

首先我们先了解一下Pager的生命周期,在Kuikly中, Pager是承载页面UI的容器。

 

 

Kuikly的Pager在初始化的过程中会初始化Pager注册的Module,我们可以在Pager初始化完成以后, 获取Module

internal class TestPage : Pager() {
    override fun created() {
        super.created()
        
        val myLogModule = acquireModule<MyLogModule>("KRMyLogModule") // 调用acquireModule并传入module名字获取module
        myLogModule.log("test log") // 调用log打印日志
        myLogModule.logWithCallback("log with callback") { // 异步调用含有返回值的log方法
            val reslt = it // 原生侧返回的JSONObject对象
        }
        val result = myLogModule.syncLog("sync log") // 同步调用含有返回值的log方法
    }
}
 

 

如果你想在组合组件中获取Module, 你可以这样获取:

class TestComposeView : ComposeView<ComposeAttr, ComposeEvent>() {
 
    override fun created() {
        super.created()
        // 1. 通过acquireModule<T>(moduleName)获取Module, 如果找不到Module的话会抛异常
        val myLogModule = acquireModule<MyLogModule>("KRMyLogModule")
        
        // 2. getModule<T>(moduleName)获取Module, 如果找不到Module的话返回null
        val myLogModule = getModule<MyLogModule>("KRMyLogModule")
    }
}
 

 

如果你可能会想在非Pager类和非组合组件中获取Module, 你获取当前的Pager示例,通过Pager来获取Module

class Test {
    fun setValue(v: Int) {
        val myLogModule = PagerManager.getCurrentPager()
            .acquireModule<MyLogModule>("KRMyLogModule")
    }
}
 
❌
❌