Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处。
本文从源码角度剖析 RNA 中 Java <> Js 的通信机制(基于最新的 RNA Release 20)。
对于传统 Java<>Js 通信而言,Js 调用 Java 通不外乎 Jsbridge、onprompt、log 及 addjavascriptinterface 四种方式,在 Java 调用 Js 只有 loadurl 及高版本才支持的 evaluateJavaScript 两种。但在 RN 中没有采用了传统 Java 与 Js 之间的通信机制,而是借助 MessageQueue 及模块配置表,将调用转化为{moduleID, methodID,callbackID,args},处理端在模块配置表里查找注册的模块与方法并调用。
一、Module Registry
在 RNA 中,在应用启动时根据 ReactPackage 会自动生成 NativeModuleRegistry 及 JavaScriptModuleRegistry 两份模块配置表,包含系统及自定义模块,Java 端与 Js 端持有相同的模块配置表,标识为可识别为 Native 模块或 Js 模块都是通过实现相应接口,并将实例添加 ReactPackage 的 CreactXXModules 方法即可。
CoreModulesPackage.java
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext catalystApplicationContext) {
return Arrays.<NativeModule>asList(
new AndroidInfoModule(),
new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler),
new DebugComponentOwnershipModule(catalystApplicationContext));
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Arrays.asList(
DeviceEventManagerModule.RCTDeviceEventEmitter.class,
JSTimersExecution.class,
RCTEventEmitter.class,
RCTNativeAppEventEmitter.class,
AppRegistry.class
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return new ArrayList<>(0);
}
Js 模块 extends 自 JavascriptModule,映射在 Js 相对应 Js 模块,通过动态代理实现调用 Js 模块。下例 AppRegistry.java 为在加载完 Jsbundle 后,Native 去启动 React Application 的总入口,appkey 为应用的 ID。映射每个 JavascriptModule 的信息保存在 JavaScriptModuleRegistration 中,统一由 JavaScriptModuleRegistry 统一管理。
AppRegistry.java
public interface AppRegistry extends JavaScriptModule {
void runApplication(String appKey, WritableMap appParameters);
void unmountApplicationComponentAtRootTag(int rootNodeTag);
}
Java 模块 extends 自 BaseJavaModule,在 Js 层存在同名文件识别为可调用的 Native。重写 getName 识别为 Js 的模块名,重写 getConstants 识别为 Js 可访问的常量,方法通过注解 @ReactMethod 可识别供 Js 调用的 API 接口,所有 Java 层提供的模块接口统一由 NativeModuleRegistry 统一暴露。
AndroidInfoModule.java
public class AndroidInfoModule extends BaseJavaModule {
@Override
public String getName() {
return "AndroidConstants";
}
@Override
public @Nullable Map<String, Object> getConstants() {
HashMap<String, Object> constants = new HashMap<String, Object>();
constants.put("Version", Build.VERSION.SDK_INT);
return constants;
}
}
二、Java -> Js
完整通信机制流程图:
简要说明下这5个步骤:
1.CatalystanceImpl 为 Js<>Java 通信高层封装实现类,业务模块通过 ReactInstanceManager 与 CatalystanceImpl 间接通信,调用Js暴露出来的API。
2.将来自Java层的调用拆分为 ModuleID,MethodID 及 Params,JavaScriptModuleInvocationHandler 通过动态代理方式交由 CatalystanceImpl 统一处理。
CatalystanceImpl 进一步将 ModuleID,MethodID 及 Params 转交给 ReactBridge JNI 处理。
-
ReactBridge 调用 C++层的调用链转发 ModuleID,MethodID 及 Params。
5.最终通过 JSCHelper 的 evaluateScript 的方法将 ModuleID,MethodID 及 Params 借助 JSC 传递给 Js 层。
整体调用关系比较清晰,下面分别借助源码说明上面整个流程。
在 Java 层 implements JavaScriptModule 这个 interface 被识别为 Js 层暴露的公共 Module,(由JS不允许的方法名称重载,所以继承自 JavaScriptModule 同样不允许方法重载)。JavaScriptModuleRegistry 负责管理所有的 JavaScriptModule,持有对 JavaScriptModuleInvocationHandler 的引用,通过 invoke 的方式,统一调度从 Java -> Js 的调用。
JavaScriptModuleInvocationHandler.java
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstanceImpl mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration;
public JavaScriptModuleInvocationHandler(
CatalystInstanceImpl catalystInstance,
JavaScriptModuleRegistration moduleRegistration) {
mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String tracingName = mModuleRegistration.getTracingName(method);
mCatalystInstance.callFunction(
mModuleRegistration.getModuleId(),
mModuleRegistration.getMethodId(method),
Arguments.fromJavaArgs(args),
tracingName);
return null;
}
}
CatalystInstance 为 Java 与 Js 之前通信的高层接口,已被抽离成接口,CatalystInstanceImpl 为其基础实现类,业务侧在 ReactInstanceManager Create ReactContext 时通过 Builder 构建实例化,业务一般不直接持有 CatalystInstance 的引用,一般通过 Framework 层的 ReactInstanceManager 的实现类进行访问。持有对 JavaScriptModuleRegistry& RativeModuleRegistry 的引用。
CatalystInstanceImpl.java
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
}
在 CatalystInstance 初始化时会调用 initializeBridge 初始化私有成员 ReactBridge,ReactBridge 做为 JNI 层的通信桥接对象,负责 Java<>JCS 之间的通信。在 Java 层调用 JS 会调用 JNI 的 CallFunction 的方法,通过 JSC 转接到 JS 层的模块。
CatalystInstanceImpl.java
private ReactBridge initializeBridge(
JavaScriptExecutor jsExecutor,
JavaScriptModulesConfig jsModulesConfig) {
mReactQueueConfiguration.getJSQueueThread().assertIsOnThread();
Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor");
ReactBridge bridge;
try {
bridge = new ReactBridge(
jsExecutor,
new NativeModulesReactCallback(),
mReactQueueConfiguration.getNativeModulesQueueThread());
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig");
try {
bridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
bridge.setGlobalVariable(
"__RCTProfileIsProfiling",
Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false");
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
return bridge;
}
ReactBridge.java
/**
* All native functions are not thread safe and appropriate queues should be used
*/
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
public native void loadScriptFromFile(@Nullable String fileName, @Nullable String sourceURL);
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
public native void invokeCallback(int callbackID, NativeArray arguments);
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
public native boolean supportsProfiling();
public native void startProfiler(String title);
public native void stopProfiler(String title, String filename);
private native void handleMemoryPressureModerate();
private native void handleMemoryPressureCritical();
Onload.cpp 为 C++ 层主要入口,涵盖类型操作,jsbundle 加载及全局变量操作等。通过 bridge.cpp 的转接到 JSExector.cpp 执行 JS。JSExector.cpp 最终将调用转发到 JSCHelper.cpp 中执行evaluateScript 的函数,从而执行 JS 的调用。
OnLoad.cpp
static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId,
NativeArray::jhybridobject args) {
auto bridge = extractRefPtr<Bridge>(env, obj);
auto arguments = cthis(wrap_alias(args));
try {
bridge->callFunction(
(double) moduleId,
(double) methodId,
std::move(arguments->array)
);
} catch (...) {
translatePendingCppExceptionToJavaException();
}
Bridge.cpp
void Bridge::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
if (*m_destroyed) {
return;
}
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.callFunction");
#endif
auto returnedJSON = m_jsExecutor->callFunction(moduleId, methodId, arguments);
m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */);
}
JSCExectutor.cpp
std::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
// TODO: Make this a first class function instead of evaling. #9317773
std::vector<folly::dynamic> call{
(double) moduleId,
(double) methodId,
std::move(arguments),
};
return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
}
JSCHelpers.cpp
JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef source, const char *cachePath) {
JSValueRef exn, result;
#if WITH_FBJSCEXTENSIONS
if (source){
// If evaluating an application script, send it through `JSEvaluateScriptWithCache()`
// to add cache support.
result = JSEvaluateScriptWithCache(context, script, NULL, source, 0, &exn, cachePath);
} else {
result = JSEvaluateScript(context, script, NULL, source, 0, &exn);
}
#else
result = JSEvaluateScript(context, script, NULL, source, 0, &exn);
#endif
if (result == nullptr) {
Value exception = Value(context, exn);
std::string exceptionText = exception.toString().str();
FBLOGE("Got JS Exception: %s", exceptionText.c_str());
auto line = exception.asObject().getProperty("line");
std::ostringstream locationInfo;
std::string file = source != nullptr ? String::adopt(source).str() : "";
locationInfo << "(" << (file.length() ? file : "<unknown file>");
if (line != nullptr && line.isNumber()) {
locationInfo << ":" << line.asInteger();
}
locationInfo << ")";
throwJSExecutionException("%s %s", exceptionText.c_str(), locationInfo.str().c_str());
}
return result;
}
至此,从 Java -> C++ 层调用链结束,JSC 将执行 JS 调用,在 JS Framewrok 层接收来自 C++的调用为 MessageQueue.js 的 callFunctionReturnFlushedQueue。在调用 CallFunction 执行 Js 后,会调用 flushedQueue 更新队列。
MessageQueue.js
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => {
this.__callFunction(module, method, args);
this.__callImmediates();
});
return this.flushedQueue();
}
MessageQueue.js
__callFunction(module, method, args) {
this._lastFlush = new Date().getTime();
this._eventLoopStartTime = this._lastFlush;
if (isFinite(module)) {
method = this._methodTable[module][method];
module = this._moduleTable[module];
}
Systrace.beginEvent(`${module}.${method}()`);
if (__DEV__ && SPY_MODE) {
console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')');
}
var moduleMethods = this._callableModules[module];
invariant(
!!moduleMethods,
'Module %s is not a registered callable module.',
module
);
moduleMethods[method].apply(moduleMethods, args);
Systrace.endEvent();
}
三、Js -> Java
对于 JS -> Java 调用的设计相对独特,在 React Native 的设计中, JS 是不能直接调用 Java 的接口的,而是将来自 JS 层的调用 Push 到 JS 层的一个 MessageQueue 中,在事件发生时会调用 JS 相应的模块方法去处理,处理完这些事件后再执行 JS 想让 Java 执行的方法,与 native 开发里事件响应机制是一致的。
完整通信机制流程图:
简要说明下这5个步骤:
1.JS 层调用 Java 层暴露的 API。
2.将来自 JS 层的调用拆分为 ModuleID,MethodID 及 Params 分别 push 进相应的 queue 中。
3.当事件发生时,会执行从 Java -> JS 上面这条调用链路。
4.在执行完 callFunctionReturnFlushedQueue 后,会调用 flushedQueue 并返回 MessageQueue,即刷新后的队列。
5.Java 层的 JavaRegistry 根据模块配置表调用相应模块执行。
下面分别借助源码说明上面整个流程。
MessageQueue.js
__nativeCall(module, method, params, onFail, onSucc) {
if (onFail || onSucc) {
// eventually delete old debug info
(this._callbackID > (1 << 5)) &&
(this._debugInfo[this._callbackID >> 5] = null);
this._debugInfo[this._callbackID >> 1] = [module, method];
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
}
global.nativeTraceBeginAsyncFlow &&
global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID);
this._callID++;
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
var now = new Date().getTime();
if (global.nativeFlushQueueImmediate &&
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
global.nativeFlushQueueImmediate(this._queue);
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
}
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
if (__DEV__ && SPY_MODE && isFinite(module)) {
console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
}
}
MessageQueue.js
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => {
this.__callFunction(module, method, args);
this.__callImmediates();
});
return this.flushedQueue();
}
invokeCallbackAndReturnFlushedQueue(cbID, args) {
guard(() => {
this.__invokeCallback(cbID, args);
this.__callImmediates();
});
return this.flushedQueue();
}
flushedQueue() {
this.__callImmediates();
let queue = this._queue;
this._queue = [[], [], [], this._callID];
return queue[0].length ? queue : null;
}
Js 层通过调用__nativeCall 将 ModuleID,MethodID 及 Params 放入不同队列。当 Java 层事件发生后会调用 Java -> Js 整个调用链,最终到 flushedQueue 并返回 MessageQueue。
Java 层收到来自 MessageQueue 的调用信息,查询 Java 层模块配置表,调用相应模块相应接口。
CatalystInstanceImpl$NativeModulesReactCallback.java
private class NativeModulesReactCallback implements ReactCallback {
@Override
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
}
}
查询到相应 Java 模块,通过反射调用相应接口。
BaseJavaModule.java
@Override
public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod");
try {
mMethod.invoke(BaseJavaModule.this, mArguments);
} catch (IllegalArgumentException ie) {
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
如果你觉得内容意犹未尽,如果你想了解更多相关信息,请扫描以下二维码,关注我们的公众账号,可以获取更多技术类干货,还有精彩活动与你分享~
腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!
【腾讯Bugly干货分享】深入源码探索 ReactNative 通信机制的更多相关文章
-
【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李 ...
-
【腾讯Bugly干货分享】微信iOS SQLite源码优化实践
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57b58022433221be01499480 作者:张三华 前言 随着微信iO ...
-
【腾讯Bugly干货分享】微信Tinker的一切都在这里,包括源码(一)
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ecdf2d98250b4631ae034b 最近半年以来,Android热补 ...
-
【腾讯Bugly干货分享】微信热补丁Tinker的实践演进之路
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ad7a70eaed47bb2699e68e Dev Club 是一个交流移动 ...
-
【腾讯Bugly干货分享】H5 视频直播那些事
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57a42ee6503dfcb22007ede8 Dev Club 是一个交流移动 ...
-
【腾讯Bugly干货分享】React Native项目实战总结
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/577e16a7640ad7b4682c64a7 “8小时内拼工作,8小时外拼成长 ...
-
【腾讯Bugly干货分享】聊聊苹果的Bug - iOS 10 nano_free Crash
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/hnwj24xqrtOhcjEt_TaQ9w 作者:张 ...
-
【腾讯Bugly干货分享】跨平台 ListView 性能优化
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/FbiSLPxFdGqJ00WgpJ94yw 导语 精 ...
-
【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog
本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/581c2c46bef1702a2db3ae53 Dev Club 是一个交流移动 ...
随机推荐
-
1500. Prime Gap 11 月 11日
/*本篇为转载,在此申明,具体就是先设定从2以后所有的数都为质数,定为质数的数的倍数则不是质数,慢慢排除后面的数*/ #include<iostream>#include<cstri ...
-
使用微软 URL Rewrite Module 开启IIS伪静态
原文 使用微软 URL Rewrite Module 开启IIS伪静态 在IIS5和IIS6时代,我们使用URL REWRITING可实现URL重写,使得WEB程序实现伪静态,但默认情况下只能实现.A ...
-
CodeForces 721D Maxim and Array
贪心,优先队列. 先看一下输入的数组乘积是正的还是负的. ①如果是负的,也就是接下来的操作肯定是让正的加大,负的减小.每次寻找一个绝对值最小的数操作就可以了. ②如果是正的,也是考虑绝对值,先操作绝对 ...
-
入坑IT十年(二)技术以外
上一篇博客里提到:技术越来越简单,发布后不久,就看到<技术并不是越来越简单>,这显然是打擂台来了. 技术究竟是不是越来越简单?其实这个问题,要看你究竟是以什么角度来思考这个问题.我们可以举 ...
-
Centos6.8 yum安装MySQL5.6
第一步:安装仓库wget http://repo.mysql.com//mysql57-community-release-el6-8.noarch.rpmrpm -ivh mysql-communi ...
-
P0505
算法训练 P0505 时间限制:1.0s 内存限制:256.0MB 一个整数n的阶乘可以写成n!,它表示从1到n这n个整数的乘积.阶乘的增长速度非常快,例如,13!就已经比较大了 ...
-
20175314 《Java程序设计》第三周学习总结
20175314 <Java程序设计>第三周学习总结 教材学习内容总结 编程语言的发展事是从面向机器(汇编.机器)到面向过程(C)再到面向对象(Java) 成员变量: 1.成员变量定义在类 ...
-
QTableView 二次整理
一.设置可视化的组件 参考: http://www.cnblogs.com/ribavnu/p/4810412.html 二.常用基本属性 http://www.cnblogs.com/ribavnu ...
-
CSS选择符-----属性选择符
Element[att] 选择具有att属性的E元素 <!DOCTYPE html> <html> <head> <meta charset=" ...
-
LeetCode: 【L4】N-Queens 解题报告
[L4]N-Queens 解题报告 N-Queens Total Accepted: 16418 Total Submissions: 63309 My Submissions The n-queen ...