自定义终端模块
很多时候 JS 需要访问对应终端的一些能力模块,比如数据库、下载、网络请求等,这时候就需要使用 Module 来暴露接口给JS使用。Hippy SDK 中默认实现了部分 Module,但这极有可能无法满足你的需求,这就需要你对 Module 进行扩展封装。
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;
}
5. 执行流程
1. 注册流程
2. 使用流程
6. 源码
1. 注册流程
MyActivity
在onCreate
时初始化Hippy引擎
mHippyEngine.initEngine(new HippyEngine.EngineListener() {...});
// HippyEngineManagerImpl#initEngine
public void initEngine(EngineListener listener) {
...
restartEngineInBackground();
...
}
// HippyEngineManagerImpl#restartEngineInBackground
private synchronized void restartEngineInBackground() {
...
mEngineContext = new HippyEngineContextImpl(mDebugMode, mServerHost);
...
}
// HippyEngineContextImpl#constructor
public HippyEngineContextImpl(DebugMode debugMode, String debugServerHost) {
mModuleManager = new HippyModuleManagerImpl(this, mAPIProviders);
...
}
// HippyModuleManagerImpl#constructor
public HippyModuleManagerImpl(HippyEngineContext context, List<HippyAPIProvider> packages) {
this.mContext = context;
mNativeModuleInfo = new ConcurrentHashMap<>();
mJsModules = new HashMap<>();
/* 分析1 */
addModules(packages);
}
- / 分析1 /
HippyModuleManagerImpl
将指定本地模块添加到内存中的入口
public synchronized void addModules(List<HippyAPIProvider> apiProviders) {
...
for (HippyAPIProvider provider : apiProviders) {
// 此处取出的即是在 【4. 注册Module 】步骤中添加的 module
Map<Class<? extends HippyNativeModuleBase>, Provider<? extends
HippyNativeModuleBase>> nativeModules = provider.getNativeModules(mContext);
if (nativeModules != null && nativeModules.size() > 0) {
Set<Class<? extends HippyNativeModuleBase>> keys = nativeModules.keySet();
for (Class cls : keys) {
/* 分析2 */
addNativeModule(cls, nativeModules.get(cls));
}
}
...
}
}
- / 分析2 /
HippyModuleManagerImpl
将本地模块转换成模块名与模块信息的映射
public synchronized <T extends HippyNativeModuleBase> void addNativeModule(Class<T> cls,
Provider<T> provider) {
...
/* 分析3 */
HippyNativeModuleInfo moduleInfo = new HippyNativeModuleInfo(cls, provider);
String[] names = moduleInfo.getNames();
if (names != null && names.length > 0) {
for (String name : names) {
if (!mNativeModuleInfo.containsKey(name)) {
mNativeModuleInfo.put(name, moduleInfo);
}
}
}
...
}
- / 分析3 /
HippyNativeModuleInfo
保存模块信息,如有必要则注册模块的方法并初始化模块
public HippyNativeModuleInfo(@NonNull Class<?> cls,
Provider<? extends HippyNativeModuleBase> provider) {
HippyNativeModule annotation = cls.getAnnotation(HippyNativeModule.class);
mClass = cls;
mProvider = provider;
if (annotation != null) {
mName = annotation.name();
mNames = annotation.names();
mThread = annotation.thread();
/* 分析a */
initImmediately(annotation);
}
}
/* 分析a */
private void initImmediately(@NonNull HippyNativeModule annotation) {
// 如果注解要求立即初始化,则注册模块的方法并初始化模块
if (annotation.init()) {
try {
/* 分析b */
initialize();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
/* 分析b */
public void initialize() {
if (mInit) {
return;
}
/* 分析c */
// 注册模块的方法,不重入
checkModuleMethods();
// 新建模块实例
mInstance = mProvider.get();
// 初始化模块
mInstance.initialize();
mInit = true;
}
/* 分析c */
// 检查一个模块的方法是否已经注册,没有则立即注册
private void checkModuleMethods() {
if (mMethods != null) {
return;
}
synchronized (this) {
if (mMethods != null) {
return;
}
mMethods = new ConcurrentHashMap<>();
Method[] targetMethods = mClass.getMethods();
for (Method targetMethod : targetMethods) {
HippyMethod hippyMethod = targetMethod.getAnnotation(HippyMethod.class);
if (hippyMethod == null) {
continue;
}
String methodName = hippyMethod.name();
...
mMethods.put(methodName, new HippyNativeMethod(targetMethod,
hippyMethod.isSync(),
hippyMethod.useJSValueType()));
}
}
}
2. 使用流程
HippyBridgeImpl
前端调用模块方法后,android端的执行入口
public void callNatives(String moduleName, String moduleFunc, String callId, byte[] buffer) {
/* a */
callNatives(moduleName, moduleFunc, callId, ByteBuffer.wrap(buffer));
}
/* a */
public void callNatives(String moduleName, String moduleFunc, String callId,
ByteBuffer buffer) {
...
/* b */
mBridgeCallback.callNatives(moduleName, moduleFunc, callId, params);
}
/* b */
/* HippyBridgeManagerImpl */
public void callNatives(String moduleName, String moduleFunc, String callId,
Object params) {
...
HippyCallNativeParams callNativeParams = HippyCallNativeParams
.obtain(moduleName, moduleFunc, callId, params);
/* 分析1 */
moduleManager.callNatives(callNativeParams);
}
- / 分析1 /
HippyModuleManagerImpl
根据注解中配置的线程发送消息
public void callNatives(HippyCallNativeParams params) {
...
if (moduleInfo.getThread() == HippyNativeModule.Thread.DOM) {
Handler handler = getDomThreadHandler();
Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params);
handler.sendMessage(msg);
} else if (moduleInfo.getThread() == HippyNativeModule.Thread.MAIN) {
Handler handler = getUIThreadHandler();
Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params);
handler.sendMessage(msg);
} else {
Handler handler = getBridgeThreadHandler();
Message msg = handler.obtainMessage(MSG_CODE_CALL_NATIVES, params);
handler.sendMessage(msg);
}
}
public boolean handleMessage(Message msg) {
...
case MSG_CODE_CALL_NATIVES: {
...
/* 分析2 */
doCallNatives(params);
...
}
...
}
- / 分析2 /
HippyModuleManagerImpl
利用注册的模块信息调用相关方法
void doCallNatives(@NonNull HippyCallNativeParams params) {
PromiseImpl promise = new PromiseImpl(mContext, params.moduleName, params.moduleFunc,
params.callId);
try {
// 获取注册的模块信息
HippyNativeModuleInfo moduleInfo = mNativeModuleInfo.get(params.moduleName);
if (moduleInfo == null) {
promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR,
"module can not be found");
return;
}
// 初始化模块 (可重入,不会重复初始化)
moduleInfo.initialize();
// 获取注册的模块方法
HippyNativeModuleInfo.HippyNativeMethod method = moduleInfo
.findMethod(params.moduleFunc);
if (method == null || method.isSync()) {
promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR,
"module function can not be found");
return;
}
/* a */
method.invoke(moduleInfo.getInstance(), params.params, promise);
} catch (Throwable e) {
promise.doCallback(PromiseImpl.PROMISE_CODE_NORMAN_ERROR, e.getMessage());
mContext.getGlobalConfigs().getExceptionHandler()
.handleNativeException(new RuntimeException(e), true);
}
}
/* a */
// HippyNativeModuleInfo
public void invoke(Object receiver, @Nullable Object args,
PromiseImpl promise) throws Exception {
Object[] params = null;
if (args != null) {
params = prepareArguments(args, promise);
}
// 反射调用
mMethod.invoke(receiver, params);
if (promise.needResolveBySelf()) {
promise.resolve("");
}
}