Files
2025-10-15 14:25:21 +08:00

649 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 自定义终端模块
很多时候 JS 需要访问对应终端的一些能力模块,比如数据库、下载、网络请求等,这时候就需要使用 Module 来暴露接口给JS使用。Hippy SDK 中默认实现了部分 Module,但这极有可能无法满足你的需求,这就需要你对 Module 进行扩展封装。Hippy支持 Android、iOS、Ohos、Flutter、Web(同构) 等平台的模块扩展。
<br/>
# Android
---
## Module扩展
我们将以 TestModule 为例,从头扩展一个 Module,这个 Module 将展示前端如何调用终端能力,并且把结果返回给前端。
终端扩展Module包括四步:
1. 创建 `HippyNativeModuleBase` 的子类。
2. 添加 `HippyNativeModule` 注解。
3. 实现导出给 JS 的方法。
4. 注册 Module。
## 1. 创建HippyNativeModuleBase的子类
首先我们需要创建`TestModule`类,并且继承`HippyNativeModuleBase`。
```java
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:module名称,js调用时需要通过此属性找到对应的module实例对象。
- names:module别名,支持同一个module设置不同的名称。
- init:默认为false,即module在首次调用的时候才会进行实例初始化,如果设置为true,在引擎创建时候就会马上创建实例并初始化
> **注意:init参数在非必要的情况下不要设置为true,否则可能增加引擎启动的耗时。**
``` java
@HippyNativeModule(name = "TestModule")
public class TestModule extends HippyNativeModuleBase
{
...
}
```
## 3. 实现导出给JS的方法
导出给 JS 使用的方法,必须使用注解 `@HippyMethod` ,方法必须为 `public` 类型,返回类型必须为 `void`。
支持的方法参数类型包括:
- Java基本数据类型。
- HippyArray:类似于ArrayList,线程非安全。
- HippyMap:类似于HashMap,线程非安全。
- 基于JSValue的新数据类型:注解参数useJSValueType设置为true时适用。
- Promise:回调JS的触发器,通过 `resolve` 方法返回成功信息给JS。通过 `reject` 方法返回失败实现给JS。
HippyMethod注解参数:
- name:接口名称,js调用时需要通过此参数找到对应的接口信息,并进行反射调用。
- isSync:是否为JSI接口,JSI为同步调用接口,会卡住js线程,只适用于数据结构简单且size较小的数据传输,[JSI特性介绍](feature/feature2.0/jsi.md)
- useJSValueType:接口参数是否使用新数据类型,默认为false,即使用老的HippyMap与HippyArray类型接收参数,设置为true以后参数需要使用基于JSValue为基类的扩展数据类型,[新数据类型介绍](development/type-mapping.md)
> **注意:新数据类型不能与HippyMap或HippyArray相互嵌套混用, 否则会导致数据编解码产生错误。**
```java
@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
需要自定义'APIProvider'类,并实现SDK HippyAPIProvider interface,然后在`getNativeModules` 方法中添加这个 Module,这样它才能在JS中被访问到。
```java
public class MyAPIProvider implements HippyAPIProvider {
@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 MyModule
modules.put(TestModule.class, new Provider<HippyNativeModuleBase>()
{
@Override
public HippyNativeModuleBase get()
{
return new TestModule(context);
}
});
}
@Override
public List<Class<? extends HippyJavaScriptModule>> getJavaScriptModules() {return null;}
@Override
public List<Class<? extends HippyViewController>> getControllers() {return null;}
}
```
## 5. 注册APIProvider
在HippyEngine初始化的EngineInitParams参数属性中设置providers。
``` java
List<HippyAPIProvider> providers = new ArrayList<>();
providers.add(new MyAPIProvider());
initParams.providers = providers;
```
## 注意事项
扩展Module中不能同步执行耗时操作,这可能卡住整个引擎通信线程。存在耗时场景,请使用异步线程处理。
## 混淆说明
扩展 Module 的类名和扩展方法名不能混淆,可以增加混淆例外。
``` java
-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
```objectivec
@interface TestModule : NSObject <HippyBridgeModule>
@end
```
TestModule.m
``` objectivec
@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
``` objectivec
//注意继承自HippyEventObserverModule
@interface TestModule : HippyEventObserverModule <HippyBridgeModule>
@end
```
TestModule.m
``` objectivec
@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
```
# Ohos
很多时候 `JS` 需要访问对应终端的一些能力模块,比如数据库、下载、网络请求等,这时候就需要使用 `Module` 来暴露接口给JS使用。Ohos SDK 中默认实现了部分 `Module`,但这极有可能无法满足你的需求,这就需要你对 `Module` 进行扩展封装。
---
## Module扩展
我们将以 `ExampleNativeModule` 为例,从头扩展一个 `Module`,这个 `Module` 将展示前端如何调用终端能力,并且把结果返回给前端。
终端扩展 `Module` 的步骤:
1. 创建 `HippyNativeModuleBase` 的子类。
2. 实现导出给 JS 的方法。
3. 注册 Module。
4. 注册 HippyAPIProvider。
## 1. 创建 HippyNativeModuleBase 的子类
```typescript
export class ExampleNativeModule extends HippyNativeModuleBase {
public static readonly NAME = 'ExampleNativeModule'
constructor(ctx: HippyEngineContext) {
super(ctx)
}
public call(method: string, params: Array<HippyAny>, promise: HippyModulePromise): HippyAny {
switch (method) {
case 'test': {
this.test();
break;
}
case 'testPromise': {
this.testPromise(params, promise);
break;
}
case 'testSendEvent': {
this.testSendEvent(params, promise);
}
default:
super.call(method, params, promise);
}
return null;
}
public test() {
LogUtils.i(ExampleNativeModule.NAME, 'module test');
}
public testPromise(params: Array<HippyAny>, promise: HippyModulePromise) {
promise.resolve('test');
}
public testSendEvent(params: Array<HippyAny>, promise: HippyModulePromise) {
LogUtils.i(ExampleNativeModule.NAME, 'testSendEvent');
if (this.ctx != null && this.ctx.getModuleManager() != null) {
const eventModule = this.ctx.getModuleManager().getJavaScriptModule(EventDispatcher.MODULE_NAME);
if (eventModule != null) {
const event = 'testEvent';
const params = new Map<string, HippyAny>();
params.set('testString', 'testStringValue');
const valueMap = new Map<string, HippyAny>();
valueMap.set('testString2', 'testStringValue2');
params.set('testMap', valueMap);
const array: HippyArray = [];
array.push(valueMap);
params.set('testArray', array);
(eventModule as EventDispatcher).receiveNativeEvent(event, params);
}
}
}
}
```
需要注意的是,这里与Android、iOS有几处不同。
1. 需要指定 NAME,设置为前端调用的 module name
2. 需要实现 call 方法
## 2. 实现导出给 JS 的方法
例子见上一步 call 方法的实现。
## 3. 注册 Module
```typescript
export class ExampleAPIProvider extends HippyAPIProvider {
getCustomNativeModuleCreatorMap(): Map<string, HippyNativeModuleCreator> | null {
let registerMap: Map<string, HippyNativeModuleCreator> =
new Map()
registerMap.set(ExampleNativeModule.NAME,
(ctx): HippyNativeModuleBase => new ExampleNativeModule(ctx))
return registerMap;
}
}
```
## 4. 注册 HippyAPIProvider
在 HippyEngine 初始化的 EngineInitParams 参数属性中设置 providers。
```typescript
params.providers = new Array(new ExampleAPIProvider())
```
## Turbo Module扩展
和 Module 的扩展一致,不过还需要配置 isTurbo 方法,且不需要实现 call 方法,参考如下:
```typescript
export class ExampleNativeTurboModule extends HippyNativeModuleBase {
public static readonly NAME = 'demoTurbo'
constructor(ctx: HippyEngineContext) {
super(ctx)
}
isTurbo(): boolean {
return true
}
public getString(info: string): string {
return 'demoTurbo' + info;
}
public getNum(num: number): number {
return num;
}
public getBoolean(b: boolean): boolean {
return b;
}
public getMap(map: HippyMap): HippyMap {
return map
}
public getArray(array: HippyArray): HippyArray {
return array
}
public getObject(obj: HippyAny): HippyAny {
return obj
}
public getTurboConfig(): TurboConfig {
return new TurboConfig();
}
public printTurboConfig(turboConfig: TurboConfig): string {
return turboConfig.info;
}
}
```
# Voltron
很多时候 `JS` 需要访问对应终端的一些能力模块,比如数据库、下载、网络请求等,这时候就需要使用 `Module` 来暴露接口给JS使用。Voltron SDK 中默认实现了部分 `Module`,但这极有可能无法满足你的需求,这就需要你对 `Module` 进行扩展封装。
---
## Module扩展
我们将以 `TestModule` 为例,从头扩展一个 `Module`,这个 `Module` 将展示前端如何调用终端能力,并且把结果返回给前端
终端扩展 `Module` 包括四步:
1. 创建 `TestModule`
2. 实现导出给 `JS` 的方法。
3. 注册 `Module`。
## 1. 创建 `TestModule`
首先我们需要创建 `TestModule` ,并且继承 `VoltronNativeModule` 。
```dart
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` 的方法
```dart
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有几处不同
1. 不管前端调用 `callNative` ,还是 `callNativeWithPromise` ,终端这里最后一个参数始终为 `promise` ,这里是由于 `flutter` 与安卓不同,这里不存在反射,我们无法直接获取到方法中的参数个数,所以默认最后一个参数必须填写 `promise` ,如果你调用的是 `callNative` ,也不用过于关注,这里对你没有任何影响。
2. 方法必须返回 `bool`,返回值的最大作用主要是为了让用户能够明确自己必须要处理 `promise` 的状态,这里如果返回 `true` ,则外部会忽略,如果返回 `false` ,则外部会默认返回兜底值
```dart
bool helloNativeWithPromise(VoltronMap voltronMap, Promise promise) {}
```
## 3. 注册 `Module`
上面的工作做完后,我们需要把模块注册进入 Voltron 应用,还记得初始化时的 `MyAPIProvider` 吗,这里我们要传入在 `nativeModuleGeneratorList`
```dart
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
---
## 模块的扩展
扩展模块主要包括:
1. 创建 `HippyWebModule` 的子类
2. 设置 `Moduel` 的 `name` 属性
3. 实现 `Module` 需要暴露给前端的 `API`
其中 `HippyWebModule` 类标准化了 HippyWebRenderer 可使用的模块,提供了一些 HippyWebRenderer 的上下文,在一个自定义组件中有几个比较重要的属性:
- name:定义了模块的名字,与 JS 业务侧使用 `callNative(moduleName,methodName)` 中的 `moduleName` 相对应
- context:提供了一系列的方法
```javascript
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
```javascript
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);执行失败时
}
}
```