从0使用Kuikly框架写一个小红书Demo-Day6
拓展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")
}
}