ReactNative For Android(RN4A) 源码解读 - 运行环境的创建流程

975 阅读9分钟

RN4A运行环境的创建流程

前言

国内近年来对ReactNaitve讨论的火爆程度不言而喻,可能你都已经用了一段时间的RN4A了。不过你是否清楚RN4A是如何初始化一个环境?Js是何时通知Native渲染UI组件?从RN4A的环境初始化到ReactView呈现到UI的时候,RN4A都干了什么?别急,接下来就为你揭晓这些问题的答案。(PS: 本文源码分析基于当前最新ReactNative源码 v0.40.0-rc.4,限于作者水平有限,如果有错误和理解不当之处感谢指出。)

总体流程

为了接下来细节分析的时候你心中有个整体印象,我们先直接说下RN4A的框架初始化的总体流程。(PS:根据项目实际接入RN4A的方式不同,流程可能有所不同,这里只是官方使用的一种初始化流程。)

创建ReactRootView -> 创建ReactInstanceManager -> 创建ReactContext -> RN4A环境初始化完成 -> 通知Js渲染界面。

万物之始ReactRootView

俗话说"擒贼先擒王",一般分析代码都会从源头走起。如果你查看RN4A的接入文档,就知道RN4A已经为我们封装好了ReactActiviy类,只要通过继承它,你可以省掉RN4A与Activity之间的绝大部分逻辑交互,包括生命周期回调,以及发送消息通知Js渲染UI的操作等等。通过源码我们可以看到ReactActivity中所有的逻辑都是由ReactActivityDelegate类来代理,这是一个不错的设计方式,你可以轻松地把ReactActivityDelegate集成到你自己的Activity中,自由定制RN4A环境的初始化方案。好的,有点扯远了,现在让我们来看下ReactActivityDelegate中的关键方法吧。

  protected void onCreate(Bundle savedInstanceState) {
    //省略判断悬浮窗权限
    ...

    if (mMainComponentName != null && !needsOverlayPermission) {
      loadApp(mMainComponentName);
    }
    mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
  }

我们知道onCreate方法就是Activity开始执行的地方,ReactActivityDelegateonCreate方法自然也会在Activity的onCreate中调用,这里可以看到,代码会判断当前App是否有显示悬浮层的权限,然后开始调用loadApp方法,注意,这里就是RN4A官方方式加载的入口了。我们接下来继续跟踪下去:

  protected void loadApp(String appKey) {
    if (mReactRootView != null) {
      throw new IllegalStateException("Cannot loadApp while app is already running.");
    }
    mReactRootView = createRootView();
    mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      appKey,
      getLaunchOptions());
    getPlainActivity().setContentView(mReactRootView);
  }

这段代码逻辑很简单,createRootView方法创建了一个RN4A的根View(ReactRootView),所有RN4A的View都会创建在ReactRootView里,然后将ReactRootView当成了Activity的内容布局,一般将ReactRootView作为Activity的内容布局是比较省事的方式,当然,你也可以将它作为某个ViewGroup的子View,只不过这种方式你很容易会踩到一些坑,比如你需要处理RN的View和原生View之间的事件冲突。好了,那我们接着看代码,我们看下关键的startReactApplication方法,这里注意一下它的形参,第一个参数需要一个ReactInstanceManager的实例,ReactNativeHostgetReactInstanceManager这个方法会创建一个ReactInstanceManager实例,ReactInstanceManager是RN4A的核心类,我们需要先来看下它是如何被初始化的。

  protected ReactInstanceManager createReactInstanceManager() {
    ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
      .setApplication(mApplication)
      .setJSMainModuleName(getJSMainModuleName())
      .setUseDeveloperSupport(getUseDeveloperSupport())
      .setRedBoxHandler(getRedBoxHandler())
      .setUIImplementationProvider(getUIImplementationProvider())
      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

    for (ReactPackage reactPackage : getPackages()) {
      builder.addPackage(reactPackage);
    }

    String jsBundleFile = getJSBundleFile();
    if (jsBundleFile != null) {
      builder.setJSBundleFile(jsBundleFile);
    } else {
      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
    }
    return builder.build();
  }

由于ReactInstanceManager的参数很多,所以RN4A使用了建造者(Builder)模式,我们先简单看一下这些参数的意义:

  • application - 这个就不说了;
  • jsMainModuleName - 在Js文件中设置的模块名称,通过该名称加载对应的Js组件;
  • useDeveloperSupport - 设置是否使用Dev调试工具;
  • redBoxHandler - 设置红框处理器,Js运行时的异常展示出来的红框;
  • UIImplementationProvider - UIManagerModule的工人,负责处理从Js过来的跟UI操作相关的消息(View的创建、测量、更新等各种脏活);
  • initialLifecycleState - ReactInstanceManager实例初始化时候的生命周期;
  • reactPackage - 自定义的ReactNative包:
  • jsBundleFile - 放在手机文件系统中的JsBundle的文件路径;
  • bundleAssetName - 内置在Assets目录下的JsBundle文件,如果设置了则不会走其他的JsBundle加载方式,需要注意在jsBundleFile有值的情况下不会生效;

看完ReactInstanceManager的创建,我们再返回到之前loadApp方法处,继续跟踪ReactRootViewstartReactApplication方法:

public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle launchOptions) {
    UiThreadUtil.assertOnUiThread();

    // TODO(6788889): Use POJO instead of bundle here, apparently we can't just use WritableMap
    // here as it may be deallocated in native after passing via JNI bridge, but we want to reuse
    // it in the case of re-creating the catalyst instance
    Assertions.assertCondition(
        mReactInstanceManager == null,
        "This root view has already been attached to a catalyst instance manager");

    mReactInstanceManager = reactInstanceManager;
    mJSModuleName = moduleName;
    mLaunchOptions = launchOptions;

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

这里有两个重要的操作, mReactInstanceManager.createReactContextInBackground方法完成了RN4A环境的创建和初始化,其中RN4A桥的创建和Js脚本的加载都是在这里面进行的;而另一个attachToReactInstanceManager则将ReactRootView实例与ReactInstanceManager实例绑定起来,并从Native发送runApplication消息到Js,Js收到消息便会开始执行相应的业务,这里需要注意的是,如果ReactInstanceManager是第一次创建的话由于它的内部还没有创建好RN4A上下文实例(ReactContext),Native此时并不会发送runApplication消息给Js,而是将这个操作放在RN4A所有的环境创建完成之后才被执行,这里只是先提下,下面还会说到。
接着我们看下mReactInstanceManager.createReactContextInBackground这个方法,由源码可知XReactInstanceManagerImplReactInstanceManager的唯一实现类,所以你可以一直跟踪到下面的代码,

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

    if (mUseDeveloperSupport && mJSMainModuleName != null) {
      final DeveloperSettings devSettings = mDevSupportManager.getDevSettings();

      // If remote JS debugging is enabled, load from dev server.
      if (mDevSupportManager.hasUpToDateJSBundleInCache() &&
          !devSettings.isRemoteJSDebugEnabled()) {
        // If there is a up-to-date bundle downloaded from server,
        // with remote JS debugging disabled, always use that.
        onJSBundleLoadedFromServer();
      } else if (mBundleLoader == null) {
        mDevSupportManager.handleReloadJS();
      } else {
        mDevSupportManager.isPackagerRunning(
            new DevServerHelper.PackagerStatusCallback() {
              @Override
              public void onPackagerStatusFetched(final boolean packagerIsRunning) {
                UiThreadUtil.runOnUiThread(
                    new Runnable() {
                      @Override
                      public void run() {
                        if (packagerIsRunning) {
                          mDevSupportManager.handleReloadJS();
                        } else {
                          // If dev server is down, disable the remote JS debugging.
                          devSettings.setRemoteJSDebugEnabled(false);
                          recreateReactContextInBackgroundFromBundleLoader();
                        }
                      }
                    });
              }
            });
      }
      return;
    }

    recreateReactContextInBackgroundFromBundleLoader();
  }

上面的代码还挺长的,其实只要关注recreateReactContextInBackgroundFromBundleLoader方法就行了,不过这里还是需要简单说下这一处源码的逻辑。执行过程大概是这样的,如果你启用了RN4A的Dev支持,并且Js模块名(JsModuleName)不是空的,RN4A就会判断你本地是否有最新的JsBundler文件,如果有的话就直接读取本地的JsBundle文件,否则会从你的LocalServer中加载JsBundle文件,这里为了分析源码方便,我们先假设RN4A是处在Release环境中执行的,所以我们就直接走recreateReactContextInBackgroundFromBundleLoader代码, 在RN4A源码中经过几处跳转之后我们就会看到下面这段逻辑。

private void recreateReactContextInBackground(
      JavaScriptExecutor.Factory jsExecutorFactory,
      JSBundleLoader jsBundleLoader) {
    UiThreadUtil.assertOnUiThread();

    ReactContextInitParams initParams =
        new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
    if (mReactContextInitAsyncTask == null) {
      // 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;
    }
  }

这是RN4A的一段相对重要的逻辑,我们可以看到RN4A使用了Android的异步任务(ReactContextInitAsyncTask)来执行初始化操作,阅读源码可以知道RN4A先判断当前有没有ReactContextInitAsyncTask在进行,如果有的话,RN4A会将本次的初始化参数存放到initParams全局变量,等ReactContextInitAsyncTask初始化完成之后再去重新执行初始化操作,如果当前没有ReactContextInitAsyncTask任务在执行,则直接新建一个ReactContextInitAsyncTask任务并开始执行初始化操作。我们接着跟进源码:

  /*
   * Task class responsible for (re)creating react context in the background. These tasks can only
   * be executing one at time, see {@link #recreateReactContextInBackground()}.
   */
  private final class ReactContextInitAsyncTask extends
      AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> {
    @Override
    protected void onPreExecute() {
      if (mCurrentReactContext != null) {
        tearDownReactContext(mCurrentReactContext);
        mCurrentReactContext = null;
      }
    }

    @Override
    protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
        //省略一些代码
      Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
      try {
        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(result.get());
      } catch (Exception e) {
        mDevSupportManager.handleException(e);
      } finally {
        mReactContextInitAsyncTask = null;
      }

      // Handle enqueued request to re-initialize react context.
      if (mPendingReactContextInitParams != null) {
        recreateReactContextInBackground(
            mPendingReactContextInitParams.getJsExecutorFactory(),
            mPendingReactContextInitParams.getJsBundleLoader());
        mPendingReactContextInitParams = null;
      }
    }

    @Override
    protected void onCancelled(Result<ReactApplicationContext> reactApplicationContextResult) {
      //省略一些代码
    }
  }

这也算是RN4A的核心一部分了,通过源码我们可以知道RN4A会在任务开始时候卸载掉旧的RN4A上下文实例(ReactContext是一个RN4A的上下文环境,持有了UI、Js和Native三线程,并维持了一个和Js通信的桥等),然后在异步任务线程池中RN4A会开始创建一个新的RN4A上下文实例(ReactContext),并在执行结束之后设置这个RN4A上下文实例(ReactContext);当RN4A执行完成之后如果发现有initParams(上文提到的参数),就会重新开始执行ReactContextInitAsyncTask任务。接下来我们去看下createReactContext方法都在做啥。

  /**
   * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
   */
  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    mSourceUrl = jsBundleLoader.getSourceUrl();
    List<ModuleSpec> moduleSpecs = new ArrayList<>();
    Map<Class, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
    JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();

    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    if (mUseDeveloperSupport) {
      reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
    }
    try {
      CoreModulesPackage coreModulesPackage =
        new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
      processPackage(
        coreModulesPackage,
        reactContext,
        moduleSpecs,
        reactModuleInfoMap,
        jsModulesBuilder);
    } finally {
        //省略一些代码 
    }
    for (ReactPackage reactPackage : mPackages) {
      try {
        processPackage(
          reactPackage,
          reactContext,
          moduleSpecs,
          reactModuleInfoMap,
          jsModulesBuilder);
      } finally {
         //省略一些代码
      }
    }
    NativeModuleRegistry nativeModuleRegistry;
    try {
       nativeModuleRegistry = new NativeModuleRegistry(moduleSpecs, reactModuleInfoMap);
    } finally {
        //省略一些代码
    }

    NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
        ? mNativeModuleCallExceptionHandler
        : mDevSupportManager;
    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 {
        //省略一些代码
    }

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

    reactContext.initializeWithInstance(catalystInstance);
    catalystInstance.runJSBundle();

    return reactContext;
  }

上面的代码看着有点长,不过逻辑也就这么几步:

  1. 生成一个RN4A上下文实例(ReactContext),并在开启Dev模式情况下设置一个Native异常处理器,用于捕获三个线程(UI、Js和Native)中发生的异常;
  2. 处理RN包,包括核心包(CorePackage)以及注册的自定义包(如官方提供的MainPackage),NativeModule信息被存放到moduleSpecsreactModuleInfoMap中,而JsModule则被放到jsModulesBuilder中;
  3. 通过桥构造器(CatalystInstanceImpl.Builder)构建出一个CatalystInstance实例,它在RN4A中负责中掌管Js和Native之间的通信;
  4. 将创建好的桥(CatalystInstance)放到RN4A上下文(ReactContext)中并进行初始化,这个过程实际上只是让RN4A上下文(ReactContext)持有三个关键线程管理实例(UI、JS和Native),RN4A的全部工作依赖这三条线程之间的相互协作;
  5. 最后就是运行在桥实例(CatalystInstance)中的JsBundle了,这里会从Jni层去调用Js引擎解释执行Js代码,关于RN4A的Jni层的逻辑,限于篇幅留待之后分析。

接下来让我们从ReactContextInitAsyncTask类的onPostExecute方法接着看,进入setupReactContext方法。

private void setupReactContext(ReactApplicationContext reactContext) {
    UiThreadUtil.assertOnUiThread();
    Assertions.assertCondition(mCurrentReactContext == null);
    mCurrentReactContext = Assertions.assertNotNull(reactContext);
    CatalystInstance catalystInstance =
        Assertions.assertNotNull(reactContext.getCatalystInstance());

    catalystInstance.initialize();
    mDevSupportManager.onNewReactContextCreated(reactContext);
    mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
    moveReactContextToCurrentLifecycleState();

    for (ReactRootView rootView : mAttachedRootViews) {
      attachMeasuredRootViewToInstance(rootView, catalystInstance);
    }

    ReactInstanceEventListener[] listeners =
      new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
    listeners = mReactInstanceEventListeners.toArray(listeners);

    for (ReactInstanceEventListener listener : listeners) {
      listener.onReactContextInitialized(reactContext);
    }
  }

这里RN4A会去执行一次桥CatalystInstance的初始化逻辑,并把初始化完成的消息发送出去,比如通知注册在桥的Module执行初始化的一些操作,告诉绑定的ReactRootView可以通过attachMeasuredRootViewToInstance方法通知Js"真正"执行业务逻辑了,之后Js就会开始通过一系列的消息指挥Native渲染展示UI等等。

结语

至此,RN4A从环境初始化到RN界面展示出来所经过的一个流程我们已经走了一遍,这篇文章只是分析RN4A框架的开篇,Native和Js之间的通信方式,Js Dom的解析渲染等等,会在后续的文章中分析,敬请期待。