扩展原生API

大约 9 分钟

扩展原生API

Kuikly允许开发者通过Module机制来访问平台的API,以达到复用平台生态能力的目的。下面我们以打印日志作为例子,来看Kuikly 如何通过Module机制来访问平台的API。 将Native的API暴露给Kuikly使用,需要完成以下工作

  1. Kuikly侧:
    1. 新建XXXModule类并继承Module,编写API暴露给业务方使用
    2. Pager的子类中,注册新创建的Module
  2. android侧:
    1. 新建XXXModule类并继承KuiklyRenderBaseModule, 编写API的具体实现代码
    2. XXXModule注册暴露给Kuikly
  3. iOS侧:
    新建XXXModule(类名必须与kuikly侧注册的module名字一致)并继承KRBaseModule, 编写API的具体实现代码
  4. 鸿蒙侧:
    1. 新建XXXModule类,继承KuiklyRenderBaseModule,编写API的具体实现代码
    2. XXXModule注册暴露给Kuikly

kuikly侧

  1. 我们首先新建MyLogModule类,并继承Module类, 然后实现moduleName方法,返回MyLogModule对应的标识名字,用于与Native的Module对应
class MyLogModule : Module() {

    override fun moduleName(): String = "KRMyLogModule"
    
}
  1. 在实现MyLogModule中的方法前,我们先来看其父类Module中的toNative方法。toNative方法用于Kuikly侧的Module触发Native侧对应Module的方法调用, 它含有下面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 ModuleNative Module同步通信

对于callback只回调一次的用法,框架提供了4个辅助方法:

  • syncToNativeMethod(methodName, params, null): String // 同步调用Native方法(native侧在子线程执行),传输JSONObject类型参数,返回JSON字符串
  • syncToNativeMethod(methodName, arrayOf(content), null): Any? // 同步调用Native方法(native侧在子线程执行),传输基本类型数组,返回基本类型
  • asyncToNativeMethod(methodName, params, callback) // 异步调用Native方法(native侧在主线程执行),传输JSONObject类型参数,回调JSON字符串
  • asyncToNativeMethod(methodName, arrayOf(content), callback) // 异步调用Native方法(native侧在主线程执行),传输基本类型数组,回调基本类型
  1. 接着我们新增log方法,让业务方能够打印日志。
class MyLogModule : Module() {

    /**
     * 打印日志
     * @param content 日志内容
     */
    fun log(content: String) {
        toNative(
            false,
            "log",
            content,
            null,
            false
        )
    }

    override fun moduleName(): String = "KRMyLogModule"

}

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

获取返回值

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

  1. 异步获取返回值: 这种方式是在调用toNative方法时,传递CallbackFn参数,让原生侧将结果已json字符串的形式传递给CallbackFn
  2. 同步获取: 这种方式是在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"

}
  1. 实现完Kuikly侧的module后,下面我们在Pager的子类中重写createExternalModules注册MyLogModule
internal class TestPage : Pager() {
    override fun body(): ViewBuilder {
    }

    override fun createExternalModules(): Map<String, Module>? {
        return mapOf(
            "KRMyLogModule" to MyLogModule()
        )
    }
}
  1. 最后我们来看业务是如何使用MyLogModule
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方法
    }
}
  1. 以上就是Kuikly侧的需要完成的工作,剩下的就是原生侧实现Kuikly侧定义的方法

android侧

  1. 在接入Kuikly的android宿主工程中新建KRMyLogModule类,然后继承KuiklyRenderBaseModule,并重写其call方法(call方法有两个实现,根据Module传输的数据类型,选择重写其中之一
class KRMyLogModule : KuiklyRenderBaseModule() {
    // 传输基本类型、数组、字符串
    override fun call(method: String, params: Any?, callback: KuiklyRenderCallback?): Any? {

    }

    // 传输Json(会被序列化为Json字符串)
    override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
       
    }
}

KuiklyMyLogModuletoNative方法最终会调用原生对应的Modulecall方法,也就是KRMyLogModule中的call方法。

KuiklyMyLogModule中定义了三个方法,下面我们来看在android侧如何实现这三个方法

log方法

class KRMyLogModule : KuiklyRenderBaseModule() {

    override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
        return when (method) {
            "log" -> log(params ?: "")
            else -> super.call(method, params, callback)
        }
    }

    private fun log(content: String) {
        Log.d("test", content)
    }

}

call方法中,我们通过method参数来识别log方法,然后调用我们定义的私有方法log,并将Kuikly侧传递过来的content参数传递给log方法

logWithCallback方法

class KRMyLogModule : KuiklyRenderBaseModule() {

    override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
        return when (method) {
            "log" -> log(params ?: "")
            "logWithCallback" -> logWithCallback(params ?: "", callback)
            else -> super.call(method, params, callback)
        }
    }

    private fun logWithCallback(content: String, callback: KuiklyRenderCallback?) {
        Log.d("test", content) // 1. 打印日志
        callback?.invoke(mapOf(
            "result" to 1
        )) // 2. callback对应kuikly module侧方法的callbackFn, 此处将数据存放到map中并传递给kuikly侧
    }
    ...
}

syncLog方法

class KRMyLogModule : KuiklyRenderBaseModule() {

    override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
        return when (method) {
            "log" -> log(params ?: "")
            "logWithCallback" -> logWithCallback(params ?: "", callback)
            "syncLog" -> syncLog(params ?: "")
            else -> super.call(method, params, callback)
        }
    }
    
    private fun syncLog(content: String): String {
        Log.d("test", content) // 1. 打印日志
        return "success" // 2. 将字符串同步返回给kuikly侧
    }
    ...
}
  1. 原生侧实现完API后,我们将KRMyLogModule注册暴露到Kuikly中,与Kuikly侧的MyLogModule对应起来。在实现了KuiklyRenderViewDelegatorDelegate接口 类中重写registerExternalModule方法,注册KRMyLogModule

注意

注册的名字必须与Kuikly moudle侧注册的名字一样

    override fun registerExternalModule(kuiklyRenderExport: IKuiklyRenderExport) {
        super.registerExternalModule(kuiklyRenderExport)
        with(kuiklyRenderExport) {
            ...
            moduleExport("KRMyLogModule") {
                KRMyLogModule()
            }
        }
    }

iOS侧

  1. 在接入Kuikly的iOS宿主工程中新建KRMyLogModule类,然后继承KRBaseModule

注意

iOS原生侧的Module创建是在运行时根据Kuikly注册module的名字来动态创建的,因此类名必须与Kuikly侧注册module的名字保持一致

// .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

下面我们看如何实现Kuikly侧定义的API

log方法

#import "KRMyLogModule.h"

@implementation KRMyLogModule

-(void)log:(NSDictionary *)args {
    NSString *content = args[HR_PARAM_KEY]; // 获取log内容
    NSLog(@"log:%@", content);
}

@end

方法名字保持与Kuikly侧的log方法名字一致,并且参数固定为NSDictionary类型。

Kuikly侧传递过来的参数从args字典中提取, 例如

NSString *content = args[HR_PARAM_KEY]; // 获取log内容

注意

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

logWithCallback方法

- (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侧
    
}

Kuikly侧的CallbackFn我们可以从args字典中拿到

KuiklyRenderCallback callback = args[KR_CALLBACK_KEY];

syncLog方法

- (id)syncLog:(NSDictionary *)args {
    NSString *content = args[HR_PARAM_KEY]; // 1.获取log内容
    NSLog(@"log:%@", content); // 2.打印日志
    
    return @"success"; // 3.同步返回给kuikly侧
}

鸿蒙侧

  1. 在接入Kuikly的鸿蒙宿主工程(ArkTS)中新建KRMyLogModule类,继承KuiklyRenderBaseModule,并重写其call方法
export class KRMyLogModule extends KuiklyRenderBaseModule {
    // 定义模块名(注册时用到,全局唯一)
    static readonly MODULE_NAME = "KRMyLogModule";

    // 是否同步模式(同步模式的module运行在kuikly线程,支持同步调用和异步调用; 异步模式的module运行在ui线程,只支持异步调用)
    syncMode(): boolean {
        return true;
    }

    call(method: string, params: KRAny, callback: KuiklyRenderCallback | null): KRAny {

    }
    ...
}

KuiklyMyLogModuletoNative方法最终会调用原生对应的Modulecall方法,也就是KRMyLogModule中的call方法。

KuiklyMyLogModule中定义了三个方法,下面我们来看在鸿蒙侧如何实现这三个方法

log方法

export class KRMyLogModule extends KuiklyRenderBaseModule {
    static readonly MODULE_NAME = "KRMyLogModule";
  
    syncMode(): boolean {
        return true;
    }

    call(method: string, params: KRAny, callback: KuiklyRenderCallback | null): KRAny {
        // 分发响应
        switch (method) {
            case 'log':
                this.log(params as string);
                return null;
        }
        return null
    }

    private log(content: string) {
        console.log(`log: ${content}`);
    }
    ...
}

call方法中,我们通过method参数来识别log方法,然后调用我们定义的私有方法log,并将Kuikly侧传递过来的content参数传递给log方法

logWithCallback方法

export class KRMyLogModule extends KuiklyRenderBaseModule {
    static readonly MODULE_NAME = "KRMyLogModule";
  
    syncMode(): boolean {
        return true;
    }

    call(method: string, params: KRAny, callback: KuiklyRenderCallback | null): KRAny {
        // 分发响应
        switch (method) {
            case 'log':
                this.log(params as string);
                return null;
            case 'logWithCallback':
                this.logWithCallback(params as string, callback);
                return null;
        }
        return null
    }

    private logWithCallback(content: string, callback: KuiklyRenderCallback | null) {
        console.log("log:" + content);
        // 异步返回结果
        callback?.({
            "result": 1
        });
    }
    ...
}

调用KRMyLogModulelogWithCallback方法时,我们传入KuiklyRenderCallback,在执行完相关操作后调用该callback异步返回结果。

syncLog方法

export class KRMyLogModule extends KuiklyRenderBaseModule {
    static readonly MODULE_NAME = "KRMyLogModule";
  
    syncMode(): boolean {
        return true;
    }

    call(method: string, params: KRAny, callback: KuiklyRenderCallback | null): KRAny {
        // 分发响应
        switch (method) {
            case 'log':
                this.log(params as string);
                return null;
            case 'logWithCallback':
                this.logWithCallback(params as string, callback);
                return null;
            case 'syncLog':
                // 同步返回结果
                return this.syncLog(params as string);
        }
        return null
    }

    private syncLog(content: string) {
        console.log("log:" + content);
        return "success";
    }
    ...
}

同步调用KRMyLogModule的方法时,直接在call函数中返回函数结果。

  1. 原生侧实现完API后,我们将KRMyLogModule注册暴露到Kuikly中,与Kuikly侧的MyLogModule对应起来。在实现了IKuiklyViewDelegate接口 类中重写getCustomRenderModuleCreatorRegisterMap方法,注册KRMyLogModule

注意

注册的名字必须与Kuikly moudle侧注册的名字一样

export class KuiklyViewDelegate extends IKuiklyViewDelegate {
    ...
    getCustomRenderModuleCreatorRegisterMap(): Map<string, KRRenderModuleExportCreator> {
        const map: Map<string, KRRenderModuleExportCreator> = new Map();
        // 注册自定义module
        map.set(KRMyLogModule.MODULE_NAME, () => new KRMyLogModule())
        return map;
    }
}