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

       
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
     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 统一管理。
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
8
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 统一暴露。
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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 的调用
[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);
     return null ;
      }
     }
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 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 层的模块。
[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
 
     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 的调用。
[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
 
    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 更新队列。
[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();
      });
 
      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 根据模块配置表调用相应模块执行。
下面分别借助源码说明上面整个流程。
[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 = 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 层模块配置表,调用相应模块相应接口。
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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 模块,通过反射调用相应接口。    
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
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);
  }
}

上述为ReactNative通信核心机制,实验数据证明该机制通信效率相对优于传统Hybrid的通信方式,同时,ReactNative中从<text />到TextView等View的映射同样也是使上该机制
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值