ReactNative For Android(RN4A)源码解

作者: 灵丞 | 来源:发表于2016-12-21 22:14 被阅读430次

    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的解析渲染等等,会在后续的文章中分析,敬请期待。

    相关文章

      网友评论

        本文标题:ReactNative For Android(RN4A)源码解

        本文链接:https://www.haomeiwen.com/subject/edccvttx.html