RN4A運行環(huán)境的創(chuàng)建流程
前言
國內近年來對ReactNaitve討論的火爆程度不言而喻肄鸽,可能你都已經用了一段時間的RN4A了。不過你是否清楚RN4A是如何初始化一個環(huán)境油啤?Js是何時通知Native渲染UI組件赏参?從RN4A的環(huán)境初始化到ReactView呈現到UI的時候真仲,RN4A都干了什么芥被?別急葫笼,接下來就為你揭曉這些問題的答案。(PS: 本文源碼分析基于當前最新ReactNative源碼 v0.40.0-rc.4础废,限于作者水平有限汛骂,如果有錯誤和理解不當之處感謝指出。)
總體流程
為了接下來細節(jié)分析的時候你心中有個整體印象评腺,我們先直接說下RN4A的框架初始化的總體流程帘瞭。(PS:根據項目實際接入RN4A的方式不同,流程可能有所不同蒿讥,這里只是官方使用的一種初始化流程蝶念。)
創(chuàng)建ReactRootView -> 創(chuàng)建ReactInstanceManager -> 創(chuàng)建ReactContext -> RN4A環(huán)境初始化完成 -> 通知Js渲染界面。
萬物之始ReactRootView
俗話說"擒賊先擒王"芋绸,一般分析代碼都會從源頭走起媒殉。如果你查看RN4A的接入文檔,就知道RN4A已經為我們封裝好了ReactActiviy
類摔敛,只要通過繼承它廷蓉,你可以省掉RN4A與Activity之間的絕大部分邏輯交互,包括生命周期回調马昙,以及發(fā)送消息通知Js渲染UI的操作等等桃犬。通過源碼我們可以看到ReactActivity
中所有的邏輯都是由ReactActivityDelegate
類來代理刹悴,這是一個不錯的設計方式,你可以輕松地把ReactActivityDelegate
集成到你自己的Activity中攒暇,自由定制RN4A環(huán)境的初始化方案土匀。好的,有點扯遠了形用,現在讓我們來看下ReactActivityDelegate
中的關鍵方法吧就轧。
protected void onCreate(Bundle savedInstanceState) {
//省略判斷懸浮窗權限
...
if (mMainComponentName != null && !needsOverlayPermission) {
loadApp(mMainComponentName);
}
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
我們知道onCreate
方法就是Activity開始執(zhí)行的地方,ReactActivityDelegate
的onCreate
方法自然也會在Activity的onCreate
中調用田度,這里可以看到钓丰,代碼會判斷當前App是否有顯示懸浮層的權限,然后開始調用loadApp
方法每币,注意,這里就是RN4A官方方式加載的入口了琢歇。我們接下來繼續(xù)跟蹤下去:
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
方法創(chuàng)建了一個RN4A的根View(ReactRootView
),所有RN4A的View都會創(chuàng)建在ReactRootView
里李茫,然后將ReactRootView
當成了Activity的內容布局揭保,一般將ReactRootView
作為Activity的內容布局是比較省事的方式,當然魄宏,你也可以將它作為某個ViewGroup
的子View秸侣,只不過這種方式你很容易會踩到一些坑,比如你需要處理RN的View和原生View之間的事件沖突宠互。好了味榛,那我們接著看代碼,我們看下關鍵的startReactApplication
方法予跌,這里注意一下它的形參,第一個參數需要一個ReactInstanceManager
的實例搏色,ReactNativeHost
的getReactInstanceManager
這個方法會創(chuàng)建一個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的創(chuàng)建当娱、測量、更新等各種臟活)考榨; -
initialLifecycleState
-ReactInstanceManager
實例初始化時候的生命周期跨细; -
reactPackage
- 自定義的ReactNative包: -
jsBundleFile
- 放在手機文件系統中的JsBundle的文件路徑; -
bundleAssetName
- 內置在Assets目錄下的JsBundle文件河质,如果設置了則不會走其他的JsBundle加載方式冀惭,需要注意在jsBundleFile
有值的情況下不會生效;
看完ReactInstanceManager
的創(chuàng)建,我們再返回到之前loadApp
方法處掀鹅,繼續(xù)跟蹤ReactRootView
的startReactApplication
方法:
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環(huán)境的創(chuàng)建和初始化散休,其中RN4A橋的創(chuàng)建和Js腳本的加載都是在這里面進行的;而另一個attachToReactInstanceManager
則將ReactRootView
實例與ReactInstanceManager
實例綁定起來乐尊,并從Native發(fā)送runApplication
消息到Js戚丸,Js收到消息便會開始執(zhí)行相應的業(yè)務,這里需要注意的是扔嵌,如果ReactInstanceManager
是第一次創(chuàng)建的話由于它的內部還沒有創(chuàng)建好RN4A上下文實例(ReactContext
)限府,Native此時并不會發(fā)送runApplication
消息給Js,而是將這個操作放在RN4A所有的環(huán)境創(chuàng)建完成之后才被執(zhí)行痢缎,這里只是先提下胁勺,下面還會說到。
接著我們看下mReactInstanceManager.createReactContextInBackground
這個方法独旷,由源碼可知XReactInstanceManagerImpl
是ReactInstanceManager
的唯一實現類署穗,所以你可以一直跟蹤到下面的代碼,
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
方法就行了案疲,不過這里還是需要簡單說下這一處源碼的邏輯。執(zhí)行過程大概是這樣的麻养,如果你啟用了RN4A的Dev支持络拌,并且Js模塊名(JsModuleName)不是空的,RN4A就會判斷你本地是否有最新的JsBundler文件回溺,如果有的話就直接讀取本地的JsBundle文件春贸,否則會從你的LocalServer中加載JsBundle文件,這里為了分析源碼方便遗遵,我們先假設RN4A是處在Release環(huán)境中執(zhí)行的萍恕,所以我們就直接走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)來執(zhí)行初始化操作允粤,閱讀源碼可以知道RN4A先判斷當前有沒有ReactContextInitAsyncTask在進行,如果有的話,RN4A會將本次的初始化參數存放到initParams
全局變量类垫,等ReactContextInitAsyncTask初始化完成之后再去重新執(zhí)行初始化操作司光,如果當前沒有ReactContextInitAsyncTask任務在執(zhí)行,則直接新建一個ReactContextInitAsyncTask任務并開始執(zhí)行初始化操作悉患。我們接著跟進源碼:
/*
* 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的上下文環(huán)境,持有了UI售躁、Js和Native三線程坞淮,并維持了一個和Js通信的橋等),然后在異步任務線程池中RN4A會開始創(chuàng)建一個新的RN4A上下文實例(ReactContext
)陪捷,并在執(zhí)行結束之后設置這個RN4A上下文實例(ReactContext
)回窘;當RN4A執(zhí)行完成之后如果發(fā)現有initParams
(上文提到的參數),就會重新開始執(zhí)行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;
}
上面的代碼看著有點長,不過邏輯也就這么幾步:
- 生成一個RN4A上下文實例(
ReactContext
)苍碟,并在開啟Dev模式情況下設置一個Native異常處理器付枫,用于捕獲三個線程(UI、Js和Native)中發(fā)生的異常驰怎; - 處理RN包,包括核心包(CorePackage)以及注冊的自定義包(如官方提供的MainPackage)二打,NativeModule信息被存放到
moduleSpecs
和reactModuleInfoMap
中县忌,而JsModule則被放到jsModulesBuilder
中; - 通過橋構造器(
CatalystInstanceImpl.Builder
)構建出一個CatalystInstance
實例继效,它在RN4A中負責中掌管Js和Native之間的通信症杏; - 將創(chuàng)建好的橋(CatalystInstance)放到RN4A上下文(
ReactContext
)中并進行初始化,這個過程實際上只是讓RN4A上下文(ReactContext
)持有三個關鍵線程管理實例(UI瑞信、JS和Native)厉颤,RN4A的全部工作依賴這三條線程之間的相互協作; - 最后就是運行在橋實例(
CatalystInstance
)中的JsBundle了凡简,這里會從Jni層去調用Js引擎解釋執(zhí)行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會去執(zhí)行一次橋CatalystInstance
的初始化邏輯筐眷,并把初始化完成的消息發(fā)送出去黎烈,比如通知注冊在橋的Module執(zhí)行初始化的一些操作,告訴綁定的ReactRootView
可以通過attachMeasuredRootViewToInstance
方法通知Js"真正"執(zhí)行業(yè)務邏輯了,之后Js就會開始通過一系列的消息指揮Native渲染展示UI等等照棋。
結語
至此资溃,RN4A從環(huán)境初始化到RN界面展示出來所經過的一個流程我們已經走了一遍,這篇文章只是分析RN4A框架的開篇烈炭,Native和Js之間的通信方式溶锭,Js Dom的解析渲染等等,會在后續(xù)的文章中分析梳庆,敬請期待暖途。