• React-Native系列Android——Native与Javascript通信原理(一)


    React-Native最核心的是NativeJavascript之间的通信,并且是双向通信。Native层到Javascript层,Javascript层到Native层。虽说是两个方向,但实现上大同小异,我们先从Native层入手,研究一下Native调用Javascript的过程。


    1、通信模型

    Android应用层的程序语言是JavaReact-NativeNative端的框架实现用的也是Java语言,所以实质上是JavaJavascript两种程序语言的调用。

    事实上这个过程,在Android系统上已经有了实现。就是WebView。熟悉WebView的都知道底层实现是WebKit,虽然在Android 4.4系统上切换成了Chromium,但归根结底还是WebKit的变种,仅仅是加了谷歌自己的一些东西。然而React-NativeWebView并没有一点关系,并且后者的WebKit内核也不支持ES6特性(React语法大多基于ES6),那怎么办?仅仅能自己弄一套最新的WebKit作为React-Native的解释器了,这个从安卓projectlib文件夹以下的libjsc.so动态链接库文件能够印证,这样做还有两个重要优点就是兼容绝大多少设备版本号和方便加入自己定义功能。

    所以由此,我们大概能够猜到React-Native的通信原理,画一张图来简单地描写叙述一下:


    2、Java层实现

    之前说过。React-Native的重要设计思想是组件化,为了便于维护扩展和减少耦合,React-Native并没有为了实现某一详细的通信编写代码(比方上篇博文所讲的触摸事件传递),而是设计了一套标准用于组件化。

    这套标准是向开发人员开放的,开发人员能够自行编写须要的组件用来在NativeJavascript之间通信,虽然这并非推荐的选择。

    2.1 JavaScriptModule组件

    React-Native官方实现了一定数量的组件,比方触摸事件组件。按键组件等。这些组件都位于CoreModulesPackage中,属于默认载入的。全部的组件都必须继承JavaScriptModule接口标准。JavaScriptModule位于com.facebook.react.bridge包以下:

    /**
     * Interface denoting that a class is the interface to a module with the same name in JS. Calling
     * functions on this interface will result in corresponding methods in JS being called.
     *
     * When extending JavaScriptModule and registering it with a CatalystInstance, all public methods
     * are assumed to be implemented on a JS module with the same name as this class. 
     *
     * NB: JavaScriptModule does not allow method name overloading because JS does not allow method name
     * overloading.
     */
    @DoNotStrip
    public interface JavaScriptModule {
    }

    阅读一下凝视,主要有三点信息:
    1、全部组件必须继承JavaScriptModule,并注冊在CatalystInstance中。
    2、全部public方法与Javascript层保持同名并由后者详细实现。


    3、因为Javascript不支持重载。所以Java中也不能有重载。

    细致的读者会发现,凝视里有两个单词非常关键。extendingimplementedJavaextendJavascriptimplement,也就是说Java层仅仅做接口定义。而实现由Javascript完毕。所以。搜索一下JavaScriptModule的子类会发现它们都是接口。没有详细实现类。

    有点晦涩但事实上非常好理解,举个简单的样例。去餐馆吃饭,顾客(Java)仅仅要定义(interface)好想吃什么菜,然后和餐馆(Bridge)说。餐馆会通知自己的厨师(Javascript)把详细的菜做好。这就是一个简单的通信过程Java->Bridge->Javascript

    2.2 JavaScriptModule组件的注冊

    上一篇文章讲的触摸事件的处理。里面提到一个RCTEventEmitter的类,用来将每一个触摸事件都传递给Javascript层,这个组件就是继承于JavaScriptModule

    public interface RCTEventEmitter extends JavaScriptModule {
      public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event);
      public void receiveTouches(
          String eventName,
          WritableArray touches,
          WritableArray changedIndices);
    }

    先前凝视中第1点,全部JavaScriptModule组件都必须在CatalystInstance中注冊。那我们来看一下注冊的过程。

    RCTEventEmitterfacebook官方定义的。组装在CoreModulesPackage中。而全部的package都是在com.facebook.react.ReactInstanceManagerImpl中处理的,看一下代码:

    class ReactInstanceManagerImpl extends ReactInstanceManager {
    
      ...
    
      private ReactApplicationContext createReactContext(
          JavaScriptExecutor jsExecutor,
          JSBundleLoader jsBundleLoader) {
         ...
         try {
          CoreModulesPackage coreModulesPackage =
              new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
          processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
        } finally {
          ...
        }
    
        for (ReactPackage reactPackage : mPackages) {
          ...
          try {
            processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
          } finally {
            ...
          }
        }
    
      }
    
      private void processPackage(ReactPackage reactPackage, ReactApplicationContext reactContext, NativeModuleRegistry.Builder nativeRegistryBuilder, JavaScriptModulesConfig.Builder jsModulesBuilder) {
    
        ...
    
        for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()){
          jsModulesBuilder.add(jsModuleClass);
        }
      }
      ...
    
    }

    能够看到CoreModulesPackage和开发人员扩展自己定义的mPackages都是通过processPackage方法里加入到JavaScriptModulesConfig里注冊的。

    简单的建造者模式,我们直接看一下JavaScriptModulesConfig类,位于包com.facebook.react.bridge下。

    public class JavaScriptModulesConfig {
    
      private final List<JavaScriptModuleRegistration> mModules;
    
      private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {
        mModules = modules;
      }
    
      /*package*/ List<JavaScriptModuleRegistration> getModuleDefinitions() {
        return mModules;
      }
    
      ...
    }

    JavaScriptModule明显是通过构造函数传入,然后又通过一个getter方法提供出去了,看样子JavaScriptModulesConfig仅仅起到了一个中间者的作用,并非真正的注冊类。

    回看一下之前的ReactInstanceManagerImpl类代码,createReactContext中另一段。例如以下:

    private ReactApplicationContext createReactContext(
          JavaScriptExecutor jsExecutor,
          JSBundleLoader jsBundleLoader)
         ...
    
         JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
         JavaScriptModulesConfig javaScriptModulesConfig;
        try {
          javaScriptModulesConfig = jsModulesBuilder.build();
        } finally {
          ...
        }
         ...
        CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
            .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
            .setJSExecutor(jsExecutor)
            .setRegistry(nativeModuleRegistry)
            .setJSModulesConfig(javaScriptModulesConfig)
            .setJSBundleLoader(jsBundleLoader)
            .setNativeModuleCallExceptionHandler(exceptionHandler);
    
        ...
    
        CatalystInstance catalystInstance;
        try {
          catalystInstance = catalystInstanceBuilder.build();
        } finally {
          ...
        }
    
        ...
    }

    看来终于javaScriptModulesConfig是用来构建CatalystInstance的,正如凝视所讲。果然没有骗我。

    CatalystInstance仅仅是一个接口。实现类是CatalystInstanceImpl。相同位于包com.facebook.react.bridge下。Catalyst单词的中文意思是催化剂,化学中是用来促进化学物之间的反应,难道说CatalystInstance是用来催化NativeJavascript之间的反应?让我们来瞧一瞧真面目吧。

    public class CatalystInstanceImpl implements CatalystInstance {
    
       ...
    
         private CatalystInstanceImpl(
          final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
          final JavaScriptExecutor jsExecutor,
          final NativeModuleRegistry registry,
          final JavaScriptModulesConfig jsModulesConfig,
          final JSBundleLoader jsBundleLoader,
          NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    
        ...
    
        mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig);
    
        ...
    
        try {
          mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue(
              new Callable<ReactBridge>() {
                @Override
                public ReactBridge call() throws Exception {
                  ...
                  try {
                    return initializeBridge(jsExecutor, jsModulesConfig);
                  } finally {
                      ...
                  }
                }
              }).get();
        } catch (Exception t) {
          throw new RuntimeException("Failed to initialize bridge", t);
        }
      }
    
      private ReactBridge initializeBridge(
          JavaScriptExecutor jsExecutor,
          JavaScriptModulesConfig jsModulesConfig) {
        ...
        ReactBridge bridge;
        try {
          bridge = new ReactBridge(
              jsExecutor,
              new NativeModulesReactCallback(),
              mReactQueueConfiguration.getNativeModulesQueueThread());
        } finally {
           ...
        }
        ...
        try {
          bridge.setGlobalVariable(
              "__fbBatchedBridgeConfig",
              buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
          bridge.setGlobalVariable(
              "__RCTProfileIsProfiling",
              Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ?

    "true" : "false"); } finally { ... } return bridge; } ... }

    CatalystInstanceImpl构造方法里,jsModulesConfig又被用来初始化JavaScriptModuleRegistry,字面意思是JavaScriptModule注冊表。看样子终于找到注冊类了。

    先不着急。继续往下看CatalystInstanceImpl中还初始化了ReactBridge 。字面意思就是真正连接NativeJavascript的桥梁了。ReactBridge干了什么呢?调用了setGlobalVariable方法,參数里面的buildModulesConfigJSONProperty方法又用到了JavaScriptModulesConfig,顺便来看看。

      private String buildModulesConfigJSONProperty(
          NativeModuleRegistry nativeModuleRegistry,
          JavaScriptModulesConfig jsModulesConfig) {
        JsonFactory jsonFactory = new JsonFactory();
        StringWriter writer = new StringWriter();
        try {
          JsonGenerator jg = jsonFactory.createGenerator(writer);
          jg.writeStartObject();
          jg.writeFieldName("remoteModuleConfig");
          nativeModuleRegistry.writeModuleDescriptions(jg);
          jg.writeFieldName("localModulesConfig");
          jsModulesConfig.writeModuleDescriptions(jg);
          jg.writeEndObject();
          jg.close();
        } catch (IOException ioe) {
          throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
        }
        return writer.getBuffer().toString();
      }

    这种方法终于的目的是生成一个JSON字符串,而字符串由什么构成呢?nativeModulejsModulesnativeModule先无论,jsModulesConfig调用了writeModuleDescriptions

    回头看看刚才讲的JavaScriptModulesConfig这个中间类。

    public class JavaScriptModulesConfig {
    
       ...
    
         void writeModuleDescriptions(JsonGenerator jg) throws IOException {
        jg.writeStartObject();
        for (JavaScriptModuleRegistration registration : mModules) {
          jg.writeObjectFieldStart(registration.getName());
          appendJSModuleToJSONObject(jg, registration);
          jg.writeEndObject();
        }
        jg.writeEndObject();
      }
    
      private void appendJSModuleToJSONObject(
          JsonGenerator jg,
          JavaScriptModuleRegistration registration) throws IOException {
        jg.writeObjectField("moduleID", registration.getModuleId());
        jg.writeObjectFieldStart("methods");
        for (Method method : registration.getMethods()) {
          jg.writeObjectFieldStart(method.getName());
          jg.writeObjectField("methodID", registration.getMethodId(method));
          jg.writeEndObject();
        }
        jg.writeEndObject();
      }
    
       ...
    }

    writeModuleDescriptions这种方法干了什么事呢?遍历全部JavaScriptModulepublic方法,然后通过methodID标识作为key存入JSON生成器中,用来终于生成JSON字符串。

    这里略微梳理一下。从initializeBridge->setGlobalVariable->buildModulesConfigJSONProperty->writeModuleDescriptions。整个过程作用是将全部JavaScriptModule的信息生成JSON字符串预先保存到Bridge中。至于为什么这么做。先挖个坑,研究到后面自然就明确了。

    2.3 JavaScriptModule组件的调用

    继续之前说到的NativeModuleRegistry注冊表类。位于包com.facebook.react.bridge中。

    /*package*/ class JavaScriptModuleRegistry {
    
      private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
    
      public JavaScriptModuleRegistry(CatalystInstanceImpl instance, JavaScriptModulesConfig config) {
        mModuleInstances = new HashMap<>();
        for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
          Class<? extends JavaScriptModule> moduleInterface = registration.getModuleInterface();
          JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
              moduleInterface.getClassLoader(),
              new Class[]{moduleInterface},
              new JavaScriptModuleInvocationHandler(instance, registration));
    
          mModuleInstances.put(moduleInterface, interfaceProxy);
        }
      }
    
      ...
    }

    当每次看到这段代码的时候,都有一种惊艳的感觉。前面说过JavaScriptModule组件都是接口定义。在Java端是没有实现类的,被注冊的都是Class类。没有真正的实例,Java端又怎样来调用呢?答案是:动态代理

    这里使用动态代理除了创建JavaScriptModule组件的实例化类外。另一个关键的数据,即JavaScriptModule全部的方法调用都会被invoke拦截,这样就能够统一处理全部从Java端向Javascript端的通信请求。

    JavaScriptModuleInvocationHandlerJavaScriptModuleRegistry的一个内部类,动态代理的拦截类。

      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;
        }
      }

    JavaScriptModule方法拦截invoke里调用了CatalystInstancecallFunction方法,主要传入了ModuleIdMethodIdArguments这三个重要參数(tracingName忽略)。

    public class CatalystInstanceImpl implements CatalystInstance {
    
       ...
    
        private final ReactBridge mBridge;
    
        void callFunction(
          final int moduleId,
          final int methodId,
          final NativeArray arguments,
          final String tracingName) {
    
        ...
    
        mReactQueueConfiguration.getJSQueueThread().runOnQueue(
            new Runnable() {
              @Override
              public void run() {
    
                ...
    
                try {  
                 Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId,arguments);
                } finally {
                   ...
                }
              }
            });
      }
    
       ...
    
    }

    分析这里终于豁然开朗了,原来全部Java层向Javascript层的通信请求都是走的ReactBridge.callFunction

    又有了一个问题,Javascript层详细怎么知道Java层的调用信息呢?

    还是之前举的餐馆吃饭的样例,顾客(Java)把菜名告诉餐馆(Bridge),餐馆再通知厨师(Javascript)。厨师自然就知道该做什么菜了。当然前提是要约定一个菜单了,菜单包括全部的菜名。

    还记得之前挖的一个坑吗?就是CatalystInstanceImpl中初始化ReactBridge的时候,全部JavaScriptModule信息都被以moduleID+methodID形式生成的JSON字符串预先存入了ReactBridge中,这事实上就是一个菜单索引表了。餐馆(Bridge)知道了菜名(moduleID+methodID)就能告诉厨师(Javascript)顾客(Java)想吃什么了,当然有时还少不了不放辣这样的需求了(arguments)。

    所以callFunction中有了moduleId + methodId + arguments,就能够调用到Javascript中的实现了。


    3、Bridge层实现

    通信模型图中要调用WebKit的实现,少不了Bridge这个桥梁。因为Java是不能直接调用WebKit,可是假设Java通过JNIJNI再调用WebKit不就OK了么?

    继续前面说的ReactBridgesetGlobalVariablecallFunction方法。

    public class ReactBridge extends Countable {
      static {
        SoLoader.loadLibrary(REACT_NATIVE_LIB);
      }
    
       public native void callFunction(int moduleId, int methodId, NativeArray arguments);
    
      public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
    }

    果然是JNI调用,而JNI层的入口是react/jni/OnLoad.cpp,和常规的javah规则不同,它是通过RegisterNatives方式注冊的,JNI_OnLoad里面注冊了setGlobalVariablecallFunctionnative本地方法。

    废话不多说。来看看c++setGlobalVariablecallFunction的实现吧。

    namespace bridge {
    
        static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstring jsonValue) {
        auto bridge = extractRefPtr<CountableBridge>(env, obj);
        bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue));
      }
    
       static void callFunction(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint moduleId, jint methodId,
                             NativeArray::jhybridobject args, jstring tracingName) {
       auto bridge = extractRefPtr<CountableBridge>(env, obj);
       auto arguments = cthis(wrap_alias(args));
       try {
          bridge->callFunction(
          cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
          folly::to<std::string>(moduleId),
          folly::to<std::string>(methodId),
          std::move(arguments->array),
          fromJString(env, tracingName)
        );
       } catch (...) {
         translatePendingCppExceptionToJavaException();
        }
      }
    
    }
    
    struct CountableBridge : Bridge, Countable {
      using Bridge::Bridge;
    };

    OnLoad仅仅是一个调用入口。终于走的还是CountableBridge,而CountableBridge继承的是Bridge。仅仅是加了一个计数功能。实现代码在react/Bridge.cpp中。

    void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
      runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
        executor->setGlobalVariable(propName, jsonValue);
      });
    }
    void Bridge::callFunction(
        ExecutorToken executorToken,
        const std::string& moduleId,
        const std::string& methodId,
        const folly::dynamic& arguments,
        const std::string& tracingName) {
      #ifdef WITH_FBSYSTRACE
      int systraceCookie = m_systraceCookie++;
      ...
      #endif
    
      #ifdef WITH_FBSYSTRACE
      runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) {
      ...
      #else
      runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName] (JSExecutor* executor) {
      #endif
        executor->callFunction(moduleId, methodId, arguments);
      });
    }

    两个方法调用的过程几乎相同,都是塞进runOnExecutorQueue运行队列里面等待调用,回调都是走的JSExecutor。所以还是要看JSExecutor了。

    这边提一下,Bridge类构造的时候会初始化ExecutorQueue,通过JSCExecutorFactory创建JSExecutor,而JSExecutor的真正实现类是JSCExecutor

    通过jni/react/JSCExecutor.h头文件能够验证这一点,此处略过不细讲。

    绕来绕去,略微有点晕。最后又跑到JSCExecutor.cpp里面了。

    void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
      auto globalObject = JSContextGetGlobalObject(m_context);
      String jsPropertyName(propName.c_str());
    
      String jsValueJSON(jsonValue.c_str());
      auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);
    
      JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
    }

    finally哈。前面Java层构造的JavaScriptModule信息JSON串,终于在这里被处理了,不用想也知道肯定是解析后存为一张映射表,然后等callFunction的时候映射调用。接下来看callFunction的处理。

    void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
      // TODO:  Make this a first class function instead of evaling. #9317773
      std::vector<folly::dynamic> call{
        moduleId,
        methodId,
        std::move(arguments),
      };
      std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
      m_bridge->callNativeModules(*this, calls, true);
    }
    static std::string executeJSCallWithJSC(
        JSGlobalContextRef ctx,
        const std::string& methodName,
        const std::vector<folly::dynamic>& arguments) {
    
      ...
    
      // Evaluate script with JSC
      folly::dynamic jsonArgs(arguments.begin(), arguments.end());
      auto js = folly::to<folly::fbstring>(
          "__fbBatchedBridge.", methodName, ".apply(null, ",
          folly::toJson(jsonArgs), ")");
      auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
      return Value(ctx, result).toJSONString();
    }

    callFunction里面运行的是executeJSCallWithJSC。而executeJSCallWithJSC里面将methodNamejsonArgs拼接成了一个applyJavascript运行语句。最后调用jni/react/JSCHelpers.cppevaluateScript的来运行这个语句,完毕BridgeJavascript的调用。(JSCHelpersWebKit的一些API做了封装,暂不深究,仅仅要知道它负责终于调用WebKit即可了)

    当然JSCExecutor::callFunction方法最后另一个Bridge.cpp类的callNativeModules反向通信,意图是将Javascript语句运行结果通知回Native,这个过程留在以后的文章中慢慢研究,先行略过。

    最后,总结一下Bridge层的调用过程: OnLoad.cpp->Bridge.cpp->JSCExecutor.cpp->JSCHelpers.cpp->WebKit


    4、Javascript层实现

    Javascript的通信,实质上是Weikit运行Javascript语句,调用流程是Bridge->WebKit->JavascriptWebKit中提供了很多与Javascript通信的API。比方evaluateScriptJSContextGetGlobalObjectJSObjectSetProperty等。

    4.1、JavaScriptModule映射表

    前面说过,全部JavaScriptModule信息是调用的setGlobalVariable方法生成一张映射表,这张映射表终于肯定是要保存在Javascript层的,回头细致分析下jni/react/JSCExecutor.cpp的代码。

    void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
      auto globalObject = JSContextGetGlobalObject(m_context);
      String jsPropertyName(propName.c_str());
    
      String jsValueJSON(jsonValue.c_str());
      auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);
    
      JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
    }

    JSContextGetGlobalObjectWeiKit的方法。其目的是获取Global全局对象。jsPropertyName方法字面意思就是Javascript对象的属性名,參数propName是从Java层传递过来的,在CatalystInstanceImpl.java类中能够印证这一点,详细值有两个:__fbBatchedBridgeConfig__RCTProfileIsProfiling

    bridge.setGlobalVariable(
              "__fbBatchedBridgeConfig",
              buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
          bridge.setGlobalVariable(
              "__RCTProfileIsProfiling",
              Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ?

    "true" : "false");

    我们所关注的是__fbBatchedBridgeConfig。这个值被传递到刚刚说的JSCExecutor::setGlobalVariable生成jsPropertyName对象,而jsonValue相同被JSValueMakeFromJSONString处理成一个jsValue对象,这样一来property-value就全都有了。最后JSObjectSetProperty方法,顾名思义,就是设置属性,使用的是Global全局对象,假设翻译成Javascript代码,大概应该是这样:

    global.__fbBatchedBridgeConfig = jsonValue;

    或者

    Object.defineProperty(global, '__fbBatchedBridgeConfig', { value: jsonValue});

    作用事实上是一样的。

    既然javascript接收到了关于JavaScriptModule的信息,那就要生成一张映射表了。

    我们来看node_modules eact-nativeLibrariesBatchedBridgeBatchedBridge.js的代码。

    const MessageQueue = require('MessageQueue');
    
    const BatchedBridge = new MessageQueue(
      __fbBatchedBridgeConfig.remoteModuleConfig,
      __fbBatchedBridgeConfig.localModulesConfig,
    );

    因为__fbBatchedBridgeConfig对象是被直接定义成Global全局对象的属性,就能够直接调用了,相似于window对象。__fbBatchedBridgeConfig对象里又有两个属性:remoteModuleConfiglocalModulesConfig

    哪儿冒出来的呢?

    有心的读者能猜到这两个属性都是定义在jsonValue里面的,为了验证这一点。我们再回头搜索下生成JSON串的地方,代码在CatalystInstanceImpl.java里面。

      private String buildModulesConfigJSONProperty(
          NativeModuleRegistry nativeModuleRegistry,
          JavaScriptModulesConfig jsModulesConfig) {
        JsonFactory jsonFactory = new JsonFactory();
        StringWriter writer = new StringWriter();
        try {
          JsonGenerator jg = jsonFactory.createGenerator(writer);
          jg.writeStartObject();
          jg.writeFieldName("remoteModuleConfig");
          nativeModuleRegistry.writeModuleDescriptions(jg);
          jg.writeFieldName("localModulesConfig");
          jsModulesConfig.writeModuleDescriptions(jg);
          jg.writeEndObject();
          jg.close();
        } catch (IOException ioe) {
          throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
        }
        return writer.getBuffer().toString();
      }

    这段代码分析过,localModulesConfig里面存的就是JavaScriptModule的信息,果然没错!

    再来看刚刚的BatchedBridge.js

    const MessageQueue = require('MessageQueue');
    
    const BatchedBridge = new MessageQueue(
      __fbBatchedBridgeConfig.remoteModuleConfig,
      __fbBatchedBridgeConfig.localModulesConfig,
    );

    JavaScriptModule的信息。又被传入MessageQueue的构造函数里面了,继续往MessageQueue里面看,代码在node_modules eact-nativeLibrariesUtilitiesMessageQueue.js

    class MessageQueue {
    
      constructor(remoteModules, localModules) {
        ...
    
        localModules && this._genLookupTables(
          this._genModulesConfig(localModules),this._moduleTable, this._methodTable
        );
    }

    localModules參数就是JavaScriptModule信息了,又被传进了_genLookupTables的方法里,同一时候还有两个參数_moduleTable_methodTable。推測一下,应该就是我们找的映射表了。一张module映射表,一张method映射表。

    _genLookupTables(modulesConfig, moduleTable, methodTable) {
        modulesConfig.forEach((config, moduleID) => {
          this._genLookup(config, moduleID, moduleTable, methodTable);
        });
      }
    
      _genLookup(config, moduleID, moduleTable, methodTable) {
        if (!config) {
          return;
        }
    
        let moduleName, methods;
        if (moduleHasConstants(config)) {
          [moduleName, , methods] = config;
        } else {
          [moduleName, methods] = config;
        }
    
        moduleTable[moduleID] = moduleName;
        methodTable[moduleID] = Object.assign({}, methods);
      }
    

    哈哈,和推測的一样,生成了两张映射表,存放在了MessageQueue类里面。

    4.2、callFunction的调用

    回想一下JSCExecutor.cpp中的终于callFunction调用过程。

    void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
      // TODO:  Make this a first class function instead of evaling. #9317773
      std::vector<folly::dynamic> call{
        moduleId,
        methodId,
        std::move(arguments),
      };
      std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
      m_bridge->callNativeModules(*this, calls, true);
    }
    static std::string executeJSCallWithJSC(
        JSGlobalContextRef ctx,
        const std::string& methodName,
        const std::vector<folly::dynamic>& arguments) {
    
      ...
    
      // Evaluate script with JSC
      folly::dynamic jsonArgs(arguments.begin(), arguments.end());
      auto js = folly::to<folly::fbstring>(
          "__fbBatchedBridge.", methodName, ".apply(null, ",
          folly::toJson(jsonArgs), ")");
      auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
      return Value(ctx, result).toJSONString();
    }

    executeJSCallWithJSC中有个生成语句的代码,methodName的值为callFunctionReturnFlushedQueue,所以拼装成的Javascript语句是:

    __fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null, jsonArgs);

    首先,在Javascript的运行环境下,当前作用域条件下__fbBatchedBridge能被直接调用。必须是Global全局对象的属性。

    4.1__fbBatchedBridgeConfig不同的是,jni并没有手动设置__fbBatchedBridge为全局对象的属性,那唯一的可能就是在Javascript里面通过Object.defineProperty来设置了。

    搜索一下。在BatchedBridge.js中找到例如以下代码:

    const MessageQueue = require('MessageQueue');
    
    const BatchedBridge = new MessageQueue(
      __fbBatchedBridgeConfig.remoteModuleConfig,
      __fbBatchedBridgeConfig.localModulesConfig,
    );
    
    ...
    
    Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });
    
    module.exports = BatchedBridge;
    

    这段代码等价于

    global.__fbBatchedBridge = new MessageQueue(...args);

    再次替换一下,callFuction调用的是:

    MessageQueue.callFunctionReturnFlushedQueue.apply(null, jsonArgs);

    Arguments參数再详细一下。就变成了:

    MessageQueue.callFunctionReturnFlushedQueue.apply(null, module, method, args);

    又回到MessageQueue.js了,前面才分析到它里面存放了两张映射表,如今第一件事当然是作匹配查找了,看MessageQueue.callFunctionReturnFlushedQueue的详细调用吧。

    callFunctionReturnFlushedQueue(module, method, args) {
        guard(() => {
          this.__callFunction(module, method, args);
          this.__callImmediates();
        });
    
        return this.flushedQueue();
      }
    
    var guard = (fn) => {
      try {
        fn();
      } catch (error) {
        ErrorUtils.reportFatalError(error);
      }
    };

    Lambda+闭包,代码非常简洁,但阅读起来比較吃力。而React里面都是这样的。强烈吐槽一下。

    定义guard目的是为了统一捕获错误异常,忽略这一步,以上代码等价于:

    callFunctionReturnFlushedQueue(module, method, args) {
        this.__callFunction(module, method, args);
        this.__callImmediates();
        return this.flushedQueue();
      }

    this指的是当前MessageQueue 对象。所以找到MessageQueue.__callFunction方法:

    __callFunction(module, method, args) {
        ...
        if (isFinite(module)) {
          method = this._methodTable[module][method];
          module = this._moduleTable[module];
        }
        ...
        var moduleMethods = this._callableModules[module];
        invariant(
          !!moduleMethods,
          'Module %s is not a registered callable module.',
          module
        );
    
        moduleMethods[method].apply(moduleMethods, args);
        ...
      }
    

    这里就是通过moduleIDmethodID来查询两张映射Table了。获取到了详细的moduleNamemethodName,接着肯定要做调用Javascript相应组件了。

    假设在餐馆吃饭的样例中,场景应该是这样的:顾客点完菜。餐馆服务人员也已经把菜名通知到厨师了,厨师该做菜了吧。等等。当中还漏了一步,就是这个厨师会不会做这道菜。假设让川菜师傅去做粤菜肯定是不行的,所以厨师的能力里还应该有一张技能清单,做菜前厨师须要推断下自己的技能单子里面有没有这道菜。

    代码同理。MessageQueue里面有一个_callableModules数组。它就是用来存放哪些Javascript组件是能够被调用的。正常情况下_callableModules的数据和JavaScriptModules的数据(包括方法名和參数)理应是全然相应的。

    我们来瞧瞧_callableModules数据初始化的过程,相同是在MessageQueue.js中:

    registerCallableModule(name, methods) {
        this._callableModules[name] = methods;
    }

    全部的Javascript组件都是通过registerCallableModule来注冊的,比方触摸事件RCTEventEmitter.java相应的组件RCTEventEmitter.js,代码路径是
    node_modules eact-nativeLibrariesBatchedBridgeBatchedBridgedModulesRCTEventEmitter.js

    var BatchedBridge = require('BatchedBridge');
    var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
    
    BatchedBridge.registerCallableModule(
      'RCTEventEmitter',
      ReactNativeEventEmitter
    );
    
    // Completely locally implemented - no native hooks.
    module.exports = ReactNativeEventEmitter;
    

    BatchedBridge能够看成是MessageQueue。被注冊的组件是ReactNativeEventEmitter。代码位于node_modules eact-nativeLibrariesReactNativeReactNativeEventEmitter.js

    receiveEvent: function(tag: number, topLevelType: string, nativeEventParam: Object) {
        ...
    },
    
    receiveTouches: function(eventTopLevelType: string,  touches:Array<Object>, changedIndices: Array<number>) {
       ...
    }
    

    细致对比RCTEventEmitter .java比較,是不是全然一致,哈哈

    public interface RCTEventEmitter extends JavaScriptModule {
    
      public void receiveEvent(int targetTag, String eventName,  WritableMap event);
      public void receiveTouches(String eventName, WritableArray touches, WritableArray changedIndices);
    
    }

    继续__callFunction方法代码的最后一步

    moduleMethods[method].apply(moduleMethods, args)

    假设以RCTEventEmitterreceiveTouches方法调用为例。详细语句应该是这样:

    ReactNativeEventEmitter.receiveTouches.apply(moduleMethods, eventTopLevelType, touches, changedIndices);

    结束!通信完毕。大功告成!

    5、总结

    整个通信过程涉及到三种程序语言:JavaC++Javascript,这还仅仅是单向的通信流程。假设是逆向则更加复杂。因为篇幅的关系。留到以后的博客里面研究。

    最后总结一下几个关键点:

    1、Java层

    JavaScriptModule接口类定义通信方法,在ReactApplicationContext创建的时候存入注冊表类JavaScriptModuleRegistry中。同一时候通过动态代理生成代理实例,并在代理拦截类JavaScriptModuleInvocationHandler中统一处理发向Javascript的全部通信请求。

    CatalystInstanceImpl类内部的ReactBridge详细实现与Javascript的通信请求,它是调用Bridge Jni 的出口。

    ReactBridge被创建的时候会将JavaScriptModule信息表预先发给Javascript层用来生成映射表。

    2、C++层

    OnLoadjni层的调用入口,注冊了全部的native方法。其内部调用又都是通过CountableBridge来完毕的,CountableBridgeBridge的无实现子类。而在Bridge里面JSCExecutor才是真正的运行者。

    JSCExecutor将全部来自Java层的通信请求封装成Javascript运行语句。交给WebKit内核完毕向Javascript层的调用。

    3、Javascript层

    BatchedBridgeJavascript层的调用入口,而其又是MessageQueue的伪装者。MessageQueue预先注冊了全部能够接收通信请求的组件_callableModules 。同一时候也保存着来自JavaJavaScriptModule的两张映射表。

    接收通信请求时,先通过映射表确认详细请求信息,再确认Javascript组件能否够被调用,最后通过apply方式完毕运行。

    整个通信过程流程例如以下图:


    本博客不定期持续更新,欢迎关注和交流:

    http://blog.csdn.net/megatronkings

  • 相关阅读:
    仿网易菜单 实现侧滑 SlidingMenu
    MD5 Util
    Android 关于SD卡、机身内存以及分辨率的转换的工具类
    android TextView 显示图片,类似于聊天窗口。
    WEB显示(隐藏)系统时间
    I/O复习四 字符流 InputStreamReader/OutputStreamWriter
    Knockout应用开发指南(完整版) 目录索引
    C#设计模式(23种设计模式)
    win7+ubuntu 13.04双系统安装方法
    GeoServer地图开发解决方案
  • 原文地址:https://www.cnblogs.com/llguanli/p/8442598.html
Copyright © 2020-2023  润新知