在 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
中的方法,做了兩件事:
-
建立
ReactContextInitParams
例項initParams
,如下,其入參jsExecutorFactory
為建立ReactInstanceManager
時傳入。final ReactContextInitParams initParams = new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
-
呼叫
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 模組和原生模組執行緒,並設定優先順序。