* docs(debug): update doc path for 3.0 * docs(layout): modify layout title * docs(core): modify some core --------- Co-authored-by: OpenHippy <124017524+open-hippy@users.noreply.github.com>
16 KiB
自定义终端模块
很多时候 JS 需要访问对应终端的一些能力模块,比如数据库、下载、网络请求等,这时候就需要使用 Module 来暴露接口给JS使用。Hippy SDK 中默认实现了部分 Module,但这极有可能无法满足你的需求,这就需要你对 Module 进行扩展封装。Hippy支持 Android、iOS、Flutter、Web(同构) 等平台的模块扩展。
Android
Module扩展
我们将以 TestModule 为例,从头扩展一个 Module,这个 Module 将展示前端如何调用终端能力,并且把结果返回给前端。
终端扩展Module包括四步:
- 创建
HippyNativeModuleBase的子类。 - 添加
HippyNativeModule注解。 - 实现导出给 JS 的方法。
- 注册 Module。
1. 创建HippyNativeModuleBase的子类
首先我们需要创建TestModule类,并且继承HippyNativeModuleBase。
package com.tencent.mtt.hippy.example.modules;
import com.tencent.mtt.hippy.HippyEngineContext;
import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase;
public class TestModule extends HippyNativeModuleBase
{
public LogModule(HippyEngineContext context)
{
super(context);
}
}
2. 添加HippyNativeModule注解
HippyNativeModuleBase 要求增加注解 @HippyNativeModule 。
HippyNativeModule有两个注解参数:
- name:能力名称,js调用时需要通过此访问该能力。
- thread:能力运行的线程。包括
HippyNativeModule.Thread.DOM(Dom线程)、HippyNativeModule.Thread.MAIN(主线程)、HippyNativeModule.Thread.BRIDGE(Bridge线程、默认值)。
@HippyNativeModule(name = "TestModule", thread = HippyNativeModule.Thread.BRIDGE)
public class TestModule extends HippyNativeModuleBase
{
...
}
3. 实现导出给JS的方法
导出给 JS 使用的方法,必须使用注解 @HippyMethod ,方法必须为 public 类型,返回类型必须为 void。
支持的方法参数类型包括:
- Java基本数据类型。
- HippyArray:类似于ArrayList,线程非安全。
- HippyMap:类似于HashMap,线程非安全。
- Promise:回调JS的触发器,通过
resolve方法返回成功信息给JS。通过reject方法返回失败实现给JS。
@HippyMethod(name="log")
public void log(String msg)
{
Log.d("TestModule", msg);
}
@HippyMethod(name="helloNative")
public void helloNative(HippyMap hippyMap)
{
String hello = hippyMap.getString("hello");
Log.d("TestModule", hello);
}
@HippyMethod(name = "helloNativeWithPromise")
public void helloNativeWithPromise(HippyMap hippyMap, Promise promise)
{
// 这里回来的参数可以为java的基础类型,和hippymap与hippyarry, 但是前端调用的时候必须对应上
String hello = hippyMap.getString("hello");
Log.d("TestModule", hello);
if (true)
{
// TODO 如果模块这里处理成功回调resolve
HippyMap hippyMap1 = new HippyMap();
hippyMap1.pushInt("code", 1);
hippyMap1.pushString("result", "hello i am from native");
promise.resolve(hippyMap1);
}
else
{
// 失败就回调reject
HippyMap hippyMap1 = new HippyMap();
hippyMap1.pushInt("code", -1);
promise.reject(hippyMap1);
}
}
4. 注册Module
然后需要注册这个Module。需要在 HippyPackage 的 getNativeModules 方法中添加这个 Module,这样它才能在JS中被访问到。
import com.tencent.mtt.hippy.HippyEngineContext;
import com.tencent.mtt.hippy.HippyPackage;
import com.tencent.mtt.hippy.common.Provider;
import com.tencent.mtt.hippy.example.module.TestModule;
import com.tencent.mtt.hippy.modules.javascriptmodules.HippyJavaScriptModule;
import com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase;
import com.tencent.mtt.hippy.uimanager.HippyViewController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ExamplePackages implements HippyPackage
{
@Override
public Map<Class<? extends HippyNativeModuleBase>, Provider<? extends HippyNativeModuleBase>> getNativeModules(final HippyEngineContext context)
{
Map<Class<? extends HippyNativeModuleBase>, Provider<? extends HippyNativeModuleBase>> modules = new HashMap<>();
// regist the LogModule
modules.put(ToastModule.class, new Provider<HippyNativeModuleBase>()
{
@Override
public HippyNativeModuleBase get()
{
return new TestModule(context);
}
});
return modules;
}
注意事项
扩展Module中不能同步执行耗时操作,这可能卡住整个引擎通信线程。存在耗时场景,请使用异步线程处理。
混淆说明
扩展 Module 的类名和扩展方法名不能混淆,可以增加混淆例外。
-keep class * extends com.tencent.mtt.hippy.modules.nativemodules.HippyNativeModuleBase{ public *;}
iOS
除了UI界面以外,APP开发中还会出现调用设备功能的场景,比如获取当前网络状态、发起HTTP网络请求等。SDK已经封装了一些常用功能,但自定义功能模块也是十分方便的。
注意:自定义模块及其方法的名称中不要带上
Hippy几个字(不区分大小写),否则在 iOS 上可能会碰到找不到模块或方法的问题。
功能扩展中类和方法的导出和UI组件的类和方法的导出十分相似,建议先阅读UI组件扩展再来阅读功能扩展文章。
使用 HIPPY_EXPORT_MODULE() 扩展自定义功能后,每次APP启动时都会创建一个功能实例,并且以后前端调用组件功能使用的都是这个实例,可以理解为单例的意思。
功能没有属性这个概念,不要试图去给功能绑定属性。
每个功能都有类似于UI组件对应的方法导出, 同样也对应callNative、callNativeWithCallbackId、callNativeWithPromise三种调用方式。
我们将SDK中的功能模块分为两种类型:
- 非事件型功能--当业务需要某种信息或者需要终端执行某项指令,直接通过接口调用终端代码即可。
- 事件型功能--业务需要终端监听某个事件。当事件触发时,终端通知前端。
非事件型功能扩展
扩展一个非事件型功能组件包括以下工作:
- 创建对应的功能类,并绑定前端组件,
非事件型功能只需要继承自简单的NSObject类即可。在实现文件中使用
HIPPY_EXPORT_MODULE()宏导出。 - 绑定前端方法
与扩展UI组件类似,使用
HIPPY_EXPORT_METHOD()宏绑定前端方法。注意方法名需要对齐。
TestModule.h
@interface TestModule : NSObject <HippyBridgeModule>
@end
TestModule.m
@implementation TestModule
HIPPY_EXPORT_MODULE()
HIPPY_EXPORT_METHOD(click) {
// 实现前端的click功能
}
@end
事件型功能扩展
事件型功能除了拥有非事件型功能的全部特点外,还拥有事件监听与反馈的能力。前端可能有个 MyModule.addListener(string eventname) 方法调用用于驱动终端监听某个事件,以及一套接收终端事件回调的机制。因此终端将这些机制封装为一个基类HippyEventObserverModule。所有事件型功能都必须继承自这个基类并实现必要的方法。
扩展一个事件型功能包括以下工作:
-
创建对应的事件型功能类,必须继承自
HippyEventObserverModule,并绑定前端组件。在实现文件中使用
HIPPY_EXPORT_MODULE()宏导出。 -
绑定前端方法
与扩展UI组件类似,使用
HIPPY_EXPORT_METHOD()宏绑定前端方法。注意方法名需要对齐。 -
实现
[MyModule addEventObserverForName:]与[MyModule removeEventObserverForName:]方法用以开启、关闭对某个事件的监听行为这两个方法在基类
HippyEventObserverModule中已经实现但未作任何处理,需要MyModule根据需要自行实现。同时这一步是否需要实现取决于前端是否有对应的MyModule.addListener()操作,即希望终端监听某个事件。若无,则终端可以不实现。 -
事件发生后通知前端
终端使用
[MyModule sendEvent:params:]方法通知前端。此方法在基类中已经实现。用户需要将制定参数填入并调用方法即可。 第一个参数为事件名,前端终端事件名必须一致。 第二个参数为事件信息,NSDictionary类型。
TestModule.h
//注意继承自HippyEventObserverModule
@interface TestModule : HippyEventObserverModule <HippyBridgeModule>
@end
TestModule.m
@implementation TestModule
HIPPY_EXPORT_MODULE()
HIPPY_EXPORT_METHOD(click) {
// 实现前端的click功能
}
- (void) addEventObserverForName:(NSString *)eventName {
// 监听customevent事件
if ([eventName isEqualToString:@"customevent"]) {
addLisener(eventName);
}
}
- (void) removeEventObserverForName:(NSString *)eventName {
// 移除customevent事件
if ([eventName isEqualToString:@"customevent"]) {
removeLisener(eventName);
}
}
- (void) eventOccur {
// 事件发生,通知前端
[self sendEvent:@"customevent" params:@{@"key": @"value"}];
}
@end
Voltron
很多时候 JS 需要访问对应终端的一些能力模块,比如数据库、下载、网络请求等,这时候就需要使用 Module 来暴露接口给JS使用。Voltron SDK 中默认实现了部分 Module,但这极有可能无法满足你的需求,这就需要你对 Module 进行扩展封装。
Module扩展
我们将以 TestModule 为例,从头扩展一个 Module,这个 Module 将展示前端如何调用终端能力,并且把结果返回给前端
终端扩展 Module 包括四步:
- 创建
TestModule - 实现导出给
JS的方法。 - 注册
Module。
1. 创建 TestModule
首先我们需要创建 TestModule ,并且继承 VoltronNativeModule 。
import 'package:voltron/voltron.dart';
class TestModule extends VoltronNativeModule {
static const String kModuleName = "TestModule";
TestModule(EngineContext context) : super(context);
@override
Map<String, Function> get extraFuncMap => {};
@override
String get moduleName => kModuleName;
}
2. 实现导出给 JS 的方法
class TestModule extends VoltronNativeModule {
static const String kModuleName = "TestModule";
// 这里定义方法名
static const String kLogMethodName = "log";
static const String kHelloNativeMethodName = "helloNative";
static const String kHelloNativeWithPromiseMethodName = "helloNativeWithPromise";
TestModule(EngineContext context) : super(context);
/***
* log
* @param log
* @param promise
* 自定义了扩展了一个 log 的接口并且无回调
*/
@VoltronMethod(kLogMethodName)
bool log(String log, JSPromise promise) {
// 这里回来的参数可以为 dart 的基础类型和 VoltronMap (对应前端{})与 VoltronArray (对应前端[]),但是前端调用的时候必须对应上
LogUtils.d("TestModule", log);
return true;
}
/***
* helloNative
* @param voltronMap
* @param promise
* 自定义了扩展了一个helloNative的接口,传入复杂结构参数
*/
@VoltronMethod(kHelloNativeMethodName)
bool helloNative(VoltronMap voltronMap, JSPromise promise) {
// 这里回来的参数可以为dart的基础类型和VoltronMap(对应前端{})与VoltronArray(对应前端[]),但是前端调用的时候必须对应上
String? hello = voltronMap.get<String>("hello");
if (hello != null) {
LogUtils.d("TestModule", hello);
}
return true;
}
/***
* helloNativeWithPromise
* @param voltronMap
* @param promise
* 自定义了扩展了一个helloNativeWithPromise的接口,支持回调
*/
@VoltronMethod(kHelloNativeWithPromiseMethodName)
bool helloNativeWithPromise(VoltronMap voltronMap, Promise promise) {
// 这里回来的参数可以为 dart 的基础类型和 VoltronMap (对应前端{})与 VoltronArray (对应前端[]),但是前端调用的时候必须对应上
String? hello = voltronMap.get<String>("hello");
if (hello != null) {
LogUtils.d("TestModule", hello);
if (hello.isNotEmpty) {
// 如果模块这里处理成功回调 resolve
VoltronMap hippyMap1 = VoltronMap();
hippyMap1.push("code", 1);
hippyMap1.push("result", "hello i am from native");
promise.resolve(hippyMap1);
return true;
}
}
// 失败就回调 reject
VoltronMap hippyMap1 = VoltronMap();
hippyMap1.push("code", -1);
promise.reject(hippyMap1);
return true;
}
// 这里填写方法名与方法的对应关系
@override
Map<String, Function> get extraFuncMap => {
kLogMethodName: log,
kHelloNativeMethodName: helloNative,
kHelloNativeWithPromiseMethodName: helloNativeWithPromise,
};
@override
String get moduleName => kModuleName;
}
!> 主要注意的是,这里与Hippy有几处不同
-
不管前端调用
callNative,还是callNativeWithPromise,终端这里最后一个参数始终为promise,这里是由于flutter与安卓不同,这里不存在反射,我们无法直接获取到方法中的参数个数,所以默认最后一个参数必须填写promise,如果你调用的是callNative,也不用过于关注,这里对你没有任何影响。 -
方法必须返回
bool,返回值的最大作用主要是为了让用户能够明确自己必须要处理promise的状态,这里如果返回true,则外部会忽略,如果返回false,则外部会默认返回兜底值
bool helloNativeWithPromise(VoltronMap voltronMap, Promise promise) {}
3. 注册 Module
上面的工作做完后,我们需要把模块注册进入 Voltron 应用,还记得初始化时的 MyAPIProvider 吗,这里我们要传入在 nativeModuleGeneratorList
class MyAPIProvider implements APIProvider {
// 这个是模块扩展
@override
List<ModuleGenerator> get nativeModuleGeneratorList => [
ModuleGenerator(
TestModule.kModuleName,
(context) => TestModule(context),
),
];
@override
List<JavaScriptModuleGenerator> get javaScriptModuleGeneratorList => [];
@override
List<ViewControllerGenerator> get controllerGeneratorList => [];
}
Web
模块的扩展
扩展模块主要包括:
- 创建
HippyWebModule的子类 - 设置
Moduel的name属性 - 实现
Module需要暴露给前端的API
其中 HippyWebModule 类标准化了 HippyWebRenderer 可使用的模块,提供了一些 HippyWebRenderer 的上下文,在一个自定义组件中有几个比较重要的属性:
- name:定义了模块的名字,与 JS 业务侧使用
callNative(moduleName,methodName)中的moduleName相对应 - context:提供了一系列的方法
sendEvent(type: string, params: any); //发送事件
sendUiEvent(nodeId: number, type: string, params: any); // 发送 UI 相关事件
sendGestureEvent(e: HippyTransferData.NativeGestureEvent); // 发送手势事件
subscribe(evt: string, callback: Function); // 监听某个事件
getModuleByName(moduleName: string); // 使用模块名获取模块
例子
以 CustomModule 为例,从头介绍如何扩展 Module
import { HippyWebModule } from '@hippy/web-renderer';
// 继承自 HippyWebModule
export class CustomModule extends HippyWebModule {
// 设置 Module的 name 属性
name = 'CustomModule';
// 实现API `getBrowserInfo` 和 `setBrowserTitle` ,分别提供了获取当前浏览器的信息和设置浏览器 title 的功能。
// 在提供自定义模块的 api 时,api的参数为 `function name(arg1,arg2...argn,callBack)`,前面的n个参数对应业务侧调用时的传递参数,最后一个 `callback` 是当 JS 业务侧需要有返回值形式的调用时,提供返回结果的回调。
getBrowserInfo(callBack) {
let data = {};
...
callBack.resolve(data);
}
setBrowserTitle(title, callBack) {
if (title) {
window.document.title = title;
};
...
callBack.resolve(true);
// callBack.reject(null);执行失败时
}
}