【转】深入源码探索 ReactNative 通信机制

来源:互联网 发布:淘宝直播有假货吗 编辑:程序博客网 时间:2024/06/03 17:22
       
Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处。

【React Native For Android】 Java<>Js 通信机制源码剖析
本文从源码角度剖析 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 方法即可。
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CoreModulesPackage.java
 
    @Override
    publicList<NativeModule> createNativeModules(
       ReactApplicationContext catalystApplicationContext) {
     returnArrays.<NativeModule>asList(
       newAndroidInfoModule(),
       newDeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler),
       newDebugComponentOwnershipModule(catalystApplicationContext));
    }
 
    @Override
    publicList<Class<? extendsJavaScriptModule>> createJSModules() {
    returnArrays.asList(
      DeviceEventManagerModule.RCTDeviceEventEmitter.class,
      JSTimersExecution.class,
      RCTEventEmitter.class,
      RCTNativeAppEventEmitter.class,
      AppRegistry.class
    }
 
    @Override
    publicList<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
      returnnew ArrayList<>(0);
    }
Js 模块 extends 自 JavascriptModule,映射在 Js 相对应 Js 模块,通过动态代理实现调用 Js 模块。下例 AppRegistry.java 为在加载完 Jsbundle 后,Native 去启动 React Application 的总入口,appkey 为应用的 ID。映射每个 JavascriptModule 的信息保存在 JavaScriptModuleRegistration 中,统一由 JavaScriptModuleRegistry 统一管理。
[Java] 纯文本查看 复制代码
?
1
2
3
4
5
6
7
8
AppRegistry.java
 
publicinterface AppRegistry extendsJavaScriptModule {
 
  voidrunApplication(String appKey, WritableMap appParameters);
  voidunmountApplicationComponentAtRootTag(introotNodeTag);
 
}
Java 模块 extends 自 BaseJavaModule,在 Js 层存在同名文件识别为可调用的 Native。重写 getName 识别为 Js 的模块名,重写 getConstants 识别为 Js 可访问的常量,方法通过注解 @ReactMethod 可识别供 Js 调用的 API 接口,所有 Java 层提供的模块接口统一由 NativeModuleRegistry 统一暴露。
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
AndroidInfoModule.java
 
   publicclass AndroidInfoModule extendsBaseJavaModule {
 
    @Override
    publicString getName() {
     return"AndroidConstants";
    }
 
    @Override
    public@Nullable Map<String, Object> getConstants() {
      HashMap<String, Object> constants = newHashMap<String, Object>();
      constants.put("Version", Build.VERSION.SDK_INT);
      returnconstants;
     }
   }

二、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 的调用
[JavaScript] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
    returnnull;
     }
    }
CatalystInstance 为 Java 与 Js 之前通信的高层接口,已被抽离成接口,CatalystInstanceImpl 为其基础实现类,业务侧在 ReactInstanceManager Create ReactContext 时通过 Builder 构建实例化,业务一般不直接持有 CatalystInstance 的引用,一般通过 Framework 层的 ReactInstanceManager 的实现类进行访问。持有对 JavaScriptModuleRegistry& RativeModuleRegistry 的引用。
[Java] 纯文本查看 复制代码
?
1
2
3
4
5
6
CatalystInstanceImpl.java
 
   @Override
   public<T extendsJavaScriptModule> T getJSModule(Class<T> jsInterface) {
   returnAssertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
   }
在 CatalystInstance 初始化时会调用 initializeBridge 初始化私有成员 ReactBridge,ReactBridge 做为 JNI 层的通信桥接对象,负责 Java<>JCS 之间的通信。在 Java 层调用 JS 会调用 JNI 的 CallFunction 的方法,通过 JSC 转接到 JS 层的模块。
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
CatalystInstanceImpl.java
 
    privateReactBridge 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 = newReactBridge(
           jsExecutor,
           newNativeModulesReactCallback(),
           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);
     }
 
     returnbridge;
    }
    ReactBridge.java
 
    /**
    * All native functions are not thread safe and appropriate queues should be used
     */
    publicnative void loadScriptFromAssets(AssetManager assetManager, String assetName);
    publicnative void loadScriptFromFile(@NullableString fileName, @NullableString sourceURL);
    publicnative void callFunction(intmoduleId, intmethodId, NativeArray arguments);
    publicnative void invokeCallback(intcallbackID, NativeArray arguments);
    publicnative void setGlobalVariable(String propertyName, String jsonEncodedArgument);
    publicnative boolean supportsProfiling();
    publicnative void startProfiler(String title);
    publicnative void stopProfiler(String title, String filename);
    privatenative void handleMemoryPressureModerate();
    privatenative void handleMemoryPressureCritical();

Onload.cpp 为 C++ 层主要入口,涵盖类型操作,jsbundle 加载及全局变量操作等。通过 bridge.cpp 的转接到 JSExector.cpp 执行 JS。JSExector.cpp 最终将调用转发到 JSCHelper.cpp 中执行evaluateScript 的函数,从而执行 JS 的调用。
[C++] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
OnLoad.cpp
 
   staticvoid 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
    
   voidBridge::callFunction(constdouble moduleId, constdouble methodId, constfolly::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(constdouble moduleId, constdouble methodId, constfolly::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),
    };
    returnexecuteJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
   }
   JSCHelpers.cpp
 
   JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef source, constchar *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());
     }
     returnresult;
   }
至此,从 Java -> C++ 层调用链结束,JSC 将执行 JS 调用,在 JS Framewrok 层接收来自 C++的调用为 MessageQueue.js 的 callFunctionReturnFlushedQueue。在调用 CallFunction 执行 Js 后,会调用 flushedQueue 更新队列。
[C++] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
MessageQueue.js
 
    callFunctionReturnFlushedQueue(module, method, args) {
     guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
     });
 
     returnthis.flushedQueue();
    }
    MessageQueue.js
 
    __callFunction(module, method, args) {
      this._lastFlush = newDate().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 根据模块配置表调用相应模块执行。
下面分别借助源码说明上面整个流程。
[C++] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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 = newDate().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();
      });
 
      returnthis.flushedQueue();
    }
 
    invokeCallbackAndReturnFlushedQueue(cbID, args) {
      guard(() => {
        this.__invokeCallback(cbID, args);
        this.__callImmediates();
      });
 
      returnthis.flushedQueue();
    }
 
    flushedQueue() {
      this.__callImmediates();
 
      let queue = this._queue;
      this._queue = [[], [], [], this._callID];
      returnqueue[0].length ? queue : null;
    }
Js 层通过调用__nativeCall 将 ModuleID,MethodID 及 Params 放入不同队列。当 Java 层事件发生后会调用 Java -> Js 整个调用链,最终到 flushedQueue 并返回 MessageQueue。
Java 层收到来自 MessageQueue 的调用信息,查询 Java 层模块配置表,调用相应模块相应接口。
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
CatalystInstanceImpl$NativeModulesReactCallback.java
 
    privateclass NativeModulesReactCallback implementsReactCallback {
 
      @Override
      publicvoid call(intmoduleId, intmethodId, 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 模块,通过反射调用相应接口。   
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
BaseJavaModule.java
 
@Override
publicvoid 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);
 }
}

上述为ReactNative通信核心机制,实验数据证明该机制通信效率相对优于传统Hybrid的通信方式,同时,ReactNative中从<text />到TextView等View的映射同样也是使上该机制
0 0
原创粉丝点击