React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yanbober/article/details/53157456

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

1 背景

有了前面《React Native Android 从学车到补胎和成功发车经历》《React Native Android Gradle 编译流程浅析》两篇文章的学习我们 React Native 已经能够基本接入处理一些事情了,那接下来的事情就是渐渐理解 RN 框架的一些东西,以便裁剪和对 RN 有个更深入的认识,所以本篇总结了我这段时间阅读源码的一些感触,主要总结了 React Native 启动流程、JS 调用 Java 流程、Java 调用 JS 流程。

涉及到源码分析了,所以有必要先交代下相关源码版本,以便引来不必要疑惑,如下:

"dependencies": {
  "react": "15.3.2",
  "react-native": "0.37.0"
}

首先通过前面的踩坑经历和编译流程浅析(编译流程已经暴露很多细节)我们能意识到 React Native 的大致框架流程应该是如下这样的:

这里写图片描述

也就是说其实需要我们编写代码是 Java 端(少)和 JS 端(多),其他的基本不变的,作为桥梁的核心是 C/C++ 来处理的,同时 JS(JSX)代码又是通过 Virtual DOM 来进行虚拟适应的,所以才有了 React Native 官方放话的 Learn once, do anywhere. 之说。下面我们就来解析下这个神奇的 React Native Android 框架吧。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

2 RN 启动流程框架浅析

还记得我们在《React Native Android 从学车到补胎和成功发车经历》中是怎么集成的 RN 吗?集成 RN 无非就是通过继承 ReactActivity 或者自己通过 ReactRootView 进行处理,但是实质都是触发了 ReactRootView 的 startReactApplication 方法,所以我们整个启动流程的核心入口就是这玩意;下面为了一致,我们直接从 ReactActivityDelegate 类的 onCreate 方法进行启动分析(分析源码本来就比较枯燥,坚持看下去收获是巨大的),如下:

public class ReactActivityDelegate {
  protected void onCreate(Bundle savedInstanceState) {
    //权限弹窗判断
    if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
      ......
    }
    //启动流程一定会执行的,mMainComponentName为我们设置的,与JS边保持一致
    if (mMainComponentName != null) {
      loadApp(mMainComponentName);
    }
    ......
  }

  protected void loadApp(String appKey) {
    if (mReactRootView != null) {
      throw new IllegalStateException("Cannot loadApp while app is already running.");
    }
    //创建一个ReactRootView,实质是一个FrameLayout
    mReactRootView = createRootView();
    //重磅启动流程核心方法!!!!!!
    mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      appKey,
      getLaunchOptions());
    //把View设置进Activity
    getPlainActivity().setContentView(mReactRootView);
  }
}

可以看见,ReactActivityDelegate 只是一个抽出来的封装,上面的实质就是 new 了一个 ReactRootView(实质是 Android 的 FrameLayout),接着调用 ReactRootView 的 startReactApplication 方法,完事就像常规 Android 代码一样直接通过 Activity 的 setContentView 方法把 View 设置进去。所以可以看出来,RN 的神秘之处一定在于 ReactRootView 中,Activity 对于 RN 来说只是为了让 RN 依附符合 Android 的框架而已,所以说,说白了 RN 依旧是标准 Android,因此在我们集成开发中我们可以选择整个界面(包含多级跳转)都用 React Native 实现,或者一个 Android 现有界面中部分采用 React Native 实现,因为这货就是一个 View,爱咋咋地,具体如下所示:

这里写图片描述

既然明白了 RN 就是个 View,那就接着看看 ReactRootView 呗,如下:

/**
 React Native 的 Root View,负责监听标准 Android 的 View 相关各种东东及事件分发和子 View 渲染等。
 */
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {
  public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName) {
    startReactApplication(reactInstanceManager, moduleName, null);
  }

  public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle launchOptions) {
    UiThreadUtil.assertOnUiThread();
    ......
    //标记判断,初始化会走进来的 
    if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
         mReactInstanceManager.createReactContextInBackground();
    }

    // We need to wait for the initial onMeasure, if this view has not yet been measured, we set which
    // will make this view startReactApplication itself to instance manager once onMeasure is called.
    if (mWasMeasured) {
      attachToReactInstanceManager();
    }
  }
}

可以看见,ReactRootView 果然是个牛逼的类,我也不多解释了,大段的英文注释已经交代很清楚用途和地位了,我们直接看上面代码的 startReactApplication 方法吧,可以看见他又调用了一个三个参数的同名方法,具体这三个参数来历如下(也是我们自己集成 RN 时手动 builder 模式创建的):

1. reactInstanceManager: 大内总管接口类,提供一个构造者模式的初始化 Builder,实现类是 XReactInstanceManagerImpl,这类也是我们在集成 RN 时 new ReactRootView 的之前自己创建的。
2. moduleName: 与 JS 代码约定的 String 类型识别 name,JS 端通过 AppRegistry.registerComponent 方法设置这个 name,Java 端重写基类的 getMainComponentName 方法设置这个 name,这样两边入口就对上了。
3. launchOptions: 这里默认是 null 的,如果自己不继承 ReactActivity 而自己实现的话可以通过这个参数在 startActivity 时传入一些参数到 JS 代码,用来依据参数初始化 JS 端代码。

这些参数都初始化传递好了以后,可以看见接着调用了 mReactInstanceManager 的 createReactContextInBackground 方法,mReactInstanceManager 就是上面说的第一个参数,实质是通过一个构造者模式创建的,实现类是 XReactInstanceManagerImpl,所以我们直接跳到 XReactInstanceManagerImpl 的 createReactContextInBackground 方法看看,如下:

  public void createReactContextInBackground() {
    ......
    recreateReactContextInBackgroundInner();
  }

  private void recreateReactContextInBackgroundInner() {
    UiThreadUtil.assertOnUiThread();

    if (mUseDeveloperSupport && mJSMainModuleName != null) {
        //如果是 dev 模式,BuildConfig.DEBUG=true就走这里,在线更新bundle,手机晃动出现调试菜单等等。
        //这个路线属于RN调试流程原理,后面再写文章分析,这里我们抓住主线分析
      ......
      return;
    }
    //非调试模式,即BuildConfig.DEBUG=false时执行
    recreateReactContextInBackgroundFromBundleLoader();
  }

  private void recreateReactContextInBackgroundFromBundleLoader() {
    //厉害了,word哥,在后台创建ReactContext,两个参数是重点。
    //mJSCConfig.getConfigMap()默认是一个WritableNativeMap,在前面通过构造模式构造时通过Builder类的set方法设置。
    //mJSBundleLoader是在前面通过构造模式构造时通过Builder类的多个setXXX方法均可设置的,
    //最终在Builder中build方法进行判断处理,你可以自定义Loader,或者按照build方法规则即可,
    //默认是JSBundleLoader.createAssetLoader静态方法返回的JSBundleLoader抽象类的实现类。
    //自定义热更新时setJSBundleFile方法参数就是巧妙的利用这里是走JSBundleLoader.createAssetLoader还是JSBundleLoader.createFileLoader!!!!!!
    recreateReactContextInBackground(
        new JSCJavaScriptExecutor.Factory(mJSCConfig.getConfigMap()),
        mBundleLoader);
  }

  private void recreateReactContextInBackground(
      JavaScriptExecutor.Factory jsExecutorFactory,
      JSBundleLoader jsBundleLoader) {
    UiThreadUtil.assertOnUiThread();
    //封装一把,把两个参数封装成ReactContextInitParams对象
    ReactContextInitParams initParams =
        new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
    if (mReactContextInitAsyncTask == null) {
        //初始化进来一定会走啦,这货不就是创建一个AsyncTask,然后执行,同时传递封装的参数initParams给task。
      // No background task to create react context is currently running, create and execute one.
      mReactContextInitAsyncTask = new ReactContextInitAsyncTask();
      mReactContextInitAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, initParams);
    } else {
      // Background task is currently running, queue up most recent init params to recreate context
      // once task completes.
      mPendingReactContextInitParams = initParams;
    }
  }

通过上面注释可以看见,我们《React Native Android 从学车到补胎和成功发车经历 #3-5 RN 集成后热更新核心思路》的热更新面纱也是从这个地方开始揭晓的,ReactInstanceManager 的 setJSBundleFile 如下:

    /**
     * Path to the JS bundle file to be loaded from the file system.
     *
     * Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle"}
     */
    public Builder setJSBundleFile(String jsBundleFile) {
      if (jsBundleFile.startsWith("assets://")) {
        mJSBundleAssetUrl = jsBundleFile;
        mJSBundleLoader = null;
        return this;
      }
      return setJSBundleLoader(JSBundleLoader.createFileLoader(jsBundleFile));
    }

我们先记住这个提示,后面再边分析主加载流程边插入介绍热更新的原理,所以我们还是把思路先回到 XReactInstanceManagerImpl 内部类的 ReactContextInitAsyncTask 上,如下:

  private final class ReactContextInitAsyncTask extends
      AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> {
    ......
    @Override
    protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
      ......
      try {
        //异步执行的重量级核心方法createReactContext,创建ReactContext
        JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
        return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
      } catch (Exception e) {
        // Pass exception to onPostExecute() so it can be handled on the main thread
        return Result.of(e);
      }
    }

    @Override
    protected void onPostExecute(Result<ReactApplicationContext> result) {
      try {
        //回到主线程执行的重量级核心方法setupReactContext,设置ReactContext相关
        setupReactContext(result.get());
      } catch (Exception e) {
        mDevSupportManager.handleException(e);
      } finally {
        mReactContextInitAsyncTask = null;
      }
        ......
    }
    ......
  }

可以看见,这就是典型的 AsyncTask 用法,我们先关注 doInBackground 方法,onPostExecute 方法等会回头再看;doInBackground 中首先把上面封装的 ReactContextInitParams 对象里 JavaScriptExecutor.Factory 工厂对象拿到,接着调用了工厂类的 create 方法创建 JavaScriptExecutor 抽象类的实现类 JSCJavaScriptExecutor 对象(因为上面分析 recreateReactContextInBackground 方法时第一个参数传入的是 new JSCJavaScriptExecutor.Factory(mJSCConfig.getConfigMap()))。接着往下执行了 createReactContext 方法,两个参数分别是前面封装的 ReactContextInitParams 对象中的 JSCJavaScriptExecutor 实例和 JSBundleLoader.createAssetLoader 静态方法创建的匿名内部类 JSBundleLoader 对象(热更新的话可能是另一个 Loader,参见前面分析);createReactContext 方法如下(有点长,但是句句核心啊):

  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    ......
    //这货默认不就是上面刚刚分析的"assets://" + bundleAssetName么
    mSourceUrl = jsBundleLoader.getSourceUrl();
    List<ModuleSpec> moduleSpecs = new ArrayList<>();
    Map<Class, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
    //!!!Js层模块注册表,通过它把所有的JavaScriptModule注册到CatalystInstance。我们自定义的继承JavaScriptModule接口的Java端也是通过他来管理。
    JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();
    //ContextWrapper封装类,其实就是getApplicationContext的封装,用在ReactContext中
    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    //如果是开发模式下ReactApplicationContext中有崩溃就捕获后交给mDevSupportManager处理(出错时弹个红框啥玩意的都是这货捕获的功劳)
    if (mUseDeveloperSupport) {
        //mDevSupportManager实例对象来源于XReactInstanceManagerImpl构造方法中一个工厂方法,实质由useDeveloperSupport决定DevSupportManager是哪个实例。
        //非开发模式情况下mDevSupportManager为DisabledDevSupportManager实例,开发模式下为DevSupportManagerImpl实例。
     reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
    }
    ......
    try {
        //创建CoreModulesPackage(ReactPackage),RN framework的核心Module Package,主要通过createNativeModules、createJSModules和createViewManagers等方法创建本地模块,JS模块及视图组件等。
        //CoreModulesPackage封装了通信、调试等核心类。
      CoreModulesPackage coreModulesPackage =
        new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
        //当我们设置mLazyNativeModulesEnabled=true(默认false)后启动可以得到延迟加载,感觉没啥卵用,没整明白有何用意。
        //拼装来自coreModulesPackage的各种module了,JS的直接add进了jsModulesBuilder映射表、Native的直接保存在了moduleSpecs、reactModuleInfoMap中。
      processPackage(
        coreModulesPackage,
        reactContext,
        moduleSpecs,
        reactModuleInfoMap,
        jsModulesBuilder);
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }
    //加载我们自定义的ReactPackage,譬如自己封装的和MainReactPackage等,mPackages就来源于我们自己定义的;整个过程同上CoreModulesPackage,进行各种拼装module。
    // TODO(6818138): Solve use-case of native/js modules overriding
    for (ReactPackage reactPackage : mPackages) {
      Systrace.beginSection(
          TRACE_TAG_REACT_JAVA_BRIDGE,
          "createAndProcessCustomReactPackage");
      try {
        processPackage(
          reactPackage,
          reactContext,
          moduleSpecs,
          reactModuleInfoMap,
          jsModulesBuilder);
      } finally {
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    }
    ......
    //!!!Java层模块注册表,通过它把所有的NativeModule注册到CatalystInstance。我们自定义的继承NativeModule接口的Java端也是通过他来管理。
    NativeModuleRegistry nativeModuleRegistry;
    try {
        //new一个NativeModuleRegistry,其管理了NativeModule和OnBatchCompleteListener列表(JS调用Java结束时的回掉管理)。
       nativeModuleRegistry = new NativeModuleRegistry(moduleSpecs, reactModuleInfoMap);
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
    }
    //依据外面是否设置mNativeModuleCallExceptionHandler异常捕获实现来决定exceptionHandler是使用外面的还是DevSupportManager。
    NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
        ? mNativeModuleCallExceptionHandler
        : mDevSupportManager;
    //!!!重点创建CatalystInstance的CatalystInstanceImpl实现实例
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
        .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        .setJSModuleRegistry(jsModulesBuilder.build())
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);

    ......
    final CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
    }

    if (mBridgeIdleDebugListener != null) {
      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
    }
    //关联reactContext与catalystInstance
    reactContext.initializeWithInstance(catalystInstance);
    //通过catalystInstance加载js bundle文件
    catalystInstance.runJSBundle();

    return reactContext;
  }

可以发现,上面这段代码做的事情真特么多,不过总的来说 createReactContext() 方法做的都是一些取数据组表放表的过程,核心就是通过 ReactPackage 实现类的 createNativeModules()、createJSModules() 等方法把所有 NativeModule 包装后放入 NativeModuleRegistry 及 JavaScriptModule 包装后放入 JavaScriptModuleRegistry,然后把这两张映射表交给 CatalystInstanceImpl,同时包装创建 ReactContext 对象,然后通过 CatalystInstanceImpl 的 runJSBundle() 方法把 JS bundle 文件的 JS 代码加载进来等待 Task 结束以后调用 JS 入口进行渲染 RN。既然这样就去看看 CatalystInstanceImpl 的 build 方法中调用的 CatalystInstanceImpl 构造方法到底干了哪些鸟事,如下:

public class CatalystInstanceImpl implements CatalystInstance {
    ......
    // C++ parts
  private final HybridData mHybridData;
  private native static HybridData initHybrid();

  private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModuleRegistry jsModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
    //native C++方法,用来初始化JNI相关状态然后返回mHybridData。
    mHybridData = initHybrid();
    //创建ReactNative的三个线程nativeModulesThread和jsThread、uiThread,都是通过Handler来管理的。
    mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
        ReactQueueConfigurationSpec,
        new NativeExceptionHandler());
    mBridgeIdleListeners = new CopyOnWriteArrayList<>();
    mJavaRegistry = registry;
    mJSModuleRegistry = jsModuleRegistry;
    mJSBundleLoader = jsBundleLoader;
    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
    mTraceListener = new JSProfilerTraceListener(this);
    //native C++方法,用来初始化Bridge。
    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mReactQueueConfiguration.getNativeModulesQueueThread(),
      mJavaRegistry.getModuleRegistryHolder(this));
    mMainExecutorToken = getMainExecutorToken();
  }

  private native void initializeBridge(ReactCallback callback,
                                       JavaScriptExecutor jsExecutor,
                                       MessageQueueThread jsQueue,
                                       MessageQueueThread moduleQueue,
                                       ModuleRegistryHolder registryHolder);

    ......
}

刚刚分析 createReactContext() 方法的总结没错,CatalystInstanceImpl 这货就是个封装总管,负责了 Java 层代码到 JNI 封装初始化的任务和 Java 与 JS 调用的 Java 端控制中心。所以我们先看看调用 native initializeBridge 方法时传入的 5 个参数吧,分别如下:
1. callback参数: CatalystInstanceImpl 的内部静态实现类 BridgeCallback,负责相关接口回调回传。
2. jsExecutor参数: 前面分析的 XReactInstanceManagerImpl 中赋值为 JSCJavaScriptExecutor 实例,JSCJavaScriptExecutor 中也有自己的 native initHybrid 的 C++ 方法被初始化时调用,具体在 OnLoad.cpp 的 JSCJavaScriptExecutorHolder 类中。
3. jsQueue参数: 来自于 mReactQueueConfiguration.getJSQueueThread(),mReactQueueConfiguration就是 CatalystInstanceImpl 中创建的 ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler()); 第一个参数来自于 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,实质为包装相关线程名字、类型等,然后通过 ReactQueueConfigurationImpl 的 create 创建对应线程的 Handler,这里就是名字为 js 的后台线程 Handler,第二个参数为异常捕获回调实现。
4. moduleQueue参数: 来自于 mReactQueueConfiguration.getNativeModulesQueueThread(),mReactQueueConfiguration就是 CatalystInstanceImpl 中创建的 ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler()); 第一个参数来自于 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,实质为包装相关线程名字、类型等,然后通过 ReactQueueConfigurationImpl 的 create 创建对应线程的 Handler,这里就是名字为 native_modules 的后台线程 Handler,第二个参数为异常捕获回调实现。
5. registryHolder参数: mJavaRegistry 对象来自于 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,通过 mJavaRegistry.getModuleRegistryHolder(this) 传递一个 Java 层的 ModuleRegistryHolder 实例到同名的 C++ 中,具体在 mJavaRegistry.getModuleRegistryHolder(this) 的返回值处为 return new ModuleRegistryHolder(catalystInstanceImpl, javaModules, cxxModules); 而 ModuleRegistryHolder 的构造方法中调用了 C++ 的 initHybrid(catalystInstanceImpl, javaModules, cxxModules); 方法。

CatalystInstanceImpl 这货会玩,自己在 Java 层直接把持住了 JavaScriptModuleRegistry 映射表,把 NativeModuleRegistry 映射表、BridgeCallback 回调、JSCJavaScriptExecutor、js 队列 MessageQueueThread、native 队列 MessageQueueThread 都通过 JNI 嫁接到了 C++ 中。那我们现在先把目光转移到 CatalystInstanceImpl.cpp 的 initializeBridge 方法上(关于 JNI 的 OnLoad 中初始化注册模块等等就不介绍了),如下:

void CatalystInstanceImpl::initializeBridge(
    jni::alias_ref<ReactCallback::javaobject> callback,
    // This executor is actually a factory holder.
    JavaScriptExecutorHolder* jseh,
    jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
    jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
    ModuleRegistryHolder* mrh) {
    ......
  // Java CatalystInstanceImpl -> C++ CatalystInstanceImpl -> Bridge -> Bridge::Callback
  // --weak--> ReactCallback -> Java CatalystInstanceImpl
    ......
    //instance_为ReactCommon目录下 Instance.h 中类的实例;JNI封装规则就不介绍了,之前写过文章的。
    //第一个参数为JInstanceCallback实现类,父类在cxxreact/Instance.h中。
    //第二个参数为JavaScriptExecutorHolder,实质对应java中JavaScriptExecutor,也就是上面分析java的initializeBridge方法第二个参数JSCJavaScriptExecutor。
    //第三第四个参数都是java线程透传到C++,纯C++的JMessageQueueThread。
    //第五个参数为C++的ModuleRegistryHolder的getModuleRegistry()方法。
  instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback),
                              jseh->getExecutorFactory(),
                              folly::make_unique<JMessageQueueThread>(jsQueue),
                              folly::make_unique<JMessageQueueThread>(moduleQueue),
                              mrh->getModuleRegistry());
}

到此 CatalystInstance 的实例 CatalystInstanceImpl 对象也就初始化 OK 了,同时通过 initializeBridge 建立了 Bridge 连接。关于这个 Bridge 在RN 中是通过 libjsc.so 中 JSObjectRef.h 的 JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL); 来关联的,这样就可以在 Native 设置 JS 执行,反之同理。

由于这一小节我们只讨论 RN 的加载启动流程,所以 initializeBridge 的具体实现我们下面分析互相通信交互时再仔细分析,故我们先把思路还是回到 XReactInstanceManagerImpl 中 createReactContext 方法的 reactContext.initializeWithInstance(catalystInstance); 一行,可以看见,这行代码意思就是将刚刚初始化的 catalystInstance 传递给全局唯一的 reactContext 对象,同时在 reactContext 中通过 catalystInstance 拿到 JS、Native、UI 几个 Thread 的引用,方便快速访问使用这些对象。接着调用了 catalystInstance.runJSBundle(); 方法,这个方法实现如下:

  @Override
  public void runJSBundle() {
    ......
    mJSBundleHasLoaded = true;
    //mJSBundleLoader就是前面分析的依据不同设置决定是JSBundleLoader的createAssetLoader还是createFileLoader等静态方法的匿名实现类。
    // incrementPendingJSCalls();
    mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
    ......
  }

通过注释我们假设 Loader 是默认的,也即 JSBundleLoader 类的如下方法:

  public static JSBundleLoader createAssetLoader(
      final Context context,
      final String assetUrl) {
    return new JSBundleLoader() {
      @Override
      public void loadScript(CatalystInstanceImpl instance) {
        instance.loadScriptFromAssets(context.getAssets(), assetUrl);
      }

      @Override
      public String getSourceUrl() {
        return assetUrl;
      }
    };
  }

可以看见,它实质又调用了 CatalystInstanceImpl 的 loadScriptFromAssets 方法,我们继续跟踪 CatalystInstanceImpl 的这个方法吧,如下:

native void loadScriptFromAssets(AssetManager assetManager, String assetURL);

loadScriptFromAssets 既然是一个 native 方法,我们去 CatalystInstanceImpl.cpp 看下这个方法的实现,如下:

void CatalystInstanceImpl::loadScriptFromAssets(jobject assetManager,
                                                const std::string& assetURL) {
  const int kAssetsLength = 9;  // strlen("assets://");
  //获取source路径名,不计前缀,这里默认就是index.android.bundle
  auto sourceURL = assetURL.substr(kAssetsLength);
    //assetManager是Java传递的AssetManager。
    //extractAssetManager是JSLoader.cpp中通过系统动态链接库android/asset_manager_jni.h的AAssetManager_fromJava方法来获取AAssetManager对象的。
  auto manager = react::extractAssetManager(assetManager);
    //通过JSLoader对象的loadScriptFromAssets方法读文件,得到大字符串script(即index.android.bundle文件的JS内容)。
  auto script = react::loadScriptFromAssets(manager, sourceURL);
    //判断是不是Unbundle,这里不是Unbundle,因为打包命令我们用了react.gradle的默认bundle,没用unbundle命令(感兴趣的自己分析这条路线)。
  if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) {
    instance_->loadUnbundle(
      folly::make_unique<JniJSModulesUnbundle>(manager, sourceURL),
      std::move(script),
      sourceURL);
    return;
  } else {
    //bundle命令打包的,所以走这里。
    //instance_为ReactCommon目录下 Instance.h 中类的实例,前面分析过了。
    instance_->loadScriptFromString(std::move(script), sourceURL);
  }
}

看来还没到头,这货又走到了 ReactCommon 目录下 Instance 实例的 loadScriptFromString 方法去了(由此可以看出来前面 ReactNativeAndroid 目录下的 jni 代码都是 Android 平台特有的封装,直到 ReactCommon 才是通用的),如下:

//string为index.android.bundle内容。
//sourceURL在这里默认为index.android.bundle。
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
                                    std::string sourceURL) {
    //callback_就是initializeBridge传进来的,实质实现是CatalystInstanceImpl的BridgeCallback。
    //说白了就是回传一个状态,要开始搞loadScriptFromString了
  callback_->incrementPendingJSCalls();
  SystraceSection s("reactbridge_xplat_loadScriptFromString",
                    "sourceURL", sourceURL);
    //厉害了,Word哥,年度大戏啊!
    //nativeToJsBridge_也是Instance::initializeBridge方法里初始化的,实现在Common的NativeToJsBridge类里。
  nativeToJsBridge_->loadApplication(nullptr, std::move(string), std::move(sourceURL));
}

妈的,没完没了了,继续跟吧,到 Common 的 NativeToJsBridge.cpp 看看 loadApplication 方法吧,如下:

//unbundle传入的是个空指针。
//startupScript为bundle文件内容。
//startupScript为bundle文件名。
void NativeToJsBridge::loadApplication(
    std::unique_ptr<JSModulesUnbundle> unbundle,
    std::unique_ptr<const JSBigString> startupScript,
    std::string startupScriptSourceURL) {
    //runOnExecutorQueue实质就是获取一个MessageQueueThread,然后在其线程中执行一个task。
  runOnExecutorQueue(
      m_mainExecutorToken,
      [unbundleWrap=folly::makeMoveWrapper(std::move(unbundle)),
       startupScript=folly::makeMoveWrapper(std::move(startupScript)),
       startupScriptSourceURL=std::move(startupScriptSourceURL)]
        (JSExecutor* executor) mutable {

    auto unbundle = unbundleWrap.move();
    if (unbundle) {
      executor->setJSModulesUnbundle(std::move(unbundle));
    }
    //因为我们是bundle命令打包的,所以走这里继续执行!!!
    executor->loadApplicationScript(std::move(*startupScript),
                                    std::move(startupScriptSourceURL));
  });
}

靠靠靠,还不到头,又特么绕到 JSExecutor 的 loadApplicationScript 方法里面去了,继续跟吧(这个 executor 是 runOnExecutorQueue 方法中回传的一个 map 中取的,实质是 OnLoad 中 JSCJavaScriptExecutorHolder 对应,也即 java 中 JSCJavaScriptExecutor,所以 JSExecutor 实例为 JSCExecutor.cpp 中实现),如下:

//script为bundle文件内容,sourceURL为bundle文件名
void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) throw(JSException) {
  SystraceSection s("JSCExecutor::loadApplicationScript",
                    "sourceURL", sourceURL);
    ......
    //把bundle文件和文件名等内容转换成js可以识别的String
  String jsScript = jsStringFromBigString(*script);
  String jsSourceURL(sourceURL.c_str());
    //使用webkit JSC去真正解释执行Javascript了!
  evaluateScript(m_context, jsScript, jsSourceURL);
    //绑定桥,核心是通过getGlobalObject将JS与C++通过webkit JSC bind
  bindBridge();
  flush();
    ......
}

去他大爷的,没完没了了,继续看看 bindBridge() 方法和 flush() 方法,如下:

void JSCExecutor::bindBridge() throw(JSException) {
  ......
  auto global = Object::getGlobalObject(m_context);
  auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
  ......

  auto batchedBridge = batchedBridgeValue.asObject();
  m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();
  m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue").asObject();
  //通过webkit JSC获取MessageQueue.js的flushedQueue
  m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject();
  m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject();
}

void JSCExecutor::flush() {
  SystraceSection s("JSCExecutor::flush");
  //m_flushedQueueJS->callAsFunction({})即调用MessageQueue.js的flushedQueue方法。
  //即把JS端相关通信交互数据通过flushedQueue返回传给callNativeModules。
  callNativeModules(m_flushedQueueJS->callAsFunction({}));
}

void JSCExecutor::callNativeModules(Value&& value) {
  SystraceSection s("JSCExecutor::callNativeModules");
  try {
    //把JS端相关通信数据转为JSON格式字符串数据
    auto calls = value.toJSONString();
    //m_delegate实质为Executor.h中ExecutorDelegate类的实现类JsToNativeBridge对象。
    //故callNativeModules为JsToNativeBridge.cpp中实现的方法,把calls json字符串pase成格式结构。
    m_delegate->callNativeModules(*this, folly::parseJson(calls), true);
  } catch (...) {
    ......
  }
}

卧槽!又绕回到了 JsToNativeBridge.cpp 的 callNativeModules 方法,那就看下吧,如下:

    //executor即为前面的JSCExecutor。
    //calls为被解析OK的JS端JSON通信参数结构。
    //isEndOfBatch通知是否一个批次处理OK了,这里传递了true进来,说明JS文件Loader OK了。
  void callNativeModules(
      JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
      //拿到token
    ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
    //扔到nativeQueue的线程队列去等待执行
    m_nativeQueue->runOnQueue([this, token, calls=std::move(calls), isEndOfBatch] () mutable {
      // An exception anywhere in here stops processing of the batch.  This
      // was the behavior of the Android bridge, and since exception handling
      // terminates the whole bridge, there's not much point in continuing.
      for (auto& call : react::parseMethodCalls(std::move(calls))) {
        //调用Native registry表中的java NativeMethod方法。
        m_registry->callNativeMethod(
          token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
      }
      //一些类似数据库事务操作的机制,用来告诉OK了
      if (isEndOfBatch) {
        m_callback->onBatchComplete();
        m_callback->decrementPendingJSCalls();
      }
    });
  }

终于尼玛明朗了,上面这段调用不就是前面分析的那个回调么,说白了就是 CatalystInstanceImpl.java 中 CatalystInstanceImpl 构造方法中调用 C++ 的 initializeBridge 方法时传入的第一个参数 BridgeCallback 么,也就是说 JS bundle 文件被加载完成以后 JS 端调用 Java 端时会触发 Callback 的 onBatchComplete 方法,这货最终又会触发 OnBatchCompleteListener 接口的 onBatchComplete 方法,这不就把 JS Bundle 文件加载完成以后回调 Java 通知 OK 了么,原来主要的流程是这么回事。为了接下来不迷糊,赶紧先来一把小梳理总结,用图说话,如下:

这里写图片描述

上面这幅图已经囊括了我们上面那些枯燥的启动流程的部分流程分析了,好了,从上图可以知道我们前面贴出来的 AsyncTask 的 onPostExecute 方法还没分析,所以我们把目光再回到 XReactInstanceManagerImpl 的那个 ReactContextInitAsyncTask 中,doInBackground 方法执行完成后返回了 Result 包装的 reactContext,所以我们看下 onPostExecute 方法中调用的核心方法 setupReactContext,如下:

  private void setupReactContext(ReactApplicationContext reactContext) {
    ......
    CatalystInstance catalystInstance =
        Assertions.assertNotNull(reactContext.getCatalystInstance());
    //执行Native Java Module 的 initialize
    catalystInstance.initialize();
    //重置DevSupportManager实现类的reactContext相关
    mDevSupportManager.onNewReactContextCreated(reactContext);
    //内存状态回调设置
    mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
    //置位生命周期
    moveReactContextToCurrentLifecycleState();
    //核心方法!!!!
    for (ReactRootView rootView : mAttachedRootViews) {
      attachMeasuredRootViewToInstance(rootView, catalystInstance);
    }
    ......
  }

到此我们再追一下 mAttachedRootViews 这个列表赋值的地方吧,依旧是这个类的 attachMeasuredRootView(ReactRootView rootView) 方法,然而这个方法唯一被调用的地方在 ReactRootView 的 attachToReactInstanceManager() 中,再次发现 attachToReactInstanceManager 又是在 ReactRootView 已经 measure 的情况下才会触发,所以也就是说 mAttachedRootViews 中保存的都是 ReactRootView。那我们继续回到 XReactInstanceManagerImpl 中 setupReactContext 方法的 attachMeasuredRootViewToInstance(rootView, catalystInstance); 里看看,如下:

  private void attachMeasuredRootViewToInstance(
      ReactRootView rootView,
      CatalystInstance catalystInstance) {
    ......
    //彻底reset ReactRootView中的UI
    // Reset view content as it's going to be populated by the application content from JS
    rootView.removeAllViews();
    rootView.setId(View.NO_ID);
    //通过UIManagerModule设置根布局为ReactRootView
    UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
    int rootTag = uiManagerModule.addMeasuredRootView(rootView);
    //设置相关tag
    rootView.setRootViewTag(rootTag);
    //把Java端启动传递的launchOptions包装成JS用的类型
    @Nullable Bundle launchOptions = rootView.getLaunchOptions();
    WritableMap initialProps = Arguments.makeNativeMap(launchOptions);
    //获取我们startReactApplication设置的JS端入口name,继承ReactActivity的话值为getMainComponentName()设置的
    String jsAppModuleName = rootView.getJSModuleName();
    //包装相关参数,rootTag告知JS端Native端的ReactRootView是哪个
    WritableNativeMap appParams = new WritableNativeMap();
    appParams.putDouble("rootTag", rootTag);
    appParams.putMap("initialProps", initialProps);
    //核心大招!!!!!React Native真正的启动流程入口是被Java端在这里拉起来的!!!!!!!!
    catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
    ......
  }

坚持一下,分析源码就是个苦逼的过程,坚持下来就好了,马上看到希望了;我们知道 AppRegistry.class 是 JS 端暴露给 Java 端的接口方法,所以 catalystInstance.getJSModule(AppRegistry.class) 实质就桥接到 JS 端代码去了,那就去看看 AppRegistry.js 的代码吧,如下:

//JS端对应代码,注意这个变量上面的英文已经交代很详细啦
var AppRegistry = {
    ......
    //我们JS端自己在index.android.js文件中调用的入口就是:
    //AppRegistry.registerComponent('TestRN', () => TestRN);
  registerComponent: function(appKey: string, getComponentFunc: ComponentProvider): string {
    runnables[appKey] = {
      run: (appParameters) =>
        renderApplication(getComponentFunc(), appParameters.initialProps, appParameters.rootTag)
    };
    return appKey;
  },
    ......
    //上面java端 AppRegistry 调用的 JS 端就是这个方法,索引到我们设置的appkey=TestRN字符串的JS入口
  runApplication: function(appKey: string, appParameters: any): void {
    ......
    runnables[appKey].run(appParameters);
  },
    ......
};

真他妈不容易啊,总算到头了,原来 React Native 是这么被启动起来的。现在回过头来看发现其实主启动流程也就那么回事,还以为很神秘嘻嘻的,现在总算被揭开了。总结一下吧,如下图所示即为整个 React Native 加载主流成的主要情况:

这里写图片描述

到这里 React Native 的启动流程就分析完了,不过,我猜你看到这里的时候一定会骂我,因为我知道上面的主流程中你会有很多疑惑,这也是我写这篇阅读 RN 源码总结最纠结的地方,因为想尽可能的将主加载流程和通信方式分开来分析,以便做到模块化理解,但是后来发现关联性又很强,揉一起分析更乱套,所以就有了这么一篇很长的文章,前面就当是主流程综述概要分析,细节在下面通信方式分析时会继续提及浅析,所以建议带着上面的疑惑继续向下看完这篇文章再回到 Part 2 RN 启动流程框架浅析 这一部分来看一遍,这样你的疑惑就全部揭开了。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

3 RN Java 调用 JS 端框架浅析

这是一个悲伤的故事,看源码没有伴,RN 接入也在自己一个人搞,所以搞起来总是挺慢,好在一直在坚持,源码也断断续续在工作之余看了一个多星期,这篇文章也占用了我一个美好的周末时光,有种说不出来的感觉,唉,不扯了,我们现在来看看 RN 中 Java 是如何调用 JS 代码的。

首先,通过上面加载流程或者以前我们自定义 Java & JS 交互模块的经历我们知道 JS 端代码模块对应的 Java 端都是继承 JavaScriptModule 来实现的(可以看上面 reactPackage.createJSModules() 方法,返回的是 JS 端给 Java 端约定好的 JS 模块 Java 实现);要说 Java 端如何调用 JS 端代码就得有个例子,我们就拿上面启动流程中最后 CatalystInstanceImpl.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); 拉起 JS 端 index.android.js 的 JS 组件入口来分析,这就是一个典型的 Java 端调用 JS 端代码的例子,首先我们可以知道 AppRegistry.java 是继承 JavaScriptModule 的,如下:

public interface AppRegistry extends JavaScriptModule {
  void runApplication(String appKey, WritableMap appParameters);
  void unmountApplicationComponentAtRootTag(int rootNodeTag);
  void startHeadlessTask(int taskId, String taskKey, WritableMap data);
}

然后 AppRegistry.java 是在 CoreModulesPackage 的 createJSModules() 方法中被添加入列表的,CoreModulesPackage 又是在主启动流程的 processPackage() 方法中被包装后加入 JavaScriptModuleRegistry 映射表的,JavaScriptModuleRegistry 映射表又被 Java 层的 CatalystInstanceImpl 接管。所以 Java 调用 JS 方法都是通过 CatalystInstanceImpl.getJSModule(class).methodXXX() 来执行的(我们自己模块调用的话是通过 ReactContext.getJSModule(),因为 ReactContext 在主启动流程中持有了 CatalystInstanceImpl 实例,所以 CatalystInstanceImpl 是不直接对外的),那我们就沿着这条线去观摩一把,如下 CatalystInstanceImpl.java 的 getJSModule 方法:

  @Override
  public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
      //mMainExecutorToken来自于native C++代码
    return getJSModule(mMainExecutorToken, jsInterface);
  }

  @Override
  public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
      //mJSModuleRegistry就是启动流程中processPackage()方法加进去交给CatalystInstanceImpl托管的JS代码映射表
    return Assertions.assertNotNull(mJSModuleRegistry)
        .getJavaScriptModule(this, executorToken, jsInterface);
  }

接着去 JavaScriptModuleRegistry 映射表中看看 getJavaScriptModule() 方法,如下:

  public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
    CatalystInstance instance,
    ExecutorToken executorToken,
    Class<T> moduleInterface) {
    //module加载的缓存,加载过一次且缓存存在就直接从缓存取
    ......

    //获取JavaScriptModule模块的方式,以AppRegistry模块获取为例,略叼,动态代理生成获取JS Module
    JavaScriptModuleRegistration registration =
        Assertions.assertNotNull(
            mModuleRegistrations.get(moduleInterface),
            "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
    JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
        moduleInterface.getClassLoader(),
        new Class[]{moduleInterface},
        new JavaScriptModuleInvocationHandler(executorToken, instance, registration));
    instancesForContext.put(moduleInterface, interfaceProxy);
    return (T) interfaceProxy;
  }

从上面这段代码我们可以看见,getJSModule 获取 JsModule 的实质是通过 Java 的动态代理来实现的,同时 JavaScriptModuleRegistration 对 JavaScriptModule 的包装是为了检查实现 JavaScriptModule 接口的类不能存在重载,因为与 JS 端对应,JS 不支持。那我们不妨把视线转移到 JavaScriptModuleInvocationHandler 的 invoke 方法,可以发现实质是调用了 mCatalystInstance.callFunction(executorToken, mModuleRegistration.getName(), method.getName(), jsArgs); 语句,继续跟了一下发现调用了 CatalystInstanceImpl.java 的 native callJSFunction() 方法把相关参数传递到了 C++ 层,额,前面我们知道 CatalystInstanceImpl.cpp 只是 JNI 对于 Android 层适配的特有封装,实质对应了 Common 里 Instance.cpp,而这里的 native callJSFunction() 实质是通过 Instance::callJSFunction() 调用了 NativeToJsBridge::callFunction() 方法,进而放在了 JSCExecutor 的线程队列中触发了 JSCExecutor::callFunction() 方法,我们重点关注下 JSCExecutor::callFunction() 方法,如下:

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
  ......
  auto result = [&] {
    try {
        //m_callFunctionReturnFlushedQueueJS来自于JSCExecutor::bindBridge()方法中初始化,JSCExecutor::bindBridge()是在前面分析启动流程时被调用的,实质是负责通过 Webkit JSC 拿到 JS 端代码的相关对象和方法引用,譬如拿到 JS 端 BatchedBridge.js 的 __fbBatchedBridge 属性与 MessageQueue.js 的 callFunctionReturnFlushedQueue 方法引用。此处实质为调用 MessageQueue.js 的 callFunctionReturnFlushedQueue 方法。
      return m_callFunctionReturnFlushedQueueJS->callAsFunction({
        Value(m_context, String::createExpectingAscii(moduleId)),
        Value(m_context, String::createExpectingAscii(methodId)),
        Value::fromDynamic(m_context, std::move(arguments))
      });
    } catch (...) {
      std::throw_with_nested(
        std::runtime_error("Error calling function: " + moduleId + ":" + methodId));
    }
  }();
    //调用 native 模块,暂时忽略,下一小节解释,这里重点关注 Java 调用 JS 通信
  callNativeModules(std::move(result));
}

既然都说了 m_callFunctionReturnFlushedQueueJS 是 JSCExecutor::bindBridge() 方法中初始化的,实质依赖 Webkit JSC 架起了 JS 代码与 C++ 的桥梁,那我们就去 JS 端看看 MessageQueue.js 的 callFunctionReturnFlushedQueue() 方法,如下:

  callFunctionReturnFlushedQueue(module: string, method: string, args: Array<any>) {
    guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }

继续跟下 this.__callFunction(module, method, args),如下:

  __callFunction(module: string, method: string, args: Array<any>) {
    ......
    //_callableModules属性是通过registerCallableModule()方法添加的,而我们以AppRegistry.js为例,可以看见AppRegistry.js中有调用BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);把自己注册进去,BatchedBridge.js是与MessageQueue.js绑死的。
    //说白了,这里就是在 JS 端的 Modules 中查映射表找到 AppRegistry.js
    const moduleMethods = this._callableModules[module];
    ......
    //拿到Java端调用的对应JS端指定Module的方法,譬如AppRegistry.js的runApplication()方法
    const result = moduleMethods[method].apply(moduleMethods, args);
    Systrace.endEvent();
    return result;
  }

握草,React Native Java 层调用 JS 层通信原来原来就这么回事;实质就是 Java 与 JS 端都准备好一个 Module 映射表,然后当 Java 端调用 JS 代码时 Java 端通过查表动态代理创建一个与 JS 对应的 Module 对象,当调用这个 Module 的方法时 Java 端通过动态代理的 invoke 方法触发 C++ 层,层层调用后通过 JSCExecutor 执行 JS 端队列中的映射查表找到 JS 端方法进行调用;同时会发现在 JSCExecutor 中每次 Java 调用 JS 之后会进行 Java 端的一个回调(从 JS 层的 MessageQueue.js 中获得累积的 JS Call)。为了不迷糊,我们对这一阶段总结如下图:

这里写图片描述

不多说了,一切都在上面图里,所以这时候如果你再回头去想第一部分 RN 启动流程源码浅析的疑惑点就完全明白了,也就串起来了。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

4 RN JS 调用 Java 端框架浅析

上面已经总结了 React Native 的启动流程与 Java 端代码调用 JS 端代码的通信流程,下面我们再来分析 JS 端代码调用 Java 端代码的通信流程,这样整个 React Native 核心框架的三大问题都得到浅析了,方便日后裁剪 React Native。既然要分析 JS 调用 Java 端通信框架了,我们一样有必要先找到一个例子来做突破口,那我们就以 RN 官方的《Native Modules 》为例来说明,因为这是我们经常要封装且最熟悉的东西,可以看见文档中举了一个封装 ToastAndroid 的例子,那就拿它下手吧。我们在 JS 端使用 Android 端封装的 Java 模块时是如下这样用的:

//通过NativeModules拿到ToastAndroid
import { NativeModules } from 'react-native';
module.exports = NativeModules.ToastAndroid;

//使用的地方在JS中相关逻辑处调用,官方文档标准
import ToastAndroid from './ToastAndroid';
ToastAndroid.show('Awesome', ToastAndroid.SHORT);

可以看见,JS 调用 Java 的第一步就是通过 JS 端的 NativeModules 拿到 相关的 Java 映射 Module,那我们就去 JS 的 NativeModules 里看看吧,如下:

//暂时只关注NativeModules.js中精彩的
......
//JSC全局唯一global添加一个属性,赋值为NativeModules.js中方法引用
global.__fbGenNativeModule = genModule;

let NativeModules : {[moduleName: string]: Object} = {};
//依据是否定义global.nativeModuleProxy属性来决定怎么获取。
//nativeModuleProxy属性实质是在主加载流程中JSCExecutor::JSCExecutor()构造时通过installGlobalProxy(m_context, "nativeModuleProxy", exceptionWrapMethod<&JSCExecutor::getNativeModule>());创建的,所以当JS调用NativeModules时实质为JSCExecutor::getNativeModule()方法
if (global.nativeModuleProxy) {
  NativeModules = global.nativeModuleProxy;
} else {
    //只关注精彩的主线,DEV等模式的咱们不管
  ......
}
module.exports = NativeModules;

我们看下 JSCExecutor::getNativeModule() 方法,如下:

JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName) {
  ......
    //m_nativeModules来源于JsToNativeBridge的getModuleRegistry()方法,实质被转换为了JSCNativeModules.cpp
  return m_nativeModules.getModule(m_context, propertyName);
}

继续跟踪上面 JSCNativeModules.cpp 的 getModule(m_context, propertyName) 可以发现其核心就是调用 JSCNativeModules::createModule(const std::string& name, JSContextRef context) 方法,而这个方法里实质是通过 JSC 获取全局设置的 JS 属性,然后通过 JNI 查找 Java 端映射表再触发 JS 端相关方法:

folly::Optional<Object> JSCNativeModules::createModule(const std::string& name, JSContextRef context) {
      ......
      //JSC获取NativeModules.js中的global.__fbGenNativeModule = genModule;属性
    m_genNativeModuleJS = global.getProperty("__fbGenNativeModule").asObject();
    m_genNativeModuleJS->makeProtected();
    ......
  }
    //调用folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name)获取Native的配置表
  auto result = m_moduleRegistry->getConfig(name);
  if (!result.hasValue()) {
    return nullptr;
  }
    //JS端调用m_genNativeModuleJS对应方法
  Value moduleInfo = m_genNativeModuleJS->callAsFunction({
    Value::fromDynamic(context, result->config),
    JSValueMakeNumber(context, result->index)
  });
  ......
  return moduleInfo.asObject().getProperty("module").asObject();
}

上面主要分两步,通过 C++ 获取 Java 层映射表、通过 JSC 调用 JS 端方法,我们先看下 ModuleRegistry::getConfig(const std::string& name) 源码,如下:

folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
  //临界值判断
  ......
  //modules_列表实质来源自上面加载流程中分析的CatalystInstanceImpl::initializeBridge();
  //实质就是Java端在CatalystInstanceImpl中传递到C++的ModuleRegistryHolder::getModuleRegistry()方法;
  //module实质就是ModuleRegistryHolder.cpp构造中把Java端传递过来的Module包装成CxxNativeModule、JavaNativeModule,这两实质是NativeModule C++子类
  NativeModule* module = modules_[it->second].get();

  // string name, object constants, array methodNames (methodId is index), [array promiseMethodIds], [array syncMethodIds]
  //准备创建一个动态config对象
  folly::dynamic config = folly::dynamic::array(name);

  {
    //module->getConstants()实际通过反射调用了Java层JavaModuleWrapper的getConstants方法
    config.push_back(module->getConstants());
  }

  {
    //module->getMethods()实际反射调用了Java层JavaModuleWrapper的getMethods方法,也就是BaseJavaModule.java的getMethods方法,这里会通过Java的findMethods()方法过滤出继承BaseJavaModule实现类中有@ReactMethod注解的方法!!!!!!!!(符合官方文档)
    std::vector<MethodDescriptor> methods = module->getMethods();

    folly::dynamic methodNames = folly::dynamic::array;
    folly::dynamic promiseMethodIds = folly::dynamic::array;
    folly::dynamic syncMethodIds = folly::dynamic::array;

    for (auto& descriptor : methods) {
      // TODO: #10487027 compare tags instead of doing string comparison?
      methodNames.push_back(std::move(descriptor.name));
      if (descriptor.type == "promise") {
        promiseMethodIds.push_back(methodNames.size() - 1);
      } else if (descriptor.type == "sync") {
        syncMethodIds.push_back(methodNames.size() - 1);
      }
    }

    if (!methodNames.empty()) {
      config.push_back(std::move(methodNames));
      if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {
        config.push_back(std::move(promiseMethodIds));
        if (!syncMethodIds.empty()) {
          config.push_back(std::move(syncMethodIds));
        }
      }
    }
  }

  ......
  return ModuleConfig({it->second, config});
}

真相渐渐浮出水面了,我们继续回到前面 JSCNativeModules::createModule(const std::string& name, JSContextRef context) 中看看最后调用的 NativeModules.js 中的 global.__fbGenNativeModule = genModule 方法,如下:

function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, module?: Object} {
  ......
  //通过JSC拿到C++中从Java端获取的Java的Module映射表包装配置类
  const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
  ......
  const module = {};
  //遍历构建module的属性方法
  methods && methods.forEach((methodName, methodID) => {
    const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
    const isSync = syncMethods && arrayContains(syncMethods, methodID);
    invariant(!isPromise || !isSync, 'Cannot have a method that is both async and a sync hook');
    const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
    //生成Module的函数方法
    module[methodName] = genMethod(moduleID, methodID, methodType);
  });
  Object.assign(module, constants);
  ......
  //返回一个
  return { name: moduleName, module };
}

到此 JS 调用 Java 的准备工作已经就绪了(即 JS 是如何拿到 Native 映射表与方法的),JSC会准备好一个 JS 使用的 NativeModule 对象。

有了 JS 端的 Module 映射对象,访问就变得明朗了许多;还记得刚刚 NativeModules.js 中 genMethod() 方法生成的 JS Module 的方法属性吗?当我们 JS 真正调用 ToastAndroid.show(‘Awesome’, ToastAndroid.SHORT); 时实质就是调用 genMethod() 设置的方法;那我们就仔细看看这个 genMethod() 方法,可以发现它是依据调用的 JS 方法是不是 promise、sync 等走不同逻辑,但是主线核心都是调用了 BatchedBridge.enqueueNativeCall() 方法,那我们看看 MessageQueue.js 的 enqueueNativeCall 方法,如下:

  enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
    ......
    this._callID++;
    //_queue是个队列,用于存放调用的模块、方法、参数信息
    //把JS准备调用Java的模块名、方法名、调用参数放到数组里存起来
    this._queue[MODULE_IDS].push(moduleID);
    this._queue[METHOD_IDS].push(methodID);
    ......
    this._queue[PARAMS].push(params);

    const now = new Date().getTime();
    //如果5ms内有多个方法调用就先待在队列里防止过高频率,否则调用C++的nativeFlushQueueImmediate方法
    if (global.nativeFlushQueueImmediate &&
        now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
      global.nativeFlushQueueImmediate(this._queue);
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
    }
    ......
  }

这时候 JS 端调用 Java 通信的 JS 端调用流程就结束了,主要就是通过 JSC 桥接获取 Java 端 Module 的映射表转换为 JS NativeModule 的属性和相关方法,当 JS 端通过 NativeModule.XXX.method(); 使用时实质就是把 method 方法扔进了 JS 的队列,然后在队列中分两种情况,一种是方法调用超过 5ms 时直接触发 nativeFlushQueueImmediate 方法,另一种是当 Java 调用 JS 时也会把之前队列里存的方法调用通过 JSCExecutor::flush() 处理(这回就明白上面分析启动流程为毛 Java 拉起 JS 后又来了一个反回调了吧)。

那我们先看看 JS 直接触发 nativeFlushQueueImmediate 的流程吧,由于 JS 端调用了 global.nativeFlushQueueImmediate 方法,所以实质是通过 JSC 调用了 C++ 的 JSCExecutor::nativeFlushQueueImmediate(size_t argumentCount, const JSValueRef arguments[]) 方法,因为在启动流程中 C++ 初始化 JSCExecutor 对象时通过 initOnJSVMThread() 调用 JSCExecutor::initOnJSVMThread() 进而调用 installGlobalFunction() 方法通过 JSC 把它已经关联给了 JS 。回过头发现 JSCExecutor::nativeFlushQueueImmediate 实质调用了 JSCExecutor::flushQueueImmediate(Value&& queue) 方法,进而调用了 JsToNativeBridge 的 callNativeModules(JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) 方法,只是 isEndOfBatch=false 而已。

接着我们再看下 Java 调用 JS 时把之前队列里存的方法通过 JSCExecutor::flush() 调用的情况,会发现其实质也是调用了 callNativeModules(m_flushedQueueJS->callAsFunction({})) 方法,m_flushedQueueJS->callAsFunction({})就是调用 MessageQueue.js 中的 flushedQueue() 方法,得到 JS 队列中睡觉的方法,然后传给了 callNativeModules 方法,接着发现实质也是调用了 JsToNativeBridge 的 callNativeModules(JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) 方法,只是 isEndOfBatch=true 而已。

这下就有意思了,两个分支实质原理是一致的,那我们直接看下 JsToNativeBridge 的 callNativeModules 方法:

  void callNativeModules(
      JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
    ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
    //在native队列中执行
    m_nativeQueue->runOnQueue([this, token, calls=std::move(calls), isEndOfBatch] () mutable {
      //遍历来自js队列的调用方法列表
      for (auto& call : react::parseMethodCalls(std::move(calls))) {
          //m_registry是C++的ModuleRegistry,在前面启动流程分析CatalystInstanceImpl.initializeBridge()时候传递了一个Java的ModuleRegistryHolder到C++去保存
        m_registry->callNativeMethod(
          token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
      }
      if (isEndOfBatch) {
          //类似数据库事务操作标记回调Java状态
        m_callback->onBatchComplete();
        m_callback->decrementPendingJSCalls();
      }
    });
  }

没啥说的,拿出 JS 队列里存在的 JS 调用 Java 的所有方法通过 ModuleRegistry::callNativeMethod 方法遍历调用,那就去看看这个方法,如下:

void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
                                      folly::dynamic&& params, int callId) {
  ......
  //modules_是创建ModuleRegistryHolder时根据Java层ModuleRegistryHolder创建的C++ NativeModule。
  //moduleId为模块在列表中的索引值。
  modules_[moduleId]->invoke(token, methodId, std::move(params));
}

那就继续去看看 C++ 的 ModuleRegistryHolder 构造方法中包装 Java Module 的 C++ 的 NativeModule 的子类 JavaNativeModule 的 invoke 方法吧,如下:

class JavaNativeModule : public NativeModule {
 public:
  ......
  void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) override {
    //wrapper_参数为ModuleRegistryHolder.cpp构造方法中由Java传入的Java Module被C++包装的JavaModuleWrapper对象(ModuleRegistryHolder.h中定义,映射Java的JavaModuleWrapper.java)
    //通过反射调用JavaModuleWrapper的invoke方法,同时把methodId和参数传过去。
    static auto invokeMethod =
      wrapper_->getClass()->getMethod<void(JExecutorToken::javaobject, jint, ReadableNativeArray::javaobject)>("invoke");
    invokeMethod(wrapper_, JExecutorToken::extractJavaPartFromToken(token).get(), static_cast<jint>(reactMethodId),
                 ReadableNativeArray::newObjectCxxArgs(std::move(params)).get());
  }
  ......
};

去他奶奶的,这不就是调用 Java 的对应方法么,直接到 JNI 映射的 Java 类中看这个方法,如下:

class JavaModuleWrapper {
    ......
  @DoNotStrip
  public void invoke(ExecutorToken token, int methodId, ReadableNativeArray parameters) {
    if (mMethods == null || methodId >= mMethods.size()) {
      return;
    }
    //mMethods为所有继承BaseJavaModule类的BaseJavaModule.JavaMethod对象
    mMethods.get(methodId).invoke(mCatalystInstance, token, parameters);
  }
}

喔噢!明朗了!就这样 JS 就调用了 Java 模块的方法。简单通过框架流程图来总结回顾下这个流程吧,如下:
这里写图片描述

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

5 总结

此刻只想呵呵,真特么不容易,Java、C++、JS 跳来跳去的看,总算基本搞明白这三大核心主体知识点了,有了上面三个仔细的分析下面对上面三个来个综合总结,按照我个人阅读完 React Native 这一部份源码后的核心总结如下:

  • Java层 ReactContext(ReactApplicationContext): React Native 封装后的 Android Context,通过其访问设置 RN 包装起来的核心类实现等;
  • Java层 ReactInstanceManager(ReactInstanceManagerImpl): RN对 Android 层暴露的大内总管,负责掌管 CatalystInstanceImpl 实例、ReactRootView、Activity 生命周期等;
  • Java/C++层 CatalystInstance(CatalystInstanceImpl): RN Java、C++、JS通信总舵主,统管 JS、Java 核心 Module 映射表、回调等等,三端入口与桥梁;
  • C++层 NativeToJsBridge: Java 调用 JS 的桥梁,用来调用 JS Module、回调 Java(通过JsToNativeBridge)等;
  • C++层 JsToNativeBridge: JS 调用 Java 的桥梁,用来调用 Java Module等;
  • C++层 JSCExecutor: 掌管 Webkit 的 JavaScriptCore,JS 与 C++ 的转换桥接都在这里中转处理;
  • JS层 MessageQueue: 队列栈,用来处理 JS 的调用队列、调用 Java 或者 JS Module 的方法、处理回调、管理 JS Module 等;
  • 多层 JavaScriptModule/BaseJavaModule(NativeModule): 双端字典映射表中的模块,负责 Java/JS 到彼此的映射调用格式申明,由 CatalystInstance 统管;

尼玛,看了好久的源码,搞明白以后其实发现主流程和互相调用就上面几幅图那么回事。。。。这篇文章差点难产了,断断续续工作之余抽空才搞定,总的来说还是要坚持;通过上面的分析和之前《React Native Android 从学车到补胎和成功发车经历》《React Native Android Gradle 编译流程浅析》两篇文章的配合,我们对于 React Native 已经渐渐不觉得陌生了,本篇已经循序渐进揭开了 React Native 比较核心的启动主流程脉络,后面抽空会对 React Native 进行更加全面的解刨分析,力求今年把 React Native 这个技能点翻页封存吧,加油。

这里写图片描述

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

猜你喜欢

转载自blog.csdn.net/yanbober/article/details/53157456