电视公布及新闻循环代码剖析,组件开首化与通信分析

动用 JS 打造跨平台的原生应用:React Native iOS 通讯机制初探

2015/12/30 · JavaScript
· React Native

原稿出处: Taobao前端团队(FED)-
乾秋   

亚洲必赢官网 1

在初识 React Native 时,非凡令人困惑的一个地点就是 JS 和 Native
八个端之间是怎么互相通讯的。本篇小说对 iOS 端 React Native
启动时的调用流程做下简要总计,以此窥探其幕后的通讯机制。

肥皂V 2016 1.3
http://www.jianshu.com/p/269b21958030

小伙伴们都晓得在Android开发中贯彻Java和JS的通讯可以经过WebView来完毕,包涵注册JSBridge或者在接口中阻止都可以。可是React
Native中并不曾用WebView控件的章程,而是按照WebKit内核的措施来落到实处Java与JS的通信,也就是Java与JS之间的通信都是透过中间层C++来完结的,今天大家来分析下Android中React
Native怎么落实Java和JS之间的通讯。

该种类文章希望从非 UI 模块以及 UI 模块的开端化与通讯来分析 React
Native 一些兑现原理。那篇作品是该系列的率先篇,主要分析了非 UI 模块从
APP 启动、Native 端的模块定义到 JavaScript 端的模块生成进度(基于
React Native @0.29.0 iOS 完毕)。

JS 启动进程

React Native 的 iOS 端代码是直接从 Xcode IDE
里启动的。在启动时,首先要对代码举行编译,不出意外,在编译后会弹出一个指令行窗口,这么些窗口就是经过
Node.js 启动的 development server

题材是这一个命令行是怎么启动起来的呢?实际上,Xcode 在 Build Phase
的结尾一个阶段对此做了配备:
亚洲必赢官网 2

据此,代码编译后,就会履行 packager/react-native-xcode.sh 这几个剧本。
查阅这几个本子中的内容,发现它根本是读取 XCode 带过来的环境变量,同时加载
nvm 包使得 Node.js 环境可用,最后执行 react-native-cli 的命令:

react-native bundle \ –entry-file index.ios.js \ –platform ios \
–dev $DEV \ –bundle-output “$DEST/main.jsbundle” \ –assets-dest
“$DEST”

1
2
3
4
5
6
react-native bundle \
  –entry-file index.ios.js \
  –platform ios \
  –dev $DEV \
  –bundle-output "$DEST/main.jsbundle" \
  –assets-dest "$DEST"

react-native 命令是大局安装的,在自身本机上它的地方是
/usr/local/bin/react-native。查看该文件,它调用了 react-native
包里的local-cli/cli.js 中的 run 方法,最后进入了
private-cli/src/bundle/buildBundle.js。它的调用过程为:

  1. ReactPackager.createClientFor
  2. client.buildBundle
  3. processBundle
  4. saveBundleAndMap

下边四步成功的是 buildBundle
的出力,细节很多很复杂。总体来说,buildBundle 的效果类似于 browerify 或
webpack :

  1. 从进口文件开端分析模块之间的依赖性关系;
  2. 对 JS 文件转载,比如 JSX 语法的中转等;
  3. 把转化后的次第模块一起联合为一个 bundle.js

据此 React Native 单独去贯彻那些包裹的长河,而不是直接采取 webpack
,是因为它对模块的解析和编译做了诸多优化,大大升高了包装的快慢,那样可以确保在
liveReload 时用户及时获取响应。

Tips: 通过走访
能够见到内存中缓存的装有编译后的公文名及文件内容,如:
亚洲必赢官网 3

React Native
已经推出近一年时间了,近年来也在探究iOS下用js写app的框架,从徘徊和犹疑中,最终仍然选定React
Native,她如同若隐若现的女神一样,想要下决心追到,可是不易于。要想把他采用的已存在的有早晚体量的app中,更是毋庸置疑,让我先把他里外都打听通晓,在享受一下靠边施用到现有app的方案,那是深刻React
Native种类的第一篇,后续会三番五次享受应用进度中的一些认识。

React
Native中调用所有的行为都是从Native端发起的,用户操作直接面向的也是Native。所以那个通讯模型又有什么不可视作是Native提倡对话,然后Javascript进行应对。

时序图总览

先来看下全部的时序图,前面会挨个介绍。

亚洲必赢官网 4

image

Native 启动进度

Native 端就是一个 iOS 程序,程序入口是 main
函数,像一般一样,它负责对应用程序做起头化。

除此之外 main 函数之外,AppDelegate
也是一个相比根本的类,它主要用来做一些大局的支配。在应用程序启动之后,其中的
didFinishLaunchingWithOptions
方法会被调用,在这几个主意中,主要做了几件事:

  • 概念了 JS 代码所在的职位,它在 dev 环境下是一个 URL,通过
    development server
    访问;在生产环境下则从磁盘读取,当然前提是早就手动生成过了 bundle
    文件;
  • 创制了一个 RCTRootView 对象,该类继承于 UIView,处于程序有所
    View 的最外层;
  • 调用 RCTRootView 的 initWithBundleURL 方法。在该格局中,创设了
    bridge 对象。顾名思义,bridge
    起着三个端之间的桥接功用,其中确进行事的是类就是盛名的
    RCTBatchedBridge

RCTBatchedBridge 是初叶化时通讯的骨干,大家第一关切的是 start 方法。在
start 方法中,会创立一个 GCD
线程,该线程通过串行队列调度了以下多少个根本的任务。

先是篇详细分析下React Native 中
Native和JS的相互调用的法则分析。从前bang的篇章已经介绍过,本文从代码层面更深入的来讲学,
剖析基于 React Native 0.17.0 版本,
RN在高速发展,其中的情节已和事先的旧版本多少分裂

Java要能调用到JS需求在Java层注册JS模块,先来看下JS模块的挂号进程。

[Native] iOS main 函数执行

第一,iOS APP 启动后先进行的是 main 函数,此处不多说。

loadSource

该任务担当加载 JS 代码到内存中。和眼前一致,即使 JS 地址是 URL
的样式,就通过互联网去读取,若是是文本的格局,则经过读本地磁盘文件的法门读取。

用作初篇,先成立一个示范工程,以后的享用皆以那么些工程为底蕴。近年来那个工程还很粗略,main.js的任课可以下载那里的代码

1.JavaScriptModule模块注册(Java层)

系统登记了一些常用的JS模块,在CoreModulesPackage的createJSModules中注册了部分系统焦点的JS模块,比如AppRegistry(RN组件注册模块)/RCT伊芙ntEmitter(事件发射模块)等。

class CoreModulesPackage implements ReactPackage{
  @Override
  public List<NativeModule> createNativeModules(
      ReactApplicationContext catalystApplicationContext) {
    ......
    return Arrays.<NativeModule>asList(
        new AnimationsDebugModule(
            catalystApplicationContext,
            mReactInstanceManager.getDevSupportManager().getDevSettings()),
        new AndroidInfoModule(),
        new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler),
        new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()),
        new Timing(catalystApplicationContext),
        new SourceCodeModule(
            mReactInstanceManager.getSourceUrl(),
            mReactInstanceManager.getDevSupportManager().getSourceMapUrl()),
        uiManagerModule,
        new DebugComponentOwnershipModule(catalystApplicationContext));
  }

  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    return Arrays.asList(
        DeviceEventManagerModule.RCTDeviceEventEmitter.class,
        JSTimersExecution.class,
        RCTEventEmitter.class,
        RCTNativeAppEventEmitter.class,
        AppRegistry.class,
        com.facebook.react.bridge.Systrace.class,
        DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
  }
}

public interface ReactPackage {

  /**
   * @param reactContext react application context that can be used to create modules
   * @return list of native modules to register with the newly created catalyst instance
   */
  List<NativeModule> createNativeModules(ReactApplicationContext reactContext);

  /**
   * @return list of JS modules to register with the newly created catalyst instance.
   *
   * IMPORTANT: Note that only modules that needs to be accessible from the native code should be
   * listed here. Also listing a native module here doesn't imply that the JS implementation of it
   * will be automatically included in the JS bundle.
   */
  List<Class<? extends JavaScriptModule>> createJSModules();

  /**
   * @return a list of view managers that should be registered with {@link UIManagerModule}
   */
  List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}

以RCT伊夫ntEmitter这一个JavaScriptModule模块为例来看下JS模块在Java层的已毕。

具备JS层组件已毕JavaScriptModule接口,比如RCT伊夫ntEmitter:

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

可以看出来JS模块在Java层只是不难的接轨JavaScriptModule,并不曾现实的兑现,那么难题就来了,光是一个接口是迫不得已调用的,好像到此处就跟踪不下去了?其实可以换种思路,JS模块肯定是要在Native中注册的,大家先到ReactInstanceManagerImpl中的createReactContext方法中去看下怎么注册的。

所有的NativeModule和JSModule在ReactInstanceManagerImpl中注册

方法的代码比较多,然则逻辑不复杂,关键的多少个步骤我都在代码中一向做了诠释,就不多说了,那里最紧要分析JS模块的登记,所以我们来看JavaScriptModulesConfig中。

private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    FLog.i(ReactConstants.TAG, "Creating react context.");
  //Native模块
    NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
  //JS模块
    JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();

  //React Context
    ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    if (mUseDeveloperSupport) {
      reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
    }

  //处理CoreModules---包含Native模块和JS模块
    Systrace.beginSection(
        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
        "createAndProcessCoreModulesPackage");
    try {
      CoreModulesPackage coreModulesPackage =
          new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
      processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    // 处理用户注册的模块
    for (ReactPackage reactPackage : mPackages) {
      Systrace.beginSection(
          Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
          "createAndProcessCustomReactPackage");
      try {
        processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
      } finally {
        Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    }

  //buildNativeModuleRegistry
    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
    NativeModuleRegistry nativeModuleRegistry;
    try {
       nativeModuleRegistry = nativeRegistryBuilder.build();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

  //buildJSModuleConfig
    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildJSModuleConfig");
    JavaScriptModulesConfig javaScriptModulesConfig;
    try {
      javaScriptModulesConfig = jsModulesBuilder.build();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

  ...
    //createCatalystInstance---Java/JS/C++三方通信总管
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new      CatalystInstanceImpl.Builder()
        .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        .setJSModulesConfig(javaScriptModulesConfig)
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);

    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
    CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    if (mBridgeIdleDebugListener != null) {
      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
    }

    reactContext.initializeWithInstance(catalystInstance);

  //runJSBundle
    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
    try {
      catalystInstance.runJSBundle();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    ReactMarker.logMarker("CREATE_REACT_CONTEXT_END");
    return reactContext;
  }

观望JavaScriptModulesConfig,首即使对每个JavaScriptModule构造JavaScriptModuleRegistration,存放在mModules中

/**
 * Class stores configuration of javascript modules that can be used across the bridge
 */
public class JavaScriptModulesConfig {

  private final List<JavaScriptModuleRegistration> mModules;

  private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {
    mModules = modules;
  }

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

  /*package*/ 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();
  }

  public static class Builder {

    private int mLastJSModuleId = 0;
    private List<JavaScriptModuleRegistration> mModules =
        new ArrayList<JavaScriptModuleRegistration>();

    public Builder add(Class<? extends JavaScriptModule> moduleInterfaceClass) {
      int moduleId = mLastJSModuleId++;
      mModules.add(new JavaScriptModuleRegistration(moduleId, moduleInterfaceClass));
      return this;
    }

    public JavaScriptModulesConfig build() {
      return new JavaScriptModulesConfig(mModules);
    }
  }
}

/**
 * Registration info for a {@link JavaScriptModule}. 
    Maps its methods to method ids.
 */
class JavaScriptModuleRegistration {

  private final int mModuleId;
  private final Class<? extends JavaScriptModule> mModuleInterface;
  private final Map<Method, Integer> mMethodsToIds;
  private final Map<Method, String> mMethodsToTracingNames;

  JavaScriptModuleRegistration(int moduleId, Class<? extends JavaScriptModule> moduleInterface) {
    mModuleId = moduleId;
    mModuleInterface = moduleInterface;

    mMethodsToIds = MapBuilder.newHashMap();
    mMethodsToTracingNames = MapBuilder.newHashMap();
    final Method[] declaredMethods = mModuleInterface.getDeclaredMethods();
    Arrays.sort(declaredMethods, new Comparator<Method>() {
      @Override
      public int compare(Method lhs, Method rhs) {
        return lhs.getName().compareTo(rhs.getName());
      }
    });

    // Methods are sorted by name so we can dupe check and have obvious ordering
    String previousName = null;
    for (int i = 0; i < declaredMethods.length; i++) {
      Method method = declaredMethods[i];
      String name = method.getName();
      Assertions.assertCondition(
          !name.equals(previousName),
          "Method overloading is unsupported: " + mModuleInterface.getName() + "#" + name);
      previousName = name;

      mMethodsToIds.put(method, i);
      mMethodsToTracingNames.put(method, "JSCall__" + getName() + "_" + method.getName());
    }
  }
  ......
}

到那边就有了独具的JavaScriptModules,并且都围观存放在JavaScriptModulesConfig中,那么RN框架是怎么接纳的?再回首前边ReactInstanceManagerImpl,将JavaScriptModulesConfig用来实例化catalystInstance,那么些是三方通讯的中转站。

 //createCatalystInstance---Java/JS/C++三方通信总管
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new  CatalystInstanceImpl.Builder()
        .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        .setJSModulesConfig(javaScriptModulesConfig)
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);

Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
    CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

catalystInstance是一个接口,完结类是CatalystInstanceImpl,跟到构造函数中:

private CatalystInstanceImpl(
      final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModulesConfig jsModulesConfig,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    ...
    mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig);
    ...
  }

经过JavaScriptModulesConfig实例化JavaScriptModuleRegistry,前边分析过JavaScriptModule在Java层都是implements
JavaScriptModule的接口,那么怎么去调用方法?那里就是对每个接口通过动态代理实例化一个代理对象,Java调用JS方法时统一分发到JavaScriptModuleInvocationHandler中的invoke举行处理,在invoke中其实就是调用mCatalystInstance.callFunction

很明朗,接下去需求到JavaScriptModuleRegistry中看看:

/**
 * Class responsible for holding all the {@link JavaScriptModule}s registered to this
 * {@link CatalystInstance}. 
 Uses Java proxy objects to dispatch method calls on JavaScriptModules
 * to the bridge using the corresponding module and method ids so the proper function is executed in
 * JavaScript.
 */
/*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);
    }
  }

  public <T extends JavaScriptModule> T getJavaScriptModule(Class<T> moduleInterface) {
    return (T) Assertions.assertNotNull(
        mModuleInstances.get(moduleInterface),
        "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
  }

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

在Java调用JS模块的主意时,都会经过代办对象调用invoke方法,在invoke方法中调用CatalystInstance.callFunction,CatalystInstance是一个接口,完成类CatalystInstanceImpl,看到CatalystInstanceImpl中的callFunction:

/* package */ void callFunction(
      final int moduleId,
      final int methodId,
      final NativeArray arguments,
      final String tracingName) {
    if (mDestroyed) {
      FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
      return;
    }

    incrementPendingJSCalls();

    final int traceID = mTraceID++;
    Systrace.startAsyncFlow(
        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
        tracingName,
        traceID);

    mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
        new Runnable() {
          @Override
          public void run() {
            mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();

            Systrace.endAsyncFlow(
                Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
                tracingName,
                traceID);

            if (mDestroyed) {
              return;
            }

            Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName);
            try {
              Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments);
            } finally {
              Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
            }
          }
        });
  }

所有Java层向Javascript层的通讯请求都是走的ReactBridge.callFunction

其实就是在JSQueueThread线程中调用ReactBridge的callFunction,是Native函数,到那边就从Java转到C++层了

/**
 * Interface to the JS execution environment and means of transport for messages Java<->JS.
 */
@DoNotStrip
public class ReactBridge extends Countable {

  /* package */ static final String REACT_NATIVE_LIB = "reactnativejni";

  static {
    SoLoader.loadLibrary(REACT_NATIVE_LIB);
  }

  private final ReactCallback mCallback;
  private final JavaScriptExecutor mJSExecutor;
  private final MessageQueueThread mNativeModulesQueueThread;

  /**
   * @param jsExecutor the JS executor to use to run JS
   * @param callback the callback class used to invoke native modules
   * @param nativeModulesQueueThread the MessageQueueThread the callbacks should be invoked on
   */
  public ReactBridge(
      JavaScriptExecutor jsExecutor,
      ReactCallback callback,
      MessageQueueThread nativeModulesQueueThread) {
    mJSExecutor = jsExecutor;
    mCallback = callback;
    mNativeModulesQueueThread = nativeModulesQueueThread;
    initialize(jsExecutor, callback, mNativeModulesQueueThread);
  }

  @Override
  public void dispose() {
    mJSExecutor.close();
    mJSExecutor.dispose();
    super.dispose();
  }

  public void handleMemoryPressure(MemoryPressure level) {
    switch (level) {
      case MODERATE:
        handleMemoryPressureModerate();
        break;
      case CRITICAL:
        handleMemoryPressureCritical();
        break;
      default:
        throw new IllegalArgumentException("Unknown level: " + level);
    }
  }

  private native void initialize(
      JavaScriptExecutor jsExecutor,
      ReactCallback callback,
      MessageQueueThread nativeModulesQueueThread);

  /**
   * 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();
}

再回头看看ReactBridge在CatalystInstanceImpl里面的开头化,初步化会ReactBridge调用setGlobalVariable,这是个Native函数,是在C++层注册用的,先按下后边再分析组件调用进程再分析,先看看
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)做了怎么着。

private CatalystInstanceImpl(
      final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModulesConfig jsModulesConfig,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    ......
    try {
      mBridge = mCatalystQueueConfiguration.getJSQueueThread().callOnQueue(
          new Callable<ReactBridge>() {
            @Override
            public ReactBridge call() throws Exception {
              Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge");
              try {
                return initializeBridge(jsExecutor, jsModulesConfig);
              } finally {
                Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
              }
            }
          }).get(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS);
    } catch (Exception t) {
      throw new RuntimeException("Failed to initialize bridge", t);
    }
  }

  private ReactBridge initializeBridge(
      JavaScriptExecutor jsExecutor,
      JavaScriptModulesConfig jsModulesConfig) {
    mCatalystQueueConfiguration.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(),
          mCatalystQueueConfiguration.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;
  }

在CatalystInstanceImpl中的已毕,其实就是写一个JSON字符串,有七个字段”remoteModuleConfig”和”localModulesConfig”,分别对应NativeModule(Native提要求JS调用)和JSModule(JS提须求Native调用)

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都写入了如何,跟进去JavaScriptModulesConfig.java,就是往JSON字符创中先写入接口名、moduleID、methods(方法名、methodID)等。

/*package*/ 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();
  }

initializeBridge->setGlobalVariable->buildModulesConfigJSONProperty->writeModuleDescriptions

方方面面进程功用是将所有JavaScriptModule的音讯变化JSON字符串预先保存到Bridge中,
通过methodID标识作为key存入JSON生成器中,用来最终生成JSON字符串:

中间setGlobalVariable也是Native函数,buildModulesConfigJSONProperty把所有的JavaScriptModule模块以moduleID+methodID款式转变JSON字符串,通过setGlobalVariable把JSON字符串预先存入了ReactBridge

 bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));

Java层的调用就是上面的经过,最终经过JNI调用到C++层,接下去进入这一层看看。

[Native] 注册 Native Module(在逐个 Native Module 的定义中)

在 Native 端完毕模块时都急需调用 RCT_EXPORT_MODULE
来表明该模块须要暴光给
JavaScript。除此之外还有一批宏定义用来声称其余的消息:

  • RCT_EXPORT_MODULE:申明模块
  • RCT_EXPORT_METHOD:表明方法
  • RCT_EXPORT_VIEW_PROPERTY:注脚属性

经过那种格局成功模块定义后,就可以相当便宜的取获得所有要求对外揭示的模块以及模块须要对外揭发的法子与特性。

以下是基本的宏定义:

#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

#define RCT_EXPORT_VIEW_PROPERTY(name, type) \
+ (NSArray<NSString *> *)propConfig_##name { return @[@#type]; }

...

initModules

该任务会扫描所有的 Native 模块,提取出要暴光给 JS
的那些模块,然后保留到一个字典对象中。
一个 Native 模块若是想要暴光给 JS,必要在申明时呈现地调用
RCT_EXPORT_MODULE。它的概念如下:

#define RCT_EXPORT_MODULE(js_name) \ RCT_EXTERN void
RCTRegisterModule(Class); \ + (NSString *)moduleName { return
@#js_name; } \ + (void)load { RCTRegisterModule(self); }

1
2
3
4
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

可以见见,这就是一个宏,定义了 load
方法,该方法会自动被调用,在措施中对近年来类进行登记。

模块借使要暴光出指定的艺术,需求经过 RCT_EXPORT_METHOD
宏进行宣示,原理类似。

GitHub
MGReactNativeTest
工程里有直接改动main.jsbundle

2.ReactBridge实现

通讯模型图中要调用WebKit的实现,少不了Bridge其一桥梁,因为Java是无法平昔调用WebKit,但是可以Java通过JNIJNI再调用WebKit

JNI入口react/jni/OnLoad.cpp,通过RegisterNatives办法注册的,JNI_OnLoad其间注册了setGlobalVariablecallFunctionnative地面方法

//jni/react/jni/OnLoad.cpp
namespace bridge {
  ......

static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstring jsonValue) {
  auto bridge = extractRefPtr<Bridge>(env, obj);
  bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue));
}
  ...
} // namespace bridge

extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  return initialize(vm, [] {
    // get the current env
    JNIEnv* env = Environment::current();
    registerNatives("com/facebook/react/bridge/ReadableNativeMap$ReadableNativeMapKeySetIterator", {
    ......
    registerNatives("com/facebook/react/bridge/ReactBridge", {
        makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/JavaScriptExecutor;Lcom/facebook/react/bridge/ReactCallback;Lcom/facebook/react/bridge/queue/MessageQueueThread;)V", bridge::create),
        makeNativeMethod(
          "loadScriptFromAssets", "(Landroid/content/res/AssetManager;Ljava/lang/String;)V",
          bridge::loadScriptFromAssets),
        makeNativeMethod("loadScriptFromFile", bridge::loadScriptFromFile),
        makeNativeMethod("callFunction", bridge::callFunction),
        makeNativeMethod("invokeCallback", bridge::invokeCallback),
        makeNativeMethod("setGlobalVariable", bridge::setGlobalVariable),
        makeNativeMethod("supportsProfiling", bridge::supportsProfiling),
        makeNativeMethod("startProfiler", bridge::startProfiler),
        makeNativeMethod("stopProfiler", bridge::stopProfiler),
        makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate),
        makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical),

    });
}

可以看看setGlobalVariable真正调用的是Bridge.cpp里面的setGlobalVariable:

//jni/react/Bridge.h
class JSThreadState;
class Bridge : public Countable {
public:
  typedef std::function<void(std::vector<MethodCall>, bool isEndOfBatch)> Callback;

  Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback);
  virtual ~Bridge();

  /**
   * Flush get the next queue of changes.
   */
  void flush();

  /**
   * Executes a function with the module ID and method ID and any additional
   * arguments in JS.
   */
  void callFunction(const double moduleId, const double methodId, const folly::dynamic& args);

  /**
   * Invokes a callback with the cbID, and optional additional arguments in JS.
   */
  void invokeCallback(const double callbackId, const folly::dynamic& args);

  void executeApplicationScript(const std::string& script, const std::string& sourceURL);
  void setGlobalVariable(const std::string& propName, const std::string& jsonValue);
  bool supportsProfiling();
  void startProfiler(const std::string& title);
  void stopProfiler(const std::string& title, const std::string& filename);
  void handleMemoryPressureModerate();
  void handleMemoryPressureCritical();
private:
  Callback m_callback;
  std::unique_ptr<JSThreadState> m_threadState;
  // This is used to avoid a race condition where a proxyCallback gets queued after ~Bridge(),
  // on the same thread. In that case, the callback will try to run the task on m_callback which
  // will have been destroyed within ~Bridge(), thus causing a SIGSEGV.
  std::shared_ptr<bool> m_destroyed;
};

void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
  m_threadState->setGlobalVariable(propName, jsonValue);
}

末尾调用的是JSThreadState里面的setGlobalVariable:

//jni/react/Bridge.cpp
class JSThreadState {
public:
  JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
    m_callback(callback)
  {
    m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON, bool isEndOfBatch) {
      m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
    });
  }

  void setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
    m_jsExecutor->setGlobalVariable(propName, jsonValue);
  }

private:
  std::unique_ptr<JSExecutor> m_jsExecutor;
  Bridge::Callback m_callback;
};

最后调用JSExecutor里面setGlobalVariable:

//jni/react/JSCExecutor.h
class JSCExecutor : public JSExecutor, public JSCWebWorkerOwner {
public:
  /**
   * Should be invoked from the JS thread.
   */
  explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback);
  ~JSCExecutor() override;

  virtual void executeApplicationScript(
    const std::string& script,
    const std::string& sourceURL) override;
  virtual std::string flush() override;
  virtual std::string callFunction(
    const double moduleId,
    const double methodId,
    const folly::dynamic& arguments) override;
  virtual std::string invokeCallback(
    const double callbackId,
    const folly::dynamic& arguments) override;
  virtual void setGlobalVariable(
    const std::string& propName,
    const std::string& jsonValue) override;
  virtual bool supportsProfiling() override;
  virtual void startProfiler(const std::string &titleString) override;
  virtual void stopProfiler(const std::string &titleString, const std::string &filename) override;
  virtual void handleMemoryPressureModerate() override;
  virtual void handleMemoryPressureCritical() override;

  void flushQueueImmediate(std::string queueJSON);
  void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback);
  virtual void onMessageReceived(int workerId, const std::string& message) override;
  virtual JSGlobalContextRef getContext() override;
  virtual std::shared_ptr<JMessageQueueThread> getMessageQueueThread() override;
};

} }

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

从JSCExecutor::setGlobalVariable中得以看看java层传过来的JSON串(包蕴Native
Modules和JS
Modules)和属性名(__fbBatchedBridgeConfig),赋值给全局变量globalObject,那几个全局变量通过JSContextGetGlobalObject获取到,在JSCExecutor.h头文件中:

#include <memory>
#include <unordered_map>
#include <JavaScriptCore/JSContextRef.h>
#include "Executor.h"
#include "JSCHelpers.h"
#include "JSCWebWorker.h"

JavaScriptCore/JSContextRef.h是Webkit这一个库中的文件,在ReactAndroid下的build.gradle中有目录那几个库,到build.gradle中看看:

亚洲必赢官网 5

ReactAndroid.png

//ReactAndroid/build.gradle
task downloadJSCHeaders(type: Download) {
    def jscAPIBaseURL = 'https://svn.webkit.org/repository/webkit/!svn/bc/174650/trunk/Source/JavaScriptCore/API/'
    def jscHeaderFiles = ['JSBase.h', 'JSContextRef.h', 'JSObjectRef.h', 'JSRetainPtr.h', 'JSStringRef.h', 'JSValueRef.h', 'WebKitAvailability.h']
    def output = new File(downloadsDir, 'jsc')
    output.mkdirs()
    src(jscHeaderFiles.collect { headerName -> "$jscAPIBaseURL$headerName" })
    onlyIfNewer true
    overwrite false
    dest output
}

打开jscAPIBaseURL这么些url可以找到JSContextRef.h头文件:

//JavaScriptCore/API/JSContextRef.h
...
/*!
@function
@abstract Gets the global object of a JavaScript execution context.
@param ctx The JSContext whose global object you want to get.
@result ctx's global object.
*/
JS_EXPORT JSObjectRef JSContextGetGlobalObject(JSContextRef ctx);
...

JSCExecutor::setGlobalVariable最后给JS的全局变量设置一个属性__fbBatchedBridgeConfig,这些特性的值就是JavaScriptModule,Java调用JS的时候从属性中检索,看下callFunction:

//jni/react/JSCExecutor.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));
}

static std::string executeJSCallWithJSC(
    JSGlobalContextRef ctx,
    const std::string& methodName,
    const std::vector<folly::dynamic>& arguments) {
  #ifdef WITH_FBSYSTRACE
  FbSystraceSection s(
      TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall",
      "method", methodName);
  #endif

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

最终以

__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId, methodId, arguments})的方式拼接一个applyJavascript执行语句,最后调用jni/react/JSCHelpers.cppevaluateScript的来实施这一个讲话,完结BridgeJavascript的调用,JSCHelpersWebKit的一些API做了包装,它承担最后调用WebKit

//jni/react/JSCHelpers.h
namespace facebook {
namespace react {

void installGlobalFunction(
    JSGlobalContextRef ctx,
    const char* name,
    JSObjectCallAsFunctionCallback callback);

JSValueRef makeJSCException(
    JSContextRef ctx,
    const char* exception_text);

JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef source);

} }

Bridge层的调用进度:
OnLoad.cpp->Bridge.cpp->JSCExecutor.cpp->JSCHelpers.cpp->WebKit

[Native] 收集各样模块的 config

经过上一步成功所有模块的定义后,RCTBatchedBridge
中会把具有模块音信集中采集起来,并且根据稳定的格式重新社团,最终会把这么些模块音讯体系化后注入到
JavaScript 中。

着力代码如下:

// RCTBatchedBridge.m
- (NSString *)moduleConfig
{
  NSMutableArray<NSArray *> *config = [NSMutableArray new];
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (self.executorClass == [RCTJSCExecutor class]) {
      [config addObject:@[moduleData.name]];
    } else {
      [config addObject:RCTNullIfNil(moduleData.config)];
    }
  }

  return RCTJSONStringify(@{
    @"remoteModuleConfig": config,
  }, NULL);
}

@RCTModuleData
- (NSArray *)config
{
  [self gatherConstants];
  __block NSDictionary<NSString *, id> *constants = _constantsToExport;
  _constantsToExport = nil; // Not needed anymore

  if (constants.count == 0 && self.methods.count == 0) {
    return (id)kCFNull; // Nothing to export
  }

  RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);

  NSMutableArray<NSString *> *methods = self.methods.count ? [NSMutableArray new] : nil;
  NSMutableArray<NSNumber *> *asyncMethods = nil;
  for (id<RCTBridgeMethod> method in self.methods) {
    if (method.functionType == RCTFunctionTypePromise) {
      if (!asyncMethods) {
        asyncMethods = [NSMutableArray new];
      }
      [asyncMethods addObject:@(methods.count)];
    }
    [methods addObject:method.JSMethodName];
  }

  NSMutableArray *config = [NSMutableArray new];
  [config addObject:self.name];
  if (constants.count) {
    [config addObject:constants];
  }
  if (methods) {
    [config addObject:methods];
    if (asyncMethods) {
      [config addObject:asyncMethods];
    }
  }
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);
  return config;
}

setupExecutor

那边设置的是 JS 引擎,同样分为调试环境和生育环境:
在调节环境下,对应的 Executor 为 RCTWebSocketExecutor,它经过 WebSocket
连接受 Chrome 中,在 Chrome 里运行 JS;
在生产环境下,对应的 Executor 为 RCTContextExecutor,那应当就是风传中的
javascriptcore

以身作则工程的代码

  render: function() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          {this.state.changeText}
        </Text>
        <Text style={styles.welcome} onPress={this._onPress}>
          Change
        </Text>
      </View>
    );
  },

11.png

)

3.JavaScript层实现

Javascript的通讯,实质上是Weikit电视公布及新闻循环代码剖析,组件开首化与通信分析。执行Javascript话语,调用流程是Bridge->WebKit->JavascriptWebKit中提供了很多与Javascript通信的API,比如evaluateScriptJSContextGetGlobalObjectJSObjectSetProperty等。

追思一上面前JSCExecutor::setGlobalVariable:

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

所有JavaScriptModule音信是调用的setGlobalVariable形式生成一张映射表,这张映射表最后一定是要保存在Javascript层的,JSContextGetGlobalObjectWeiKit的点子,其目标是得到Global全局对象,jsPropertyName艺术字面意思就是Javascript目的的属性名,参数propName是从Java层传递过来的,在CatalystInstanceImpl.java类中得以证实那或多或少,具体值有七个:__fbBatchedBridgeConfig__RCTProfileIsProfiling.

//com/facebook/react/bridge/CatalystInstanceImpl
bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));

翻译成JS语句大约是:

global.__fbBatchedBridgeConfig = jsonValue;

由于__fbBatchedBridgeConfig目的是被一贯定义成Global大局对象的特性,就足以一向调用了,类似于window对象,__fbBatchedBridgeConfig目标里又有八个特性:remoteModuleConfiglocalModulesConfig,分别对应Java模块和JS模块。

到JS层的BatchedBridge.js:

//Libraries/BatchedBridge/BatchedBridge.js
const MessageQueue = require('MessageQueue');

const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,   //Native(Java)模块
  __fbBatchedBridgeConfig.localModulesConfig,   //JS模块
);

// TODO: Move these around to solve the cycle in a cleaner way.

const Systrace = require('Systrace');
const JSTimersExecution = require('JSTimersExecution');

BatchedBridge.registerCallableModule('Systrace', Systrace);
BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution);

if (__DEV__) {
  BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
}

Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });

module.exports = BatchedBridge;

1.在大局变量global中定义fbBatchedBridge,在前方JSCExecutor::callFunction中调用JS语句的格局是fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId,
methodId, arguments})

2.BatchedBridge=new MessageQueue

从而再持续跟到MessageQueue.js中:

class MessageQueue {

  constructor(remoteModules, localModules) {
    this.RemoteModules = {};

    this._callableModules = {};
    this._queue = [[], [], [], 0];
    this._moduleTable = {};
    this._methodTable = {};
    this._callbacks = [];
    this._callbackID = 0;
    this._callID = 0;
    this._lastFlush = 0;
    this._eventLoopStartTime = new Date().getTime();

    [
      'invokeCallbackAndReturnFlushedQueue',
      'callFunctionReturnFlushedQueue',
      'flushedQueue',
    ].forEach((fn) => this[fn] = this[fn].bind(this));

    let modulesConfig = this._genModulesConfig(remoteModules);
    this._genModules(modulesConfig);
    localModules && this._genLookupTables(
      this._genModulesConfig(localModules),this._moduleTable, this._methodTable
    );

    this._debugInfo = {};
    this._remoteModuleTable = {};
    this._remoteMethodTable = {};
    this._genLookupTables(
      modulesConfig, this._remoteModuleTable, this._remoteMethodTable
    );
  }

  /**
   * Public APIs
   */
  callFunctionReturnFlushedQueue(module, method, args) {
    guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }

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

module.exports = MessageQueue;

把remoteModules,
localModules传进了_genLookupTables的措施里,同时还有三个参数_moduleTable_methodTable,就是我们找的映射表了,一张module映射表,一张method映射表

再回看上面前Java调用JS的语句格局:__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId,
methodId, arguments})

class MessageQueue {

 constructor(remoteModules, localModules) {
   this.RemoteModules = {};

   this._callableModules = {};
   this._queue = [[], [], [], 0];
   this._moduleTable = {};
   this._methodTable = {};
   this._callbacks = [];
   this._callbackID = 0;
   this._callID = 0;
   this._lastFlush = 0;
   this._eventLoopStartTime = new Date().getTime();

   [
     'invokeCallbackAndReturnFlushedQueue',
     'callFunctionReturnFlushedQueue',
     'flushedQueue',
   ].forEach((fn) => this[fn] = this[fn].bind(this));

   let modulesConfig = this._genModulesConfig(remoteModules);
   this._genModules(modulesConfig);
   localModules && this._genLookupTables(
     this._genModulesConfig(localModules),this._moduleTable, this._methodTable
   );

   this._debugInfo = {};
   this._remoteModuleTable = {};
   this._remoteMethodTable = {};
   this._genLookupTables(
     modulesConfig, this._remoteModuleTable, this._remoteMethodTable
   );
 }

 /**
  * Public APIs
  */
 callFunctionReturnFlushedQueue(module, method, args) {
   guard(() => {
     this.__callFunction(module, method, args);
     this.__callImmediates();
   });

   return this.flushedQueue();
 }

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

module.exports = MessageQueue;

个中有个callableModules,
它就是用来存放哪些Javascript组件是可以被调用的,正常情状下**
callableModules的多少和JavaScriptModules**的数据(包含方法名和参数)理应是一点一滴对应的.

看下哪些JS组件调用registerCallableModule进行形式的注册

亚洲必赢官网 6

resigterCallableModule.png

看一下RCTEventEmitter.js:

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

ReactNativeEventEmitter.js:

var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
  receiveEvent: function(
    tag: number,
    topLevelType: string,
    nativeEventParam: Object
  ) {
    ......
  },

  receiveTouches: function(
    eventTopLevelType: string,
    touches: Array<Object>,
    changedIndices: Array<number>
    ) {
      ......
    });

比较下Java层的接口表示,方法名称和参数个数是如出一辙的

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

此起彼伏前面__callFunction的最终一步:moduleMethods[method].apply(moduleMethods,
args);调用对应模块的格局。

[Native] 初始化 Bridge(在 ViewController 中)

self.rctRootView = 
    [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                moduleName:@"TiebaNext"
                         initialProperties:nil
                             launchOptions:nil];

moduleConfig

基于保存的模块新闻,组装成一个 JSON ,对应的字段为 remoteModuleConfig。

Native 与 JS的相互调用

0.17版本的React Native
JS引擎已经整整施用的是iOS自带的JavaScriptCore,在JSContext提供的Native与js相互调用的基础上,封装出了团结的互调方法。上边是一张结构图

架构图.png

4.总结

Java调用JavaScript可以总结为上边:

亚洲必赢官网 7

调用流程.png

迎接关怀群众号:JueCode

[Native] 将模块音信注入到 JSCExecutor 中的全局变量

// RCTBatchedBridge.m
- (void)injectJSONConfiguration:(NSString *)configJSON
                     onComplete:(void (^)(NSError *))onComplete
{
  if (!_valid) {
    return;
  }

  [_javaScriptExecutor injectJSONText:configJSON
                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
                             callback:onComplete];
}

__fbBatchedBridgeConfig 的结构如下

{
    "remoteModuleConfig": [
        [
            "模块名称",
            {
                属性对象(键值对,值类型不限)
            },
            [
                方法列表
            ],
            [
                Promise 方法 index(方法列表中哪些方法是异步的)
            ]
        ],

  1. 各种模块的 index 就是以此模块的 moduleID
  2. 各类方法的 index 就是其一方法的 methodID
  3. 最后输出到 remoteModuleConfig 中的模块并不是有所的
    Module,有的会被过滤掉,所以输出里有的是 null

如上结构的变更在 RCTModuleData config 方法中:

// RCTModuleData.m
- (NSArray *)config
{
  //......
  NSMutableArray *config = [NSMutableArray new];
  [config addObject:self.name]; // 模块名称(字符串)
  if (constants.count) {
    [config addObject:constants]; // 属性对象(对象)
  }
  if (methods) {
    [config addObject:methods]; // 方法列表(数组)
    if (asyncMethods) {
      [config addObject:asyncMethods]; // Promise 方法列表(数组)
    }
  }
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData config] %@", _moduleClass], nil);
  return config;
}

举个例子

{
    "remoteModuleConfig": [
        null,
        [
            "RCTAccessibilityManager",
            [
                "setAccessibilityContentSizeMultipliers",
                "getMultiplier",
                "getCurrentVoiceOverState"
            ]
        ],
        [
            "RCTViewManager",
            {
                "forceTouchAvailable": false
            }
        ],
        ...
        [
            "RCTClipboard",
            [
                "setString",
                "getString"
            ],
            [
                1
            ]
        ],
        ...
        [
            "RCTPushNotificationManager",
            {
                "initialNotification": null
            },
            [
                "setApplicationIconBadgeNumber",
                "getApplicationIconBadgeNumber",
                "requestPermissions",
                "abandonPermissions",
                "checkPermissions",
                "presentLocalNotification",
                "scheduleLocalNotification",
                "cancelAllLocalNotifications",
                "cancelLocalNotifications",
                "getInitialNotification",
                "getScheduledLocalNotifications",
                "addListener",
                "removeListeners"
            ],
            [
                2,
                9
            ]
        ]

injectJSONConfiguration

该职务将上一个职务组建的 JSON 注入到 Executor 中。
上边是一个 JSON 示例,由于实在的对象太大,那里只截取了前方的一些:
亚洲必赢官网 8
JSON 里面纵使所有揭暴露来的模块消息。

App启动进程中 Native和JS相互调用的日志

[Log] N->JS : RCTDeviceEventEmitter.emit(["appStateDidChange",{"app_state":"active"}]) (main.js, line 638)
[Log] N->JS : RCTDeviceEventEmitter.emit(["networkStatusDidChange",{"network_info":"wifi"}]) (main.js, line 638)
[Log] N->JS : AppRegistry.runApplication(["MGReactNative",{"rootTag":1,"initialProps":{}}]) (main.js, line 638)
[Log] Running application "MGReactNative" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([2,"RCTView",1,{"flex":1}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([3,"RCTView",1,{"flex":1}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([4,"RCTView",1,{"flex":1,"justifyContent":"center","alignItems":"center","backgroundColor":4294311167}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([5,"RCTText",1,{"fontSize":20,"textAlign":"center","margin":10,"accessible":true,"allowFontScaling":true}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([6,"RCTRawText",1,{"text":"Welcome to React Native!"}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.manageChildren([5,null,null,[6],[0],null]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([7,"RCTText",1,{"textAlign":"center","color":4281545523,"marginBottom":5,"accessible":true,"allowFontScaling":true}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([8,"RCTRawText",1,{"text":"soap1"}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.manageChildren([7,null,null,[8],[0],null]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([9,"RCTText",1,{"fontSize":20,"textAlign":"center","margin":10,"accessible":true,"allowFontScaling":true,"isHighlighted":false}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([10,"RCTRawText",1,{"text":"Change"}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.manageChildren([9,null,null,[10],[0],null]) (main.js, line 638)
[Log] JS->N : RCTUIManager.manageChildren([4,null,null,[5,7,9],[0,1,2],null]) (main.js, line 638)
[Log] JS->N : RCTUIManager.manageChildren([3,null,null,[4],[0],null]) (main.js, line 638)
[Log] JS->N : RCTUIManager.createView([12,"RCTView",1,{"position":"absolute"}]) (main.js, line 638)
[Log] JS->N : RCTUIManager.manageChildren([2,null,null,[3,12],[0,1],null]) (main.js, line 638)
[Log] JS->N : RCTUIManager.manageChildren([1,null,null,[2],[0],null]) (main.js, line 638)

日志展现了启动React Native 界面
Native与JS的调用过程,大家从最简易的例子出手,渐渐脱下女神的面罩。

[JS] 依照上述模块配置音信变更 JS 模块

//BatchedBridge.js
const BatchedBridge = new MessageQueue(
  () => global.__fbBatchedBridgeConfig
);

  1. BatchedBridge 是 MessageQueue 的一个实例
  2. 第三回访问 BatchedBridge.RemoteModules 时 MessageQueue 会利用
    global.__fbBatchedBridgeConfig 模块配置信息来变化 JS 模块

executeSourceCode

该职责中会执行加载过来的 JS 代码,执行时传出之前注入的 JSON。
在调试形式下,会透过 WebSocket 给 Chrome 发送一条 message,内容大概为:

JavaScript

{ id = 10305; inject = {remoteJSONConfig…}; method =
executeApplicationScript; url =
“”; }

1
2
3
4
5
6
{
    id = 10305;
    inject = {remoteJSONConfig…};
    method = executeApplicationScript;
    url = "http://localhost:8081/index.ios.bundle?platform=ios&dev=true";
}

JS 接收音信后,执行打包后的代码。假设是非调试格局,则一向通过
javascriptcore 的虚拟环境去实践有关代码,效果类似。

Native调用JS (Native->JS)

可以看看,启动上马之后,Native调用了JS的 RCTDevice伊芙ntEmitter.emit
广播了两个事件 appStateDidChange,networkStatusDidChange
随之调用
AppRegistry.runApplication([“MGReactNative”,{“rootTag”:1,”initialProps”:{}}])
启动了React Native引擎。
下边大家一点点分析,是如若从Native调用到JS的函数AppRegistry.runApplication的

[JS] JS 模块的浮动

率先,JS 模块的构造

{
    name: "模块名称",
    {
        方法名:方法实现(区分是否 Promise(Sync)) 类型
        属性名:
        moduleID: 模块Index
    }
}

现实包含几块:

  • 模块名
  • 模块属性
  • moduleID
  • 模块方法

第一步 遍历所有 remoteModules,即 global.__fbBatchedBridgeConfig
列表:

// MessageQueue.js
_genModules(remoteModules) {
  const modules = {};

  remoteModules.forEach((config, moduleID) => {
    const info = this._genModule(config, moduleID);
    if (info) {
      modules[info.name] = info.module;
    }
  });

  return modules;
}

第二步 解析出各样 Module
的新闻,包罗模块名、属性对象、方法列表、异步(Promise)方法列表、syncHooks(这么些和
native 结构对应不上,有可能弃用了)

// MessageQueue.js
let moduleName, constants, methods, asyncMethods, syncHooks;
if (moduleHasConstants(config)) {
  [moduleName, constants, methods, asyncMethods, syncHooks] = config;
} else {
  [moduleName, methods, asyncMethods, syncHooks] = config;
}

其三步 按照章程列表、属性对象生成 module 对象

// MessageQueue.js
// 初始化新 module
const module = {};
methods && methods.forEach((methodName, methodID) => {
  const isAsync = asyncMethods && arrayContains(asyncMethods, methodID);
  const isSyncHook = syncHooks && arrayContains(syncHooks, methodID);
  invariant(!isAsync || !isSyncHook, 'Cannot have a method that is both async and a sync hook');
  // 方法类型,Async 方法调用会返回 Promise 对象
  const methodType = isAsync ? MethodTypes.remoteAsync :
      isSyncHook ? MethodTypes.syncHook :
      MethodTypes.remote;
  // 生成方法
  module[methodName] = this._genMethod(moduleID, methodID, methodType);
});
// 将属性浅拷贝到 module 上
Object.assign(module, constants);

至于艺术的转移:

  1. 实际都是将艺术的实际操作代理给了 __nativeCall(moduleID,
    methodID, args, onFail, onSucc)
  2. 对于 async 方法会用 Promise 对象包装一下,然后 onFail 的时候
    reject,以及 onSucc 的时候 resolve(然而看代码里onFail 和 onSucc
    的职位不雷同,是不是有难点亟需更为验证)
  3. 关于 __nativeCall 的逻辑后续写一篇JS代码执行后的辨析来分析

// MessageQueue.js
_genMethod(module, method, type) {
    let fn = null;
    const self = this;
    if (type === MethodTypes.remoteAsync) {
      fn = function(...args) {
        return new Promise((resolve, reject) => {
          self.__nativeCall(
            module,
            method,
            args,
            (data) => {
              resolve(data);
            },
            (errorData) => {
              var error = createErrorFromErrorData(errorData);
              reject(error);
            });
        });
      };
    } else if (type === MethodTypes.syncHook) {
      return function(...args) {
        return global.nativeCallSyncHook(module, method, args);
      };
    } else {
      fn = function(...args) {
        const lastArg = args.length > 0 ? args[args.length - 1] : null;
        const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
        const hasSuccCB = typeof lastArg === 'function';
        const hasErrorCB = typeof secondLastArg === 'function';
        hasErrorCB && invariant(
          hasSuccCB,
          'Cannot have a non-function arg after a function arg.'
        );
        const numCBs = hasSuccCB + hasErrorCB;
        const onSucc = hasSuccCB ? lastArg : null;
        const onFail = hasErrorCB ? secondLastArg : null;
        args = args.slice(0, args.length - numCBs);
        return self.__nativeCall(module, method, args, onFail, onSucc);
      };
    }
    fn.type = type;
    return fn;
  }

JS 调用 Native

眼前大家来看, Native 调用 JS 是通过发送音讯到 Chrome
触发执行、或者直接通过 javascriptcore 执行 JS 代码的。而对此 JS 调用
Native 的情状,又是怎么着的吗?

在 JS 端调用 Native 一般都是一直通过引用模块名,然后就选择了,比如:

JavaScript

var RCTAlertManager = require(‘NativeModules’).AlertManager

1
var RCTAlertManager = require(‘NativeModules’).AlertManager

足见,NativeModules 是兼备地方模块的操作接口,找到它的概念为:

JavaScript

var NativeModules = require(‘BatchedBridge’).RemoteModules;

1
var NativeModules = require(‘BatchedBridge’).RemoteModules;

而BatchedBridge中是一个MessageQueue的对象:

JavaScript

let BatchedBridge = new MessageQueue(
__fbBatchedBridgeConfig.remoteModuleConfig,
__fbBatchedBridgeConfig.localModulesConfig, );

1
2
3
4
let BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);

在 MessageQueue 实例中,都有一个 RemoteModules 字段。在 MessageQueue
的构造函数中可以见见,RemoteModules 就是
__fbBatchedBridgeConfig.remoteModuleConfig 稍微加工后的结果。

JavaScript

class MessageQueue { constructor(remoteModules, localModules,
customRequire) { this.RemoteModules = {};
this._genModules(remoteModules); … } }

1
2
3
4
5
6
7
8
class MessageQueue {
 
  constructor(remoteModules, localModules, customRequire) {
    this.RemoteModules = {};
    this._genModules(remoteModules);
    …
    }
}

就此难点就成为: __fbBatchedBridgeConfig.remoteModuleConfig
是在何地赋值的?

事实上,那么些值就是 从 Native 端传过来的JSON 。如前所述,Executor
会把模块配置组装的 JSON 保存到其中:

JavaScript

[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@”__fbBatchedBridgeConfig” callback:onComplete];

1
2
3
[_javaScriptExecutor injectJSONText:configJSON
                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
                             callback:onComplete];

configJSON
实际保存的字段为:_injectedObjects['__fbBatchedBridgeConfig']

在 Native 第两遍调用 JS 时,_injectedObjects 会作为传递信息的
inject 字段。
JS 端收到那些音讯,经过下边那一个紧要的处理进度:

JavaScript

‘executeApplicationScript’: function(message, sendReply) { for (var key
in message.inject) { self[key] = JSON.parse(message.inject[key]); }
importScripts(message.url); sendReply(); },

1
2
3
4
5
6
7
‘executeApplicationScript’: function(message, sendReply) {
    for (var key in message.inject) {
      self[key] = JSON.parse(message.inject[key]);
    }
    importScripts(message.url);
    sendReply();
  },

总的来看没,那里读取了 inject 字段并拓展了赋值。self
是一个大局的命名空间,在浏览器里 self===window

就此,上面代码执行过后,window.__fbBatchedBridgeConfig
就被赋值为了传过来的 JSON 反体系化后的值。

总之:
NativeModules = __fbBatchedBridgeConfig.remoteModuleConfig =
JSON.parse(message.inject[‘__fbBatchedBridgeConfig’]) =
模块揭披露的具备新闻

好,有了上述的前提之后,接下去以一个实在调用例子表明下 JS 调用 Native
的进度。
首先我们通过 JS 调用一个 Native 的方式:

JavaScript

‘executeApplicationScript’: function(message, sendReply) { for (var key
in message.inject) { self[key] = JSON.parse(message.inject[key]); }
importScripts(message.url); sendReply(); },

1
2
3
4
5
6
7
‘executeApplicationScript’: function(message, sendReply) {
    for (var key in message.inject) {
      self[key] = JSON.parse(message.inject[key]);
    }
    importScripts(message.url);
    sendReply();
  },

拥有 Native 方法调用时都会先进入到上面的办法中:

JavaScript

fn = function(…args) { let lastArg = args.length > 0 ?
args[args.length – 1] : null; let secondLastArg = args.length > 1 ?
args[args.length – 2] : null; let hasSuccCB = typeof lastArg ===
‘function’; let hasErrorCB = typeof secondLastArg === ‘function’; let
numCBs = hasSuccCB + hasErrorCB; let onSucc = hasSuccCB ? lastArg :
null; let onFail = hasErrorCB ? secondLastArg : null; args =
args.slice(0, args.length – numCBs); return self.__nativeCall(module,
method, args, onFail, onSucc); };

1
2
3
4
5
6
7
8
9
10
11
fn = function(…args) {
  let lastArg = args.length > 0 ? args[args.length – 1] : null;
  let secondLastArg = args.length > 1 ? args[args.length – 2] : null;
  let hasSuccCB = typeof lastArg === ‘function’;
  let hasErrorCB = typeof secondLastArg === ‘function’;
  let numCBs = hasSuccCB + hasErrorCB;
  let onSucc = hasSuccCB ? lastArg : null;
  let onFail = hasErrorCB ? secondLastArg : null;
  args = args.slice(0, args.length – numCBs);
  return self.__nativeCall(module, method, args, onFail, onSucc);
};

也就是尾数后多个参数是谬误和正确的回调,剩下的是方法调用本身的参数。

在 __nativeCall 方法中,会将七个回调压到 callback 数组中,同时把
(模块、方法、参数) 也单独保存到其中的队列数组中:

JavaScript

onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail; onSucc &&
params.push(this._callbackID); this._callbacks[this._callbackID++]
= onSucc; this._queue[0].push(module);
this._queue[1].push(method); this._queue[2].push(params);

1
2
3
4
5
6
7
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
this._queue[0].push(module);
this._queue[1].push(method);
this._queue[2].push(params);

到这一步,JS 端告一段落。接下来是 Native 端,在调用 JS
时,经过如下的流水线:

亚洲必赢官网 9

简单来说,就是在调用 JS 时,顺便把前边封存的 queue 作为再次回到值
一并赶回,然后会对该重返值举办解析。
在 _handleRequestNumber 方法中,终于成功了 Native 方法的调用:

– (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSUInteger)moduleID methodID:(NSUInteger)methodID
params:(NSArray *)params { // 解析模块和措施 RCTModuleData *moduleData
= _moduleDataByID[moduleID]; id<RCTBridgeMethod> method =
moduleData.methods[methodID]; <a
href=’; {
// 落成调用 [method invokeWithBridge:self module:moduleData.instance
arguments:params]; } <a
href=’;
(NSException *exception) { } NSMutableDictionary *args =
[method.profileArgs mutableCopy]; [args setValue:method.JSMethodName
forKey:@”method”]; [args
setValue:RCTJSONStringify(RCTNullIfNil(params), NULL) forKey:@”args”];
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
– (BOOL)_handleRequestNumber:(NSUInteger)i
                    moduleID:(NSUInteger)moduleID
                    methodID:(NSUInteger)methodID
                      params:(NSArray *)params
{
  // 解析模块和方法
  RCTModuleData *moduleData = _moduleDataByID[moduleID];
  id<RCTBridgeMethod> method = moduleData.methods[methodID];
  <a href=’http://www.jobbole.com/members/xyz937134366′>@try</a> {
    // 完成调用
    [method invokeWithBridge:self module:moduleData.instance arguments:params];
  }
  <a href=’http://www.jobbole.com/members/wx895846013′>@catch</a> (NSException *exception) {
  }
 
  NSMutableDictionary *args = [method.profileArgs mutableCopy];
  [args setValue:method.JSMethodName forKey:@"method"];
  [args setValue:RCTJSONStringify(RCTNullIfNil(params), NULL) forKey:@"args"];
}

同时,执行后还会因此 invokeCallbackAndReturnFlushedQueue 触发 JS
端的回调。具体细节在 RCTModuleMethod 的 processMethodSignature 方法中。

再下结论一下,JS 调用 Native 的长河为 :

  • JS 把(调用模块、调用方法、调用参数) 保存到行列中;
  • Native 调用 JS 时,顺便把队列重返过来;
  • Native 处理队列中的参数,同样解析出(模块、方法、参数),并通过
    NSInvocation 动态调用;
  • Native方法调用落成后,再一次主动调用 JS。JS 端通过
    callbackID,找到呼应JS端的 callback,举办四次调用

一切进程大约就是那样,剩下的一个难题不怕,为啥要等待 Native 调用 JS
时才会接触,中间会不会有很长延时?
实质上,只要有事件触发,Native 就会调用
JS。比如,用户只要对屏幕举办触摸,就会接触在 RCTRootView 中登记的
Handler,并发送给JS:

JavaScript

[_bridge enqueueJSCall:@”RCTEventEmitter.receiveTouches”
args:@[eventName, reactTouches, changedIndexes]];

1
2
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
                  args:@[eventName, reactTouches, changedIndexes]];

除了触摸事件,还有 提姆er 事件,系统事件等,只要事件触发了,JS
调用时就会把队列重回。那块领悟可以参考 React
Native通信机制详解
一文中的“事件响应”一节。

系统JavascriptCore 中Native怎么样调用JS

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"function add(a, b) { return a + b; }"];
JSValue *add = context[@"add"];
NSLog(@"Func:  %@", add);

JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
NSLog(@"Sum:  %d",[sum toInt32]);
//OutPut:
//  Func:  function add(a, b) { return a + b; }
//  Sum:  28

JSContext 是运作 JavaScript 代码的条件。一个 JSContext
是一个大局环境的实例,我们可以从
JSContext全局变量中用下标的不二法门取出JS代码中定义的函数
add,它用JSValue类型包装了一个 JS 函数,
假使你规定JSValue是一个JS函数类型,可以拔取callWithArguments
来调用它。
更详尽的介绍可以学学那篇小说
Java​Script​Core

[JS] NativeModules 生成

通过后面的步调,native 注入到 JSCExecutor 中的
global.__fbBatchedBridgeConfig 在 BatchedBridge.RemoteModules
属性首次被访问的时候被分析成完全的 module,下一步则是将这几个 module 放入
NativeModules 对象并供外部访问。
其它,那里有个一个小拍卖,native 注入的模块平时以 RCT 或者 RK
为前缀,此处会将前缀干掉(以下代码未包涵)。

//NativeModules.js
const BatchedBridge = require('BatchedBridge');
const RemoteModules = BatchedBridge.RemoteModules;
//代码省略...
/**
 * 这里也是一个 lazy getters
 * Define lazy getters for each module.
 * These will return the module if already loaded, or load it if not.
 */
const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
  Object.defineProperty(NativeModules, moduleName, {
    configurable: true,
    enumerable: true,
    get: () => {
      let module = RemoteModules[moduleName];
      //代码省略...
      Object.defineProperty(NativeModules, moduleName, {
        configurable: true,
        enumerable: true,
        value: module,
      });
      return module;
    },
  });
});

总结

俗话说一图胜千言,整个启动进度用一张图概括起来就是:
亚洲必赢官网 10

本文简要介绍了 iOS 端启动时 JS 和 Native 的相互进度,可以观察BatchedBridge 在双边通讯进程中扮演了关键的角色。Native 调用 JS 是由此WebSocket 或间接在 javascriptcore 引擎上举行;JS 调用 Native
则只把调用的模块、方法和参数先缓存起来,等到事件触发后透过重临值传到
Native 端,其它两者都保留了拥有揭露的 Native
模块音讯表作为通讯的底蕴。由于对 iOS
端开发并不熟悉,文中如有错误的地点还请提议。

参考资料:

  • GCD
    Reference
  • BRIDGING IN REACT
    NATIVE
  • React Native
    调研报告
  • React Native通讯机制详解

    1 赞 1 收藏
    评论

亚洲必赢官网 11

AppRegistry.runApplication

精晓的您肯定想到,React Native
的也是用同样办法调用到AppRegistry.runApplication,是的,然则是透过一个通用接口来调用的
RCTJavaScriptContext 封装了OC方法callFunction,

- (void)callFunctionOnModule:(NSString *)module
                      method:(NSString *)method
                   arguments:(NSArray *)args
                    callback:(RCTJavaScriptCallback)onComplete
{
  // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
  [self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete];
}

_executeJSCall 执行的有血有肉代码是

    method =  @“callFunctionReturnFlushedQueue”
    JSStringRef moduleNameJSStringRef = JSStringCreateWithUTF8CString("__fbBatchedBridge");
    JSValueRef moduleJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef);
    JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method);
    JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, 
    resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, 

可以观察Native 从JSContext中拿出JS全局对象
__fbBatchedBridge,然后调用了其callFunctionReturnFlushedQueue函数

[JS] UIManager 的再加工

native 生成 __fbBatchedBridgeConfig 配置音讯的时候,view 模块和非 view
模块(差异是 view 模块都是由 RC电视iewManager 来保管)是独家对待的,所有的
view 模块新闻都位居了 “RCTUIManager” 的品质对象中,参照
__fbBatchedBridgeConfig的布局大体上是上边那样的。

透过 NativeModules 的转移进度做到了 remoteModuleConfig
中外层模块的处理(包涵 NativeModules.UIManager
也在这一进程中变化了始于版本),那 UIManager 的再加工进程则是分析和处理
RCTUIManager 内各样 view 的长河。

{
    "remoteModuleConfig": [
        [
            // 模块名称
            "RCTUIManager",
            // 属性对象
            {
                // 此处省略
                "RCTRawText": {
                    "Manager": "RCTRawTextManager",
                    "NativeProps": {
                        "text": "NSString"
                    }
                },
                "RCTSwitch": {
                    "Manager": "RCTSwitchManager",
                    "NativeProps": {
                        "thumbTintColor": "UIColor",
                        "tintColor": "UIColor",
                        "value": "BOOL",
                        "onChange": "BOOL",
                        "onTintColor": "UIColor",
                        "disabled": "BOOL"
                    }
                }
                // 此处省略
            },
            // 方法列表
            [
                "removeSubviewsFromContainerWithID",
                "removeRootView",
                "replaceExistingNonRootView",
                "setChildren",
                "manageChildren",
                "createView",
                "updateView",
                "focus",
                "blur",
                "findSubviewIn",
                "dispatchViewManagerCommand",
                "measure",
                "measureInWindow",
                "measureLayout",
                "measureLayoutRelativeToParent",
                "measureViewsInRect",
                "takeSnapshot",
                "setJSResponder",
                "clearJSResponder",
                "configureNextLayoutAnimation"
            ],
            // Promise 方法 index(方法列表中哪些方法是异步的)
            [
                16
            ]
        ],

UIManager 的再加工最要紧不外乎四个地点,为 UIManager 上的各种 view 添加
Constants 和 Commands 属性。

  • 1 生成 Constants 属性

    • 获得每个 view 的 Manager 名称,例如 RCTSwitchManager
    • 在 NativeModules 上查找 RCTSwitchManager 对象
    • 万一存在则将 RCTSwitchManager 对象中所有非 function 属性放入
      Constants

// UIManager.js
const viewConfig = UIManager[viewName];
if (viewConfig.Manager) {
let constants;
/* $FlowFixMe - nice try. Flow doesn't like getters */
Object.defineProperty(viewConfig, 'Constants', {
  configurable: true,
  enumerable: true,
  get: () => {
    if (constants) {
      return constants;
    }
    constants = {};
    const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
    viewManager && Object.keys(viewManager).forEach(key => {
      const value = viewManager[key];
      if (typeof value !== 'function') {
        constants[key] = value;
      }
    });
    return constants;
  },
});   
  • 2 生成 Commands 属性

    • 得到每个 view 的 Manager 名称,例如 RCTSwitchManager
    • 在 NativeModules 上查找 RCTSwitchManager 对象
    • 假若存在则将 RCTSwitchManager 对象中持有 function 属性放入
      Commands,value 为属性的索引值

// UIManager.js
let commands;
/* $FlowFixMe - nice try. Flow doesn't like getters */
Object.defineProperty(viewConfig, 'Commands', {
  configurable: true,
  enumerable: true,
  get: () => {
    if (commands) {
      return commands;
    }
    commands = {};
    const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
    let index = 0;
    viewManager && Object.keys(viewManager).forEach(key => {
      const value = viewManager[key];
      if (typeof value === 'function') {
        commands[key] = index++;
      }
    });
    return commands;
  },
});

是时候克制心中的恐惧,初叶掀裙子了 ,来看望main.jsbundle中的JS代码

上文Natvie调用JS的门路到了
__fbBatchedBridge.callFunctionReturnFlushedQueue
js代码这一步,demo工程中,大家团结写的index.ios.js 只有区区几行,去Node
Server转一圈或React
Native的预编译之后,竟然发出了1.3M,近5W行JS代码的main.jsbundle
文件,对于极端同学来说,大概是一座雁荡山。不要惧怕,大家一起寻找其中的奥妙。

接轨跟踪js代码中的 __fbBatchedBridge

//main.js
__d('BatchedBridge',function(global, require, module, exports) {  'use strict';
    var MessageQueue=require('MessageQueue');
    var BatchedBridge=new MessageQueue(
        __fbBatchedBridgeConfig.remoteModuleConfig,
        __fbBatchedBridgeConfig.localModulesConfig);

    Object.defineProperty(global,'__fbBatchedBridge',{value:BatchedBridge});

    module.exports=BatchedBridge;
});

大家发现那段JS代码中有那句
Object.defineProperty(global,’__fbBatchedBridge’,{value:BatchedBridge});

启动 JavaScript App

- (void)runApplication:(RCTBridge *)bridge
{
  NSString *moduleName = _moduleName ?: @"";
  NSDictionary *appParameters = @{
    @"rootTag": _contentView.reactTag,
    @"initialProps": _appProperties ?: @{},
  };

  [bridge enqueueJSCall:@"AppRegistry.runApplication"
                   args:@[moduleName, appParameters]];
}
准备知识,对JS很熟的同班能够略过或指正

那段JS代码怎么精晓吧,这些是nodejs的模块代码,当打包成main.js之后,含义又有变动,大家差不离可以那样敞亮,__d()
是一个概念module的JS函数,其就相当于下边那段代码中的 define
函数,从代码上很不难可以知晓,它定义一个module,名字Id为BatchedBridge,同时传递了一个厂子函数,另一个模块的代码可以经过调用require获取那一个module,例如
var BatchedBridge=require('BatchedBridge');

那是一个懒加载机制,当有人调用require时,工厂函数才实施,在代码最后,把那个模块要导出的内容赋值给module.exports。

        function define(id,factory){
            modules[id]={
                factory:factory,
                module:{exports:{}},
                isInitialized:false,
                hasError:false};}

        function require(id){
            var mod=modules[id];
            if(mod&&mod.isInitialized){
                return mod.module.exports;}

好,大家赶紧回来,在上段代码中当BatchedBridge module创制时,通过那句
Object.defineProperty(global,’__fbBatchedBridge‘,{value:BatchedBridge});
把温馨定义到JSContext的全局变量上。所以在Native代码中得以经过
JSContext[@”__fbBatchedBridge”]获取到,

从代码中也得以观看BatchedBridge
是JS类MessageQueue的实例,并且它导出的时候并没有导出构造函数MessageQueue,而是导出的实例BatchedBridge,所以它是React
Native JS引擎中全局唯一的。它也是Natvie和JS互通的关键桥梁。

 __fbBatchedBridge.callFunctionReturnFlushedQueue("AppRegistr","runApplication",["MGReactNative",{"rootTag":1,"initialProps":{}}])

大家继续看MessageQueue 类的callFunctionReturnFlushedQueue
函数,它说到底调用到__callFunction(module, method, args)函数

__callFunction(module, method, args) {
    var moduleMethods = this._callableModules[module];
    if (!moduleMethods) {

      moduleMethods = require(module);
    }

    moduleMethods[method].apply(moduleMethods, args);
  }

看起来__callFunction就是终极的散发函数了,首先它从this._callableModules中找到模块对象,若是它还尚无加载,就动态加载它(require),要是找到就举行最后的JS函数。

友好支付的JS模块如若暴光给Native调用

先看下AppRegistry是什么样暴光给Natvie的

__d('AppRegistry',function(global, require, module, exports) {  'use strict';

    var BatchedBridge=require('BatchedBridge');
    var ReactNative=require('ReactNative');

    var AppRegistry={

        runApplication:function(appKey,appParameters){
            runnables[appKey].run(appParameters);
            },
        }

    BatchedBridge.registerCallableModule(
        'AppRegistry',
        AppRegistry);

    module.exports=AppRegistry;
});

有眼前的任课,现在看那几个应该不态费力了,可以见到AppRegistry模块工厂函数中,执行了
BatchedBridge.registerCallableModule(‘AppRegistry’,AppRegistry);,把团结注册到BatchedBridge的CallableModule中,所以在上一节中,__callFunction才能在_callableModules找到AppRegistry实例,才能调用其runApplication函数。自己写的模块代码可以用React
Native那种办法暴光给Natvie调用,和一贯暴光的差别是,符合React
Natvie的模块化原则,其余一个直观的补益是您的模块可以是懒加载的,并且不会传染全局空间。

当前好不不难把从N-JS的所有路径跑通了,大家梳理下一切流程看看。

[RCTBatchedBridge enqueueJSCall:@“AppRegistry.runApplication”
args:[“MGReactNative”,{“rootTag”:1,”initialProps”:{}}]];

>```
 RCTJavaScriptContext callFunctionOnModule:@"AppRegistr"
                      method:@"runApplication"
                   arguments:["MGReactNative",{"rootTag":1,"initialProps":{}}]
                    callback:(RCTJavaScriptCallback)onComplete
//main.js

__fbBatchedBridge.callFunctionReturnFlushedQueue(“AppRegistr”,”runApplication”,[“MGReactNative”,{“rootTag”:1,”initialProps”:{}}])

>```
>//main.js
 BatchedBridge.__callFunction("AppRegistr","runApplication",["MGReactNative",{"rootTag":1,"initialProps":{}}])
//main.js

var moduleMethods = BatchedBridge._callableModules[module];
if (!moduleMethods) {
moduleMethods = require(module);
}
moduleMethods[method].apply(moduleMethods, args);

##JS调用Native (JS->Native)

接下来我们看看从JS如何调用Native,换句话说Native如何开放API给JS

我们以弹Alert框的接口为例,这是Native的OC代码,导出RCTAlertManager类的alertWithArgs:(NSDictionary *)args
                  callback:(RCTResponseSenderBlock)callback)方法

@interface RCTAlertManager() : NSObject <RCTBridgeModule,
RCTInvalidating>

@end

@implementation RCTAlertManager
RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args
callback:(RCTResponseSenderBlock)callback)
{

end

####要把OC类或实例的函数导出给JS用,需实现以下三个步骤

* OC类实现RCTBridgeModule协议
* 在.m的类实现中加入RCT_EXPORT_MODULE(),帮助你实现RCTBridgeModule协议
* 要导出的函数用RCT_EXPORT_METHOD()宏括起来,不用这个宏,不会导出任何函数

现在从JS里可以这样调用这个方法:

var
RCTAlertManager=require(‘react-native’).NativeModules.AlertManager;
RCTAlertManager.alertWithArgs({message:’JS->Native
Call’,buttons:[{k1:’button1′},{k2:’button1′}]},function(id,v)
{console.log(‘RCTAlertManager.alertWithArgs() id:’ + id +’ v:’ + v)});

执行之后的效果,弹出一个Alert


![alert.png](http://upload-images.jianshu.io/upload_images/1393048-13528fed29ee9da5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)         

对于详细的如何导出函数推荐阅读[Native Modules](http://facebook.github.io/react-native/docs/native-modules-ios.html#content)

我们今天的目的不是和女神喝茶聊天,是深入女神内心,是内心咳咳。来看看今天的重点

####动态导出Native API,延迟加载Native 模块

在JS中可以直接使用RCTAlertManager.alertWithArgs来调用,说明JS中已经定义了和OC对象相对应的JS对象,我们从导出一个Native类开始,完整跟踪下这个过程。

####生成Native API 配置表
RCTAlertManager类实现了RCTBridgeModule协议,并且在类的实现里包含了RCT_EXPORT_MODULE() 宏

@protocol RCTBridgeModule <NSObject>

define RCT_EXPORT_MODULE(js_name) \

RCT_EXTERN void RCTRegisterModule(Class); \

  • (NSString *)moduleName { return @#js_name; } \
  • (void)load { RCTRegisterModule(self); }

// Implemented by RCT_EXPORT_MODULE

  • (NSString *)moduleName;

@optional

在OC里,一个类所在文件被引用时,系统会调用其+(void)load函数,当RCTAlertManager所在文件被引用时,系统调用load 函数,函数里简单的调用RCTRegisterModule(self) 把自己注册到一个全局数组RCTModuleClasses,这样系统中导出的类都会自动注册到这个全局变量数组里(so easy)。

在JS中有一个BatchedBridge用来和Native通讯,在Natvie中也有一个RCTBatchedBridge类,它封装了JSContext即JS引擎
在RCTBatchedBridge start 函数中,做了5件事

> 1. jsbundle文件的下载或本地读取(异步)
> 2. 初始化导出给JS用的Native模块
 >3. 初始化JS引擎
 >4. 生成配置表,并注入到JS引擎中,
 >5. 执行jsbundle文件。

//伪代码

  • (void)start
    {
    //1 jsbundle文件的下载或地点读取(异步)
    NSData *sourceCode;
    [self loadSource:^(NSError *error, NSData *source) {sourceCode =
    source}];

//2 早先化导出给JS用的Native模块
[self initModules];

//3 初始化JS引擎
[self setUpExecutor];

//4 生成Native模块配置表 把配置表注入到JS引擎中
NSSting* config = [self moduleConfig];
[self injectJSONConfiguration:config onComplete:^(NSError *error) {});

//5 最终执行jsbundle
[self executeSourceCode:sourceCode];

}

现在我们最关心第二步初始化Native模块 initModules 和moduleConfig 到底是什么

//伪代码

  • (void)initModules
    {
    亚洲必赢官网 ,//遍历上节讲到的RCTGetModuleClasses全局数组,用导出模块的类仍旧实例创设RCTModuleData
    for (Class moduleClass in RCTGetModuleClasses())
    {
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);

    //这里一个很有意思的地点,即使导出的类或其任何父类重写了init方法,或者类中有setBridge方法
    //则React
    Native即使开发者期望以此导出模块在Bridge第三回开始化时实例化,否则怎么着,我们想想
    if ([moduleClass instanceMethodForSelector:@selector(init)] !=
    objectInitMethod ||
    [moduleClass instancesRespondToSelector:setBridgeSelector]) {
    module = [moduleClass new];
    }

    // 创建RCTModuleData
    RCTModuleData *moduleData;
    if (module) {
    moduleData = [[RCTModuleData alloc]
    initWithModuleInstance:module];
    } else {
    moduleData = [[RCTModuleData alloc]
    initWithModuleClass:moduleClass bridge:self];
    }

    //保存到数组中,数组index就是其一模块的目录
    [_moduleDataByID addObject:moduleData];
    }
    }

initModules里根据是否重写init或添加了setBridge来决定是不是要马上实例化RCTGetModuleClasses里的导出类,然后用实例或类创建RCTModuleData,缓存到本地,以便JS调用时查询。


再来看第四步导出的  NSSting* config = [self moduleConfig] 是什么内容

{"remoteModuleConfig":
[["RCTStatusBarManager"],
["RCTSourceCode"],
["RCTAlertManager"],
["RCTExceptionsManager"],
["RCTDevMenu"],
["RCTKeyboardObserver"],
["RCTAsyncLocalStorage"],
.
.
.
]}  

它仅仅是一个类名数组。

####注入配置表到JS引擎,并创建对应的JS对象

生产配置表后,通过下面的方法把这个类名数组注入到JSContext,赋值给JS全局变量__fbBatchedBridgeConfig


[_javaScriptExecutor injectJSONText:configJSON
              asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
                         callback:onComplete];


在JS端,当有人引用了BatchedBridge `var BatchedBridge=require('BatchedBridge'); `,其工厂函数会通过 __fbBatchedBridgeConfig配置表创建MessageQueue的实例BatchedBridge

var MessageQueue=require('MessageQueue');

var BatchedBridge=new MessageQueue(
    __fbBatchedBridgeConfig.remoteModuleConfig,
    __fbBatchedBridgeConfig.localModulesConfig);

我们看看MessageQueue的构造函数,构造函数里为每个导出类创建了一个对应的module对象,因为此时config里只有一个导出类的名字,所以这里只为这个对象增加了一个成员变量 module.moduleID,并把module保存到this.RemoteModules数组里

_genModule(config, moduleID) {
let module = {};
if (!constants && !methods && !asyncMethods) {
module.moduleID = moduleID;
}
this.RemoteModules[moduleName] = module;
}

接着我们顺藤摸瓜看看那里使用的BatchedBridge.RemoteModules

#### NativeModules模块

NativeModules在初始化时,用BatchedBridge.RemoteModules保存的类名列表,为每个JS对象增加了函数等属性

__d(‘NativeModules’,function(global, require, module, exports) { ‘use
strict’;
var RemoteModules=require(‘BatchedBridge’).RemoteModules;
var NativeModules={};

 //遍历NativeModules中导出类名
Object.keys(RemoteModules).forEach(function(moduleName){

   //把类名定义为NativeModules的一个属性,比如AlertManager类,定义只有就可以用NativeModules.AlertManager 访问
    Object.defineProperty(NativeModules,moduleName,{

        //这个属性(AlertManager)是可以遍历的,当然属性也是个对象里面有属性和函数
        enumerable:true,

        //属性都有get和set函数,当调用访问这个属性时,会调用get函数  NativeModules.AlertManager         
        get:function(){

            var module=RemoteModules[moduleName];
            if(module&&typeof module.moduleID==='number'&&global.nativeRequireModuleConfig){

                //调用Native提供的全局函数nativeRequireModuleConfig查询AlertManager 导出的常量和函数
                var json=global.nativeRequireModuleConfig(moduleName);                                   
                module=config&&BatchedBridge.processModuleConfig(JSON.parse(json),module.moduleID);
                RemoteModules[moduleName]=module;
            }
            return module;
        }
    });
});
module.exports=NativeModules;

});

React Native 把所有的Native导出类定义在一个NativeModules模块里,所以使用Natvie接口时也可以直接这样拿到对应的JS对象
`var RCTAlertManager=require('NativeModules').AlertManager;`

代码里我加了注释
思考一个问题,为什么React Natvie搞的那么麻烦,为什么不在上一个步骤里(MessageQueue的构造函数)里就创建出完整的JS对象。

没错,就是模块的懒加载,虽然Native导出了Alert接口,在JS引擎初始化后,JS里只存在一个名字为AlertManager的空对象


![906C7A90-0A85-4FD6-B433-39CE041D4445.png](http://upload-images.jianshu.io/upload_images/1393048-05a36b4e5fec6836.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

当调用了RCTAlertManager.alertWithArgs({message:'JS->Native Call',buttons:[{k1:'button1'}时,才会调用AlertManager 的get函数到Native里查询导出的常量和函数,并定义到AlertManager中。


![7RGT1@Z}N19_9{KQ~P_SDFE.jpg](http://upload-images.jianshu.io/upload_images/1393048-82920ec46d63fa04.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

####Native模块对应的JS对象中函数是如何调用到Native

RCTAlertManager.alertWithArgs 这个函数是如何调用到Native里的呢,在BatchedBridge.processModuleConfig函数中,用_genMethod创建了一个闭包fn为每个函数赋值,这个函数转调self.__nativeCall(module, method, args, onFail, onSucc); 我们调用RCTAlertManager.alertWithArgs函数,其实都是调用的这个fn闭包。

_genMethod(module, method, type) {
  fn = function(...args) {
    return self.__nativeCall(module, method, args, onFail, onSucc);
  };
return fn;

}

__nativeCall,好熟悉的名字,

__nativeCall(module, method, params, onFail, onSucc) {

this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);

global.nativeFlushQueueImmediate(this._queue);
this._queue = [[],[],[]];
this._lastFlush = now;

}

global.nativeFlushQueueImmediate 是Native提供的接口,__nativeCall把需要调用的module,method,params都塞到队列里,然后传递到Native,

我们在回到Native 找到上文提到的两个关键接口,Native模块查询接口:global.nativeRequireModuleConfig和调用接口global.nativeFlushQueueImmediate,他们是在JS引擎(JSContext)初始化时,定义到全局变量的。

//RCTContextExecutor setUP
//简化过的代码

  • (void)setUp
    {

    self->_context.context[@”nativeRequireModuleConfig”] =
    ^NSString *(NSString *moduleName) {
    NSArray *config = [weakBridge configForModuleName:moduleName];
    return RCTJSONStringify(config, NULL);
    };

    self->_context.context[@”nativeFlushQueueImmediate”] =
    ^(NSArray<NSArray *> *calls){
    [weakBridge handleBuffer:calls batchEnded:NO];
    };

    }

[weakBridge handleBuffer:calls batchEnded:NO]; 经过一系列传递,调用到_handleRequestNumber 中,用moduleID找到RCTModuleData,再用methodID 找到`id<RCTBridgeMethod> method` 然后在moduleData.instance实例中执行
  • (BOOL)_handleRequestNumber:(NSUInteger)i
    moduleID:(NSUInteger)moduleID
    methodID:(NSUInteger)methodID
    params:(NSArray *)params
    {

    RCTModuleData *moduleData = _moduleDataByID[moduleID];
    id<RCTBridgeMethod> method = moduleData.methods[methodID];

    [method invokeWithBridge:self module:moduleData.instance
    arguments:params];
    }

这里有必要再强调一次moduleData.instance 这个地方。
  • (id<RCTBridgeModule>)instance
    {
    if (!_instance) {
    _instance = [_moduleClass new];

    }
    return _instance;
    }

还记的前面BatchedBridge 初始化时的initModules吗

//这里一个很有意思的地方,如果导出的类或其任何父类重写了init方法,或者类中有setBridge方法
//则React Native假设开发者期望这个导出模块在Bridge第一次初始化时实例化,否则怎么样,大家想想
if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod ||
    [moduleClass instancesRespondToSelector:setBridgeSelector]) {
  module = [moduleClass new];
}

否则就是在用户真正调用时,在moduleData.instance里实例化,React Native已经懒到骨髓了。

RCTModuleData中每个函数的封装 RCTModuleMethod里还有一个优化点,JS传递到Native的参数需要进行响应的转换,RCTModuleMethod在调用函数只前,先预解析一下,创建每个参数转换的block,缓存起来,这样调用时,就直接使用对应函数指针进行参数转换了,大要详细了解可以看 - (void)processMethodSignature函数。

####回调函数

前面我们为了直观,忽略了回调函数,alertWithArgs的第二个参数是一个JS回调函数,用来指示用户点击了哪个button,并打印出一行日志。

RCTAlertManager.alertWithArgs({message:’JS->Native
Call’,buttons:[{k1:’button1′},{k2:’button1′}]},function(id,v)
{console.log(‘RCTAlertManager.alertWithArgs() id:’ + id +’ v:’ + v)});

回调函数的调用和直接从Native调用JS是差不多的,再回头看看__nativeCall 函数我们忽略的部分

__nativeCall(module, method, params, onFail, onSucc) {

//Native接口最多支持两个回调
if (onFail || onSucc) {
  onFail && params.push(this._callbackID);
  this._callbacks[this._callbackID++] = onFail;
  onSucc && params.push(this._callbackID);
  this._callbacks[this._callbackID++] = onSucc;
}

this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);

global.nativeFlushQueueImmediate(this._queue);
this._queue = [[],[],[]];
this._lastFlush = now;

}

可以看到把onFail,onSucc两个函数类型转化为两个数字ID插入到参数列表后面,并把函数函数缓存起来。
从Native调用过来也比较简单了,传递过callbackID到JS,就可以执行到回调函数。

JS传递的参数仅仅是个整形ID,Native如何知道这个ID就是个回调函数呢?

答案是和其他参数一样通过Native的函数签名,如果发现对应的参数是个block,则从JS传递过来的ID就是对应的回调ID,把其转化为RCTResponseSenderBlock的闭包。

`RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args
                  callback:(RCTResponseSenderBlock)callback)`



到此为止,我们已经把整个JS->Natvie的流程都走通了,
梳理一下整个流程。


![调用图.png](http://upload-images.jianshu.io/upload_images/1393048-54a66f3ac2bece25.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
总结一下


>1. Native初始化时, Native生成要导出模块的名字列表(注意注意注意),仅仅是模块(类)名字列表, ModuleConfig
2. 在React Native 的JS引擎初始化完成后,向JSContext注入ModuleConfig,赋值到JS全局变量 __fbBatchedBridgeConfig
3. 还记得那个N->JS大使---JS对象BatchedBridge吗,BatchedBridge创建的时候会用__fbBatchedBridgeConfig变量里Native模块名字列表定义一个同名的JS对象,但是是一个没有任何方法的空对象,只增加了一个获取方法数组的get函数。此时初始化的操作已完成。
4. 很久很久之后,有人用RCTAlertManager.alertWithArgs 调用了Native的代码,咳咳,这人是我,此时JS去获取RCTAlertManager方法列表时,发现是空的,就调用Native提供的查询函数nativeRequireModuleConfig 获取RCTAlertManager对象的详细的导出信息(方法列表),并定义成同名的JS函数,此函数转调到OC的实现
5. 此时RCTAlertManager对应的JS对象才定义完整,JS找到了alertWithArgs函数,每个对应的JS函数都是一个封装了调用__nativeCall的闭包,JS通过此函数转发到Native

可以看出,Native导出的配置表并不是在一开始就完整的注入JS并定义对应的JS对象,而是仅仅注入了一个模块名字,当运行期间有人调用的时候,才再从Native查询调用模块的详细配置表,这种懒加载机制缓解了一个大型的app导出的Api很多,全部导入JS导致初始化时内存占用过大的问题。


##消息循环
###线程问题

React Native为JS引擎创建了一个独立的线程

//RCTJavaScriptContext

  • (instancetype)init
    {
    NSThread *javaScriptThread = [[NSThread alloc]
    initWithTarget:[self class]
    selector:@selector(runRunLoopThread)
    object:nil];
    javaScriptThread.name
    = @”com.facebook.React.JavaScript”;
    [javaScriptThread start];
    return [self initWithJavaScriptThread:javaScriptThread
    context:nil];
    }

所有的JS代码都运行在"com.facebook.React.JavaScript"后台线程中,所有的操作都是异步,不会卡死主线程UI。并且JS调用到Native中的接口中有强制的线程检查,如果不是在React线程中则抛出异常。
这样有一个问题,从JS调用Native中的代码是执行在这个后台线程中,我们上文的RCTAlertManager.alertWithArgs明显是个操作UI的接口,执行在后台线程会crash,在导出RCTAlertManager时,通过实现方法- (dispatch_queue_t)methodQueue,原生模块可以指定自己想在哪个队列中被执行
  • (dispatch_queue_t)methodQueue
    {
    return dispatch_get_main_queue();
    }

类似的,如果一个操作需要花费很长时间,原生模块不应该阻塞住,而是应当声明一个用于执行操作的独立队列。举个例子,RCTAsyncLocalStorage模块创建了自己的一个queue,这样它在做一些较慢的磁盘操作的时候就不会阻塞住React本身的消息队列:
  • (dispatch_queue_t)methodQueue
    {
    return
    dispatch_queue_create(“com.facebook.React.AsyncLocalStorageQueue”,
    DISPATCH_QUEUE_SERIAL);
    }

###React的消息循环

这是典型的事件驱动机制和消息循环,当无任何事件时,runloop(消息循环)处于睡眠状态,当有事件时,比如用户操作,定时器到时,网络事件等等,触发一此消息循环,最总表现为UI的改变或数据的变化。



![消息循环.png](http://upload-images.jianshu.io/upload_images/1393048-fa97c3d86046dcb1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这里要注意的是,以上我们讲到从 JS调用到Native是调用global.nativeFlushQueueImmediate 立即执行的。React消息循环这里做了一次缓存,比如用户点击一次,所有触发的JS->N的调用都缓存到MessageQueue里,当N->JS调用完成时,以返回值的形式返回MessageQueue, 减少了N->JS的交互次数。缓存时间是 `MIN_TIME_BETWEEN_FLUSHES_MS = 5`毫秒内的调用。

__nativeCall(module, method, params, onFail, onSucc) {

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._lastFlush = now;
}

}

在`MIN_TIME_BETWEEN_FLUSHES_MS`时间内的调用都会缓存到this._queue,以返回值的形式返回给Native,形成一次消息循环

callFunctionReturnFlushedQueue(module, method, args) {
guard(() => {
this.__callFunction(module, method, args);
this.__callImmediates();
});

return this.flushedQueue();

}

flushedQueue() {
this.__callImmediates();

let queue = this._queue;
this._queue = [[],[],[]];
return queue[0].length ? queue : null;

}

第一篇的内容就是这些,想看懂容易,想尽量简洁明了的总结成文字真是一件很不容易的事情,特别是这里很多JS的代码。有问题大家留言指正。下一篇将介绍ReactNative的渲染原理。
http://www.jianshu.com/p/269b21958030
网站地图xml地图