ReactNative 详解(四) 源码之RN启动过程

本章主要来探讨一下,RN 的启动过程都做了什么?同时简单的介绍下在 Android 中是如何实现 ReactNative 的。进而引出解决一个重要的问题,ReactNative 的预加载。

ReactNative 系统框架概述

ReactNative 源码结构图如下:

ReactNative 详解(四) 源码之RN启动过程

其中几个主要内容:

  • Libraries:JS层的实现,实现了JS组件的封装与JS队列的封装
  • ReactAndroid:Android 源码实现
  • ReactCommon:C++ 层实现,实现了对脚本引擎JSC的封装与通信桥ReactBridge,Android与iOS调用
  • React:ReactNative源码的主要内容

ReactNative 主要工作就两部分:
第一部分实现:ReactNative 应用启动流程;ReactNative应用UI的绘制与渲染;ReactNative应用通信机制;ReactNative应用线程模型
第二部分:ReactNative运行时的异常以及异常的捕获与处理;SOLoader加载动态链接库;ReactNative触摸事件处理机制。

我们先从一个 Demo 工程来看 ReactNative 启动流程

启动流程

应用初始化

首先,我们打开这个普通工程的 android 目录,这里就是一个完整的 android 项目。

  1. 首先我们看 MainApplication.java 里面的 RN 的初始化操作
1234567891011121314151617181920212223242526272829303132
public class  extends Application implements ReactApplication {  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {        public boolean getUseDeveloperSupport() {      return BuildConfig.DEBUG;    }        protected List<ReactPackage> getPackages() {      return Arrays.<ReactPackage>asList(          new MainReactPackage()      );    }        protected String getJSMainModuleName() {      return "index";    }  };    public ReactNativeHost getReactNativeHost() {    return mReactNativeHost;  }    public void onCreate() {    super.onCreate();        SoLoader.init(this, /* native exopackage */ false);  }}

ReactApplication 可以看到我们在Application里实现了 ReactApplication接口,该接口要求创建一个ReactNativeHost对象。ReactNativeHost 对象,本身持有 ReactInstanceManager 对象。其对外暴露两个需要实现的方法:

1234567
//是否开启dev模式,dev模式下会有一些调试工具public abstract boolean getUseDeveloperSupport();// 返回app需要的ReactPackage,这些ReactPackage里包含了运行时需要用到的NativeModule// JavaScriptModule以及ViewManager// 我们的自定义 Module 也是在这里添加的protected abstract List<ReactPackage> getPackages();

ReactNativeHost主要的工作就是创建 ReactInstanceManager,创建部分代码如下:

12345678910111213141516171819202122232425262728293031323334353637
public abstract class ReactNativeHost {  protected ReactInstanceManager createReactInstanceManager() {    // Builder 模式,创建 ReactInstanceManager 实例    ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()      // 设置应用上下文      .setApplication(mApplication)      // 设置应用的 jsBundle,可以传给 url 来使其从服务器拉去 jsBundle      // 仅在 dev 下生效      .setJSMainModulePath(getJSMainModuleName())      // 是否开启 dev 模式      .setUseDeveloperSupport(getUseDeveloperSupport())      // 红盒回调      .setRedBoxHandler(getRedBoxHandler())      .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())      // 自定义UI实现机制,不会使用      .setUIImplementationProvider(getUIImplementationProvider())      .setJSIModulesPackage(getJSIModulePackage())      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);    // 添加ReactPackage (就是我们复写的抽象方法)    for (ReactPackage reactPackage : getPackages()) {      builder.addPackage(reactPackage);    }    //获取js Bundle的加载路径    String jsBundleFile = getJSBundleFile();    if (jsBundleFile != null) {      builder.setJSBundleFile(jsBundleFile);    } else {      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));    }    // 创建    ReactInstanceManager reactInstanceManager = builder.build();    ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_END);    return reactInstanceManager;  }}
  1. 接下来看 MainActivity.java
1234567
public class MainActivity extends ReactActivity {    @Override    protected String getMainComponentName() {        // 返回组件名        return "TestDemo";    }}

这里我们的 MainActivity 继承自 ReactActivity,ReactActivity作为JS页面的容器。最后我们的前端界面的内容,就是渲染到了这个容器上面。(ReactActivity 后面介绍)

应用启动流程

我们开始分析 ReactActivityReactAcivity 本身继承自 Activity,并实现了其生命周期。本质上其什么也没有做,都是委托给了 ReactActivityDelegate。代码如下:

123456789101112131415161718192021
public abstract class ReactActivity extends Activity    implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {        private final ReactActivityDelegate mDelegate;    protected ReactActivity() {        mDelegate = createReactActivityDelegate();    }    // 获取渲染的 RN 组件名称    protected @Nullable String getMainComponentName() {        return null;    }    protected ReactActivityDelegate createReactActivityDelegate() {        return new ReactActivityDelegate(this, getMainComponentName());    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        // 委托给 ReactActivityDelegate 来实现        mDelegate.onCreate(savedInstanceState);    }}

其本身所有行为都交给了 ReactActivityDelegate 来处理,我们只需要关心 ReactActivityDelegate 即可。

ReactActivityDelegate

我们先来看,ReactActivity 的 onCreate 方法,本质上映射到了 ReactActivityDelegate 的 onCreate。

ReactActivityDelegate.onCreate(Bundle savedInstanceState)
1234567891011121314151617181920212223242526272829
public class ReactActivityDelegate {    public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {        mActivity = activity;        mMainComponentName = mainComponentName;        mFragmentActivity = null;    }    protected void onCreate(Bundle savedInstanceState) {        if (mMainComponentName != null) {            // 载入 app 页面            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());        // mActivity.setContentView        getPlainActivity().setContentView(mReactRootView);    }}

创建阶段主要了做了如下几个事情:

  1. 创建ReactRootView作为应用的容器,它本质上是一个FrameLayout。
  2. 调用ReactRootView.startReactApplication()进一步执行应用启动流程。
  3. 调用Activity.setContentView() 将创建的 ReactRootView 作为 ReactActivity的content view。

所以呢,RN 其实被渲染到了一个 ReactRootView 上面,他可以被用在 Android 任何地方(比如 RN 视图和 原生视图组合)。接下来我们看启动过程 startReactApplication

ReactRootView.startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle launchOptions)

简要代码如下:

123456789101112131415161718192021222324252627
public class ReactRootView extends SizeMonitoringFrameLayout    implements RootView, MeasureSpecProvide {    public void startReactApplication(        ReactInstanceManager reactInstanceManager,        String moduleName,        @Nullable Bundle initialProperties,        @Nullable String initialUITemplate) {        try {            // 判断是否运行在主线程上            UiThreadUtil.assertOnUiThread();            mReactInstanceManager = reactInstanceManager;            mJSModuleName = moduleName;            mAppProperties = initialProperties;            mInitialUITemplate = initialUITemplate;            // 创建RN应用上下文            if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {                mReactInstanceManager.createReactContextInBackground();            }            attachToReactInstanceManager();        } finally {            Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);        }    }}

参数信息:

  • ReactInstanceManager reactInstanceManager:ReactInstanceManager 实例
  • String moduleName:模块的名字,对应ReactActivity.getMainComponentName()与AppRegistry.registerComponent()
  • Bundle launchOptions:Bundle 类型,可以在 startActivity 时候传递参数到 JS 层

(UiThreadUtil 主要包装了两个方法:UiThreadUtil.isOnUiThread(),UiThreadUtil.runOnUiThread(Runnable runnable))

主要还是调用了 ReactInstanceManager 上的 createReactContextInBackground 方法。

ReactInstanceManager.createReactContextInBackground()
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
public class ReactInstanceManager { /**  * Trigger react context initialization asynchronously in a background async task. This enables  * applications to pre-load the application JS, and execute global code before  * {@link ReactRootView} is available and measured. This should only be called the first time the  * application is set up, which is enforced to keep developers from accidentally creating their  * application multiple times without realizing it.  *  * Called from UI thread. */ @ThreadConfined(UI)  public void createReactContextInBackground() {    Log.d(ReactConstants.TAG, "ReactInstanceManager.createReactContextInBackground()");    Assertions.assertCondition(        !mHasStartedCreatingInitialContext,        "createReactContextInBackground should only be called when creating the react " +            "application for the first time. When reloading JS, e.g. from a new file, explicitly" +            "use recreateReactContextInBackground");    mHasStartedCreatingInitialContext = true;    recreateReactContextInBackgroundInner();  }  @ThreadConfined(UI)  private void recreateReactContextInBackgroundInner() {    UiThreadUtil.assertOnUiThread();    // 这里有一大堆代码,都是在开发模式下在线更新 bundle (mDevSupportManager.handleReloadJS()),调用开发菜单等调试功能的地方    // 但是实际上都调用到了 下面那句话    if (mUseDeveloperSupport && mJSMainModulePath != null) {    }    // 线上模式    recreateReactContextInBackgroundFromBundleLoader();  }  @ThreadConfined(UI)  private void recreateReactContextInBackgroundFromBundleLoader() {    recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);  }  @ThreadConfined(UI)  private void recreateReactContextInBackground(    JavaScriptExecutorFactory jsExecutorFactory,    JSBundleLoader jsBundleLoader) {    UiThreadUtil.assertOnUiThread();    // ReactContextInitParams 类就是实现了一个断言,两个参数必须有    final ReactContextInitParams initParams = new ReactContextInitParams(      jsExecutorFactory,      jsBundleLoader);    if (mCreateReactContextThread == null) {      // 初始化一个异步任务,创建 ReactContextInitAsyncTask      runCreateReactContextOnNewThread(initParams);    } else {      // 创建ReactContext的后台任务已经开启,缓存initParams在队列中等待重新创建ReactContext      mPendingReactContextInitParams = initParams;    }  }  public ReactContextInitParams(    JavaScriptExecutorFactory jsExecutorFactory,    JSBundleLoader jsBundleLoader) {    mJsExecutorFactory = Assertions.assertNotNull(jsExecutorFactory);    mJsBundleLoader = Assertions.assertNotNull(jsBundleLoader);  }}

这里创建过程从上到下执行,最后调用到 runCreateReactContextOnNewThread。该方法实际上就是新启了一个线程,来执行如下内容:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
final ReactApplicationContext reactApplicationContext =                    createReactContext(                        initParams.getJsExecutorFactory().create(),                        initParams.getJsBundleLoader());// 如果创建完毕 createContext 则回调这个线程Runnable setupReactContextRunnable =                      new Runnable() {                        @Override                        public void run() {                          try {                            setupReactContext(reactApplicationContext);                          } catch (Exception e) {                            mDevSupportManager.handleException(e);                          }                        }                      };// c++ 层回调                  reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);``` 完成 ReactApplicationContext 的创建。我们看传入的两个参数:* JsExecutorFactory jsExecutor:当该类被加载时,它会自动去加载"reactnativejnifb.so"库,并会调用 Native 方法 initHybrid() 初始化 C++层 RN 与 JSC通信的框架。* JSBundleLoader jsBundleLoader:缓存了JSBundle的信息,封装了上层加载JSBundle的相关接口,CatalystInstance通过其间接调用ReactBridge 去加载J S文件,不同的场景会创建不同的加载器,具体可以查看类JSBundleLoader。##### ReactInstanceManager.createReactContext( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader)简要代码如下```javapublic class ReactInstanceManager {      private ReactApplicationContext createReactContext(      JavaScriptExecutor jsExecutor,      JSBundleLoader jsBundleLoader) {    // ReactApplicationContext 是 ReactContext的包装类。实际上调用的就是 ReactContext。    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);    NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null        ? mNativeModuleCallExceptionHandler        : mDevSupportManager;    reactContext.setNativeModuleCallExceptionHandler(exceptionHandler);    // 创建 JavaModule 注册表Builder,用来创建JavaModule注册表,JavaModule注册表将所有的JavaModule注册到CatalystInstance中。    // mPackages 就是 ReactNativeHost 那里 getPackages 的各种模块 ReactPackage    NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);    // 创建 CatalystInstance     CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()      .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())      .setJSExecutor(jsExecutor)      .setRegistry(nativeModuleRegistry)      .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 (mJSIModulePackage != null) {      catalystInstance.addJSIModules(mJSIModulePackage        .getJSIModules(reactContext, catalystInstance.getJavaScriptContextHolder()));    }    if (mBridgeIdleDebugListener != null) {      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);    }    // 通过CatalystInstance开始加载JS Bundle    // runJSBundle 在 CatalysInstanceImp 中    catalystInstance.runJSBundle();    // 关联ReacContext与CatalystInstance    reactContext.initializeWithInstance(catalystInstance);    return reactContext;  }}
  1. 主要创建 JavaModule 表,交给 CatalystInstance 管理
  2. 处理ReactPackage,将JavaModule与JavaScriptModule放进各自对应的注册表里。
  3. 通过上面jsExecutor、nativeModuleRegistry、jsModulesRegistry、jsBundleLoader、exceptionHandler等参数创建CatalystInstance实例。
  4. 关联 ReactContext 与 CatalystInstance,并将JS Bundle加载进来,等待ReactContextInitAsyncTask结束以后调用JS入口渲染页面。
  5. 最后调用 CatalystInstance.runJSBundle()去加载 JS Bundle

最终由 C++ 中的JSCExecutor.cpp 完成了 JS Bundle 的加载。
这里起到作用的就是 CatalystInstance 他由 CatalystInstanceImpl 构造而成。查看代码:

CatalystInstanceImpl

1234567891011121314151617181920212223242526272829303132333435363738394041424344
public class CatalystInstanceImpl implements CatalystInstance {  // C++ parts  private final HybridData mHybridData;  private native static HybridData initHybrid();  // 在C++层初始化通信桥ReactBridge  private native void initializeBridge(      ReactCallback callback,      JavaScriptExecutor jsExecutor,      MessageQueueThread jsQueue,      MessageQueueThread moduleQueue,      Collection<JavaModuleWrapper> javaModules,      Collection<ModuleHolder> cxxModules);  private CatalystInstanceImpl(    final ReactQueueConfigurationSpec reactQueueConfigurationSpec,    final JavaScriptExecutor jsExecutor,    final NativeModuleRegistry nativeModuleRegistry,    final JSBundleLoader jsBundleLoader,    NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {    // Native方法,用来创建JNI相关状态,并返回mHybridData。    mHybridData = initHybrid();    // RN中的三个线程:Native Modules Thread、JS Thread、UI Thread,都是通过Handler来管理的。    mReactQueueConfiguration = ReactQueueConfigurationImpl.create(        reactQueueConfigurationSpec,        new NativeExceptionHandler());    mBridgeIdleListeners = new CopyOnWriteArrayList<>();    mNativeModuleRegistry = nativeModuleRegistry;    mJSModuleRegistry = new JavaScriptModuleRegistry();    mJSBundleLoader = jsBundleLoader;    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;    mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();    mTraceListener = new JSProfilerTraceListener(this);    // Native方法,调用initializeBridge()方法,并创建BridgeCallback实例,初始化Bridge。    initializeBridge(      new BridgeCallback(this),      jsExecutor,      mReactQueueConfiguration.getJSQueueThread(),      mNativeModulesQueueThread,      mNativeModuleRegistry.getJavaModules(this),      mNativeModuleRegistry.getCxxModules());    mJavaScriptContextHolder = new JavaScriptContextHolder(getJavaScriptContext());  }}
  • ReactCallback callback:CatalystInstanceImpl的静态内部类ReactCallback,负责接口回调。
  • JavaScriptExecutor jsExecutor:JS执行器,将JS的调用传递给C++层。
  • MessageQueueThread jsQueue.getJSQueueThread():JS线程,通过mReactQueueConfiguration.getJSQueueThread()获得,
  • Collection javaModules:java modules,来源于mJavaRegistry.getJavaModules(this)。
  • Collection cxxModules):c++ modules,来源于mJavaRegistry.getCxxModules()。

CatalystInstanceImpl 创建好,调用 runJSBundle 来加载js

CatalystInstanceImpl.runJSBundle()

这个代码最后会调用,初始化创建 ReactInstanceManager -> createReactContext 传入的 JSBundleLoader bundleLoader 上的

12
// 调用加载器加载JS Bundle,不同情况下加载器各不相同。mJSBundleLoader.loadScript(CatalystInstanceImpl.this);

bundleLoader 由 ReactInstanceManagerBuilder.setJSBundleFile(String jsBundleFile) 创建而来

12345678910111213
public ReactInstanceManagerBuilder setJSBundleFile(String jsBundleFile) {    if (jsBundleFile.startsWith("assets://")) {        mJSBundleAssetUrl = jsBundleFile;        mJSBundleLoader = null;        return this;    }    return setJSBundleLoader(JSBundleLoader.createFileLoader(jsBundleFile));}public ReactInstanceManagerBuilder setJSBundleLoader(JSBundleLoader jsBundleLoader) {    mJSBundleLoader = jsBundleLoader;    mJSBundleAssetUrl = null;    return this;}

JSBundleLoader 提供了多种加载方案,通过 ReactInstanceManagerBuilder.setJSBundleFile(String jsBundleFile) 调用的加载器如下:

12345678910111213141516171819
public abstract class JSBundleLoader {  public static JSBundleLoader createFileLoader(final String fileName) {    return createFileLoader(fileName, fileName, false);  }  public static JSBundleLoader createFileLoader(      final String fileName,      final String assetUrl,      final boolean loadSynchronously) {    return new JSBundleLoader() {      @Override      public String loadScript(CatalystInstanceImpl instance) {        instance.loadScriptFromFile(fileName, assetUrl, loadSynchronously);        return fileName;      }    };  }  /** Loads the script, returning the URL of the source it loaded. */  public abstract String loadScript(CatalystInstanceImpl instance);}

可以看到,在这种加载器下,最后调用的是 CatalystInstanceImpl 上的 loadScriptFromFile

12345678
public class CatalystInstanceImpl implements CatalystInstance {  private native void jniLoadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously);  void loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously) {    mSourceURL = sourceURL;    // C++ 方法    jniLoadScriptFromFile(fileName, sourceURL, loadSynchronously);  }}

CatalystInstanceImpl.java 最终还是调用C++层的 CatalystInstanceImpl.cpp去加载JS Bundle。

接下来就是 C++ 部分了,不太会了呢。

ReactInstanceManager.setupReactContext(final ReactApplicationContext reactContext)

createContext 调用完毕后,C++ 会回调了 setupReactContextRunnable 线程,该线程调用的就是 setupReactContext 方法。代码如下

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
public class ReactInstanceManager {  private void setupReactContext(final ReactApplicationContext reactContext) {    Log.d(ReactConstants.TAG, "ReactInstanceManager.setupReactContext()");    ReactMarker.logMarker(PRE_SETUP_REACT_CONTEXT_END);    ReactMarker.logMarker(SETUP_REACT_CONTEXT_START);    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "setupReactContext");    synchronized (mAttachedRootViews) {      synchronized (mReactContextLock) {        mCurrentReactContext = Assertions.assertNotNull(reactContext);      }      CatalystInstance catalystInstance =          Assertions.assertNotNull(reactContext.getCatalystInstance());      // 执行Native Java module的初始化      catalystInstance.initialize();      // 重置DevSupportManager的ReactContext      mDevSupportManager.onNewReactContextCreated(reactContext);      // 内存状态回调设置      mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);      // 复位生命周期 (和这个有关 LifecycleState.RESUMED)      moveReactContextToCurrentLifecycleState();      ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_START);      for (ReactRootView rootView : mAttachedRootViews) {        attachRootViewToInstance(rootView);      }      ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_END);    }    ReactInstanceEventListener[] listeners =      new ReactInstanceEventListener[mReactInstanceEventListeners.size()];    final ReactInstanceEventListener[] finalListeners =        mReactInstanceEventListeners.toArray(listeners);    UiThreadUtil.runOnUiThread(        new Runnable() {          @Override          public void run() {            for (ReactInstanceEventListener listener : finalListeners) {              listener.onReactContextInitialized(reactContext);            }          }        });    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);    ReactMarker.logMarker(SETUP_REACT_CONTEXT_END);    reactContext.runOnJSQueueThread(        new Runnable() {          @Override          public void run() {            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);            ReactMarker.logMarker(CHANGE_THREAD_PRIORITY, "js_default");          }        });    reactContext.runOnNativeModulesQueueThread(        new Runnable() {          @Override          public void run() {            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);          }        });  }}

ReactInstanceManager.attachRootViewToInstance 方法,重置 ReactRootView 内容,然后将ReactRootView作为根布局,作为根布局进行绘制。随后调用 rootView.setRootViewTag(rootTag); 设置内相关内容,调用 rootView.runApplication() 启动 js。

rootView.runApplication 里面就是包装了一些启动参数 launchOptions 与 模块名 jsAppModuleName。然后最终调用了

catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams) 方法:

检查下这个代码:

123456789
/** * JS module interface - main entry point for launching React application for a given key. */public interface AppRegistry extends JavaScriptModule {  void runApplication(String appKey, WritableMap appParameters);  void unmountApplicationComponentAtRootTag(int rootNodeTag);  void startHeadlessTask(int taskId, String taskKey, WritableMap data);}

根据注释我们知道这有可能就是js层暴露给java的接口方法。?? (AppRegister.js)

原文:大专栏  ReactNative 详解(四) 源码之RN启动过程


上一篇:Oracle分页SQL


下一篇:Oracle的查询-分页查询