360SDN.COM

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

来源:  2017-06-16 22:11:33    评论:0点击:

来源:http://blog.csdn.net/megatronkings/article/details/51114278

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的解释器了,这个从安卓工程lib目录下面的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

阅读一下注释,主要有三点信息:
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
  • 2
  • 3
  • 4
  • 5

先前注释中第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 jsModuleClass : reactPackage.createJSModules()){
      jsModulesBuilder.add(jsModuleClass);
    }
  }
  ...

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

可以看到CoreModulesPackage和开发者扩展自定义的mPackages都是通过processPackage方法里添加到JavaScriptModulesConfig里注册的。

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

public class JavaScriptModulesConfig {

  private final List mModules;

  private JavaScriptModulesConfig(List modules) {
    mModules = modules;
  }

  /*package*/ List getModuleDefinitions() {
    return mModules;
  }

  ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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() {
            @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;
  }


   ...
}
  • 12
  • 3
  • 4
  • 5
  • 6
  • 7
  • 9
  • 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

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();
  }
  • 1
  • 2
  • 3
  • 4
  • 5

这个方法最终的目的是生成一个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();
  }

   ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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<Classextends JavaScriptModule>, JavaScriptModule> mModuleInstances;

  public JavaScriptModuleRegistry(CatalystInstanceImpl instance, JavaScriptModulesConfig config) {
    mModuleInstances = new HashMap<>();
    for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
      Classextends JavaScriptModule> moduleInterface = registration.getModuleInterface();
      JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
          moduleInterface.getClassLoader(),
          new Class[]{moduleInterface},
          new JavaScriptModuleInvocationHandler(instance, registration));

      mModuleInstances.put(moduleInterface, interfaceProxy);
    }
  }

  ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

当每次看到这段代码的时候,都有一种惊艳的感觉。前面说过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;
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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 {
               ...
            }
          }
        });
  }

   ...

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

分析这里终于豁然开朗了,原来所有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);
}
  • 1
  • 2
  • 3

果然是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(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(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();
    }
  }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
struct CountableBridge : Bridge, Countable {
  using Bridge::Bridge;
};
  • 1
  • 2

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);
  });
}
  • 1
  • 2
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);
  });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

两个方法调用的过程差不多,都是塞进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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

    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 call{
        moduleId,
        methodId,
        std::move(arguments),
      };
      std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
      m_bridge->callNativeModules(*this, calls, true);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    static std::string executeJSCallWithJSC(
        JSGlobalContextRef ctx,
        const std::string& methodName,
        const std::vector& arguments) {
    
      ...
    
      // Evaluate script with JSC
      folly::dynamic jsonArgs(arguments.begin(), arguments.end());
      auto js = folly::to(
          "__fbBatchedBridge.", methodName, ".apply(null, ",
          folly::toJson(jsonArgs), ")");
      auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
      return Value(ctx, result).toJSONString();
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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");
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    global.__fbBatchedBridgeConfig = jsonValue;
    • 1
    • 1

    或者

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

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

    我们来看node_modules\react-native\Libraries\BatchedBridge\BatchedBridge.js的代码。

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

    由于__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();
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    再来看刚刚的BatchedBridge.js

    const MessageQueue = require('MessageQueue');
    
    const BatchedBridge = new MessageQueue(
      __fbBatchedBridgeConfig.remoteModuleConfig,
      __fbBatchedBridgeConfig.localModulesConfig,
    );
    • 1
    • 2
    • 3
    • 4
    • 5

    JavaScriptModule的信息,又被传入MessageQueue的构造函数里面了,继续往MessageQueue里面看,代码在node_modules\react-native\Libraries\Utilities\MessageQueue.js

    class MessageQueue {
    
      constructor(remoteModules, localModules) {
        ...
    
        localModules && this._genLookupTables(
          this._genModulesConfig(localModules),this._moduleTable, this._methodTable
        );
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    哈哈,和猜测的一样,生成了两张映射表,存放在了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 call{
        moduleId,
        methodId,
        std::move(arguments),
      };
      std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
      m_bridge->callNativeModules(*this, calls, true);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    static std::string executeJSCallWithJSC(
        JSGlobalContextRef ctx,
        const std::string& methodName,
        const std::vector& arguments) {
    
      ...
    
      // Evaluate script with JSC
      folly::dynamic jsonArgs(arguments.begin(), arguments.end());
      auto js = folly::to(
          "__fbBatchedBridge.", methodName, ".apply(null, ",
          folly::toJson(jsonArgs), ")");
      auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
      return Value(ctx, result).toJSONString();
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这段代码等价于

    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);
      }
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Lambda+闭包,代码很简洁,但阅读起来比较吃力,而React里面都是这种,强烈吐槽一下。

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

    callFunctionReturnFlushedQueue(module, method, args) {
        this.__callFunction(module, method, args);
        this.__callImmediates();
        return this.flushedQueue();
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    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);
        ...
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里就是通过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\react-native\Libraries\BatchedBridge\BatchedBridgedModules\RCTEventEmitter.js

    var BatchedBridge = require('BatchedBridge');
    var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
    
    BatchedBridge.registerCallableModule(
      'RCTEventEmitter',
      ReactNativeEventEmitter
    );
    
    // Completely locally implemented - no native hooks.
    module.exports = ReactNativeEventEmitter;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    BatchedBridge可以看成是MessageQueue,被注册的组件是ReactNativeEventEmitter,代码位于node_modules\react-native\Libraries\ReactNative\ReactNativeEventEmitter.js

    receiveEvent: function(tag: number, topLevelType: string, nativeEventParam: Object) {
        ...
    },
    
    receiveTouches: function(eventTopLevelType: string,  touches:Array, changedIndices: Array) {
       ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    仔细对照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);
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5

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

    moduleMethods[method].apply(moduleMethods, args)
    • 1

    如果以RCTEventEmitterreceiveTouches方法调用为例,具体语句应该是这样:

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

    结束!通信完成,大功告成!

    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

    为您推荐

    友情链接 |九搜汽车网 |手机ok生活信息网|ok生活信息网|ok微生活
     Powered by www.360SDN.COM   京ICP备11022651号-4 © 2012-2016 版权