React Native startReactApplication 简析React Native startReactApplication 方法
Yutoti_三石 人气:0在 React Native 启动流程简析 这篇文章里,我们梳理了 RN
的启动流程,最后的 startReactApplication
由于相对复杂且涉及到最终执行前端 js
的流程,我们单独将其提取出来,独立成文加以分析。
首先来看 startReactApplication
的调用之处:
mReactRootView.startReactApplication( getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
可以看到是在 rootView
上调用 startReactApplication
,入参为 instanceManager、appKey、mLaunchOptions
。
顺着 startReactApplication
扒出其调用链:
mReactInstanceManager.createReactContextInBackground() -> recreateReactContextInBackgroundInner() -> recreateReactContextInBackgroundFromBundleLoader() -> recreateReactContextInBackground() -> runCreateReactContextOnNewThread()
recreateReactContextInBackground
为 ReactInstanceManager
中的方法,做了两件事:
1.创建 ReactContextInitParams
实例 initParams
,如下,其入参 jsExecutorFactory
为创建 ReactInstanceManager
时传入。
final ReactContextInitParams initParams = new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
2.调用 runCreateReactContextOnNewThread
runCreateReactContextOnNewThread
为 ReactInstanceManager
中的方法,主要做了两件事:
- 创建一个新的线程,并在新线程中通过
createReactContext
创建ReactContext
上下文; - 通过
setupReactContext
来设置上下文环境,并最终调用到AppRegistry.js
启动App。
createReactContext
先看其调用的地方:
final ReactApplicationContext reactApplicationContext = createReactContext( initParams.getJsExecutorFactory().create(), initParams.getJsBundleLoader());
其两个入参分别为 JsExecutorFactory
创建的 JavaScriptExecutor
实例,和 JsBundleLoader
实例。
JavaScriptExecutor
startReactApplication
第一个入参为 getReactNativeHost().getReactInstanceManager()
获取 ReactInstanceManager
实例。ReactInstanceManager
实例在 RN 应用中只有一个,先前在创建 MainActivity
时已创建。
回顾 React Native 启动流程简析,在创建过程中实际上是调用下面的方法:
ReactInstanceManager reactInstanceManager = builder.build()
builder
即 ReactInstanceManagerBuilder
,我们来到该类的 build
方法,发现其最终是执行 return new ReactInstanceManager(...)
,在构造参数中第 4 个参数即为:getDefaultJSExecutorFactory
,来到其定义处:
private JavaScriptExecutorFactory getDefaultJSExecutorFactory( String appName, String deviceName, Context applicationContext) { try { // If JSC is included, use it as normal initializeSoLoaderIfNecessary(applicationContext); SoLoader.loadLibrary("jscexecutor"); return new JSCExecutorFactory(appName, deviceName); } catch (UnsatisfiedLinkError jscE) { /* ... */ } }
也就是说在创建 ReactInstanceManagerBuilder
时我们就创建了 JSCExecutorFactory
,并在随后调用其 create
方法创建 JSCExecutor
。JSCExecutorFactory
实现了 JavaScriptExecutorFactory
接口,其 create
方法如下,返回了 JSCExecutor
实例:
@Override public JavaScriptExecutor create() throws Exception { WritableNativeMap jscConfig = new WritableNativeMap(); jscConfig.putString("OwnerIdentity", "ReactNative"); jscConfig.putString("AppIdentity", mAppName); jscConfig.putString("DeviceIdentity", mDeviceName); return new JSCExecutor(jscConfig); }
再往下看 JSCExecutor
的定义,其继承自 JavaScriptExecutor
类:
@DoNotStrip /* package */ class JSCExecutor extends JavaScriptExecutor { static { SoLoader.loadLibrary("jscexecutor"); } /* package */ JSCExecutor(ReadableNativeMap jscConfig) { super(initHybrid(jscConfig)); } @Override public String getName() { return "JSCExecutor"; } private static native HybridData initHybrid(ReadableNativeMap jscConfig); }
于是就很清楚了,createReactContext
第一个参数为 JSCExecutor
实例,是通过 SoLoader
加载的 C++ 模块。
JsBundleLoader
同样的,在 return new ReactInstanceManager(...)
,其构造参数中第 5 个参数为:JSBundleLoader.createAssetLoader(mApplication, mJSBundleAssetUrl, false)
来到其定义之处,发现其返回了 JSBundleLoader
实例,并重写了其 loadScript
方法。
public static JSBundleLoader createAssetLoader( final Context context, final String assetUrl, final boolean loadSynchronously) { return new JSBundleLoader() { @Override public String loadScript(JSBundleLoaderDelegate delegate) { delegate.loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously); return assetUrl; } }; }
在创建完 JSCExecutor
实例和 JSBundleLoader
实例后,正式进入到 createReactContext
方法。
createReactContext
private ReactApplicationContext createReactContext( final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext); CatalystInstanceImpl.Builder catalystInstanceBuilder = /* ... */ try { catalystInstance = catalystInstanceBuilder.build(); } finally { /* ... */ } reactContext.initializeWithInstance(catalystInstance); TurboModuleManager turboModuleManager = new TurboModuleManager( /* ... */ ) catalystInstance.setTurboModuleManager(turboModuleManager); if (mJSIModulePackage != null) { catalystInstance.addJSIModules( /* ... */ ); } catalystInstance.runJSBundle(); return reactContext;
在这里面,首先创建了 reactContext
,并通过 catalystInstanceBuilder
创建了 catalystInstance
。接着通过 initializeWithInstance
方法将 reactContext
和 catalystInstance
关联起来,并进行了一系列的为 catalystInstance
初始化的工作。最后进入到方法 catalystInstance.runJSBundle()
中。
initializeWithInstance
通过调用 getUIQueueThread
、getNativeModulesQueueThread
、getJSQueueThread
创建了3个线程队列,分别是 UI线程、NativeModules 线程,和 JS 线程。
runJSBundle
public void runJSBundle() { mJSBundleLoader.loadScript(CatalystInstanceImpl.this); synchronized (mJSCallsPendingInitLock) { mAcceptCalls = true; for (PendingJSCall function : mJSCallsPendingInit) { function.call(this); } mJSCallsPendingInit.clear(); mJSBundleHasLoaded = true; } Systrace.registerListener(mTraceListener); }
通过先前返回的 mJSBundleLoader
执行其 loadScript
方法:
public String loadScript(JSBundleLoaderDelegate delegate) { delegate.loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously); return assetUrl; }
loadScriptFromAssets
方法在 CatalystInstanceImpl
中:
public void loadScriptFromAssets( AssetManager assetManager, String assetURL, boolean loadSynchronously) { mSourceURL = assetURL; jniLoadScriptFromAssets(assetManager, assetURL, loadSynchronously); }
这里的 assetURL
是在 createAssetLoader
创建 mJSBundleLoader
时传入,其赋值时机是在 reactInstanceManagerBuilder
实例中,由 reactNativeHost
实例的 createReactInstanceManager
方法。若 开发者在 MainApplication.java
中通过重写 getJSBundleFile
方法自定义了 assetURL
则使用该 url,否则使用系统默认,如:file://sdcard/myapp_cache/index.android.bundle
。
jniLoadScriptFromAssets
方法为 C++ 侧定义的方法,用于读取 js 文件。为什么 Java 代码中可以直接调用 C++ 方法,这里还要打个问号,后续在分析 Java 与 C++ 通信及 Java 与 JS 通信时阐释。
通过 createReactContext
创建了 reactContext
,创建了 catalystInstance
实例,并将上述两者关联,接着通过 catalystInstance
读入 js 文件。接下来就进入到 setupReactContext
的环节。
setupReactContext
private void setupReactContext(final ReactApplicationContext reactContext) { synchronized (mAttachedReactRoots) { catalystInstance.initialize(); for (ReactRoot reactRoot : mAttachedReactRoots) { if (reactRoot.getState().compareAndSet(ReactRoot.STATE_STOPPED, ReactRoot.STATE_STARTED)) { attachRootViewToInstance(reactRoot); } } } UiThreadUtil.runOnUiThread( public void run() { listener.onReactContextInitialized(reactContext); } ) reactContext.runOnJSQueueThread( public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } ) reactContext.runOnNativeModulesQueueThread( public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } ) }
这里面做的事情如下:
- catalystInstance.initialize(): 所有原生模块的初始化
- attachRootViewToInstance(reactRoot): 绘制所有的 RootView 并添加到相应实例并设置相应的监听事件
- 创建 UI 模块、JS 模块和原生模块线程,并设置 JS 模块和原生模块所在线程的优先级
总结本文
从 createReactContext 和 setupReactContext 两个方法的源码出发,分析了 RN startReactApplication 方法的执行过程,其中:
createReactContext 的主要作用是:创建 reactContext
、创建 catalystInstance
实例,并将上述两者关联,接着通过 catalystInstance
读入 js 文件。
setupReactContext的主要作用是:初始化所有原生模块,绘制所有 rootview,创建 UI 模块、JS 模块和原生模块线程,并设置优先级。
加载全部内容