【美高梅开户网址】组件早先化与通讯分析,通信及音信循环代码剖析

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

2015/12/30 · JavaScript
· React Native

原稿出处: Tmall前端团队(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地图