React Native Android 原始碼框架淺析(主流程及 Java 與 JS 雙邊通訊)
1 背景
有了前面《React Native Android 從學車到補胎和成功發車經歷》和《React Native Android Gradle 編譯流程淺析》兩篇文章的學習我們 React Native 已經能夠基本接入處理一些事情了,那接下來的事情就是漸漸理解 RN 框架的一些東西,以便裁剪和對 RN 有個更深入的認識,所以本篇總結了我這段時間閱讀原始碼的一些感觸,主要總結了 React Native 啟動流程、JS 呼叫 Java 流程、Java 呼叫 JS 流程。
涉及到原始碼分析了,所以有必要先交代下相關原始碼版本,以便引來不必要疑惑,如下:
"dependencies" : {
"react": "15.3.2",
"react-native": "0.37.0"
}
首先通過前面的踩坑經歷和編譯流程淺析(編譯流程已經暴露很多細節)我們能意識到 React Native 的大致框架流程應該是如下這樣的:
也就是說其實需要我們編寫程式碼是 Java 端(少)和 JS 端(多),其他的基本不變的,作為橋樑的核心是 C/C++ 來處理的,同時 JS(JSX)程式碼又是通過 Virtual DOM 來進行虛擬適應的,所以才有了 React Native 官方放話的 Learn once, do anywhere. 之說。下面我們就來解析下這個神奇的 React Native Android 框架吧。
2 RN 啟動流程框架淺析
還記得我們在《React Native Android 從學車到補胎和成功發車經歷》中是怎麼整合的 RN 嗎?整合 RN 無非就是通過繼承 ReactActivity 或者自己通過 ReactRootView 進行處理,但是實質都是觸發了 ReactRootView 的 startReactApplication 方法,所以我們整個啟動流程的核心入口就是這玩意;下面為了一致,我們直接從 ReactActivityDelegate 類的 onCreate 方法進行啟動分析(分析原始碼本來就比較枯燥,堅持看下去收穫是巨大的),如下:
public class ReactActivityDelegate {
protected void onCreate(Bundle savedInstanceState) {
//許可權彈窗判斷
if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
......
}
//啟動流程一定會執行的,mMainComponentName為我們設定的,與JS邊保持一致
if (mMainComponentName != null) {
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());
//把View設定進Activity
getPlainActivity().setContentView(mReactRootView);
}
}
可以看見,ReactActivityDelegate 只是一個抽出來的封裝,上面的實質就是 new 了一個 ReactRootView(實質是 Android 的 FrameLayout),接著呼叫 ReactRootView 的 startReactApplication 方法,完事就像常規 Android 程式碼一樣直接通過 Activity 的 setContentView 方法把 View 設定進去。所以可以看出來,RN 的神祕之處一定在於 ReactRootView 中,Activity 對於 RN 來說只是為了讓 RN 依附符合 Android 的框架而已,所以說,說白了 RN 依舊是標準 Android,因此在我們整合開發中我們可以選擇整個介面(包含多級跳轉)都用 React Native 實現,或者一個 Android 現有介面中部分採用 React Native 實現,因為這貨就是一個 View,愛咋咋地,具體如下所示:
既然明白了 RN 就是個 View,那就接著看看 ReactRootView 唄,如下:
/**
React Native 的 Root View,負責監聽標準 Android 的 View 相關各種東東及事件分發和子 View 渲染等。
*/
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {
public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName) {
startReactApplication(reactInstanceManager, moduleName, null);
}
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle launchOptions) {
UiThreadUtil.assertOnUiThread();
......
//標記判斷,初始化會走進來的
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();
}
}
}
可以看見,ReactRootView 果然是個牛逼的類,我也不多解釋了,大段的英文註釋已經交代很清楚用途和地位了,我們直接看上面程式碼的 startReactApplication 方法吧,可以看見他又呼叫了一個三個引數的同名方法,具體這三個引數來歷如下(也是我們自己整合 RN 時手動 builder 模式建立的):
1. reactInstanceManager: 大內總管介面類,提供一個構造者模式的初始化 Builder,實現類是 XReactInstanceManagerImpl,這類也是我們在整合 RN 時 new ReactRootView 的之前自己建立的。
2. moduleName: 與 JS 程式碼約定的 String 型別識別 name,JS 端通過 AppRegistry.registerComponent 方法設定這個 name,Java 端重寫基類的 getMainComponentName 方法設定這個 name,這樣兩邊入口就對上了。
3. launchOptions: 這裡預設是 null 的,如果自己不繼承 ReactActivity 而自己實現的話可以通過這個引數在 startActivity 時傳入一些引數到 JS 程式碼,用來依據引數初始化 JS 端程式碼。
這些引數都初始化傳遞好了以後,可以看見接著呼叫了 mReactInstanceManager 的 createReactContextInBackground 方法,mReactInstanceManager 就是上面說的第一個引數,實質是通過一個構造者模式建立的,實現類是 XReactInstanceManagerImpl,所以我們直接跳到 XReactInstanceManagerImpl 的 createReactContextInBackground 方法看看,如下:
public void createReactContextInBackground() {
......
recreateReactContextInBackgroundInner();
}
private void recreateReactContextInBackgroundInner() {
UiThreadUtil.assertOnUiThread();
if (mUseDeveloperSupport && mJSMainModuleName != null) {
//如果是 dev 模式,BuildConfig.DEBUG=true就走這裡,線上更新bundle,手機晃動出現除錯選單等等。
//這個路線屬於RN除錯流程原理,後面再寫文章分析,這裡我們抓住主線分析
......
return;
}
//非除錯模式,即BuildConfig.DEBUG=false時執行
recreateReactContextInBackgroundFromBundleLoader();
}
private void recreateReactContextInBackgroundFromBundleLoader() {
//厲害了,word哥,在後臺建立ReactContext,兩個引數是重點。
//mJSCConfig.getConfigMap()預設是一個WritableNativeMap,在前面通過構造模式構造時通過Builder類的set方法設定。
//mJSBundleLoader是在前面通過構造模式構造時通過Builder類的多個setXXX方法均可設定的,
//最終在Builder中build方法進行判斷處理,你可以自定義Loader,或者按照build方法規則即可,
//預設是JSBundleLoader.createAssetLoader靜態方法返回的JSBundleLoader抽象類的實現類。
//自定義熱更新時setJSBundleFile方法引數就是巧妙的利用這裡是走JSBundleLoader.createAssetLoader還是JSBundleLoader.createFileLoader!!!!!!
recreateReactContextInBackground(
new JSCJavaScriptExecutor.Factory(mJSCConfig.getConfigMap()),
mBundleLoader);
}
private void recreateReactContextInBackground(
JavaScriptExecutor.Factory jsExecutorFactory,
JSBundleLoader jsBundleLoader) {
UiThreadUtil.assertOnUiThread();
//封裝一把,把兩個引數封裝成ReactContextInitParams物件
ReactContextInitParams initParams =
new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
if (mReactContextInitAsyncTask == null) {
//初始化進來一定會走啦,這貨不就是建立一個AsyncTask,然後執行,同時傳遞封裝的引數initParams給task。
// 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;
}
}
/**
* Path to the JS bundle file to be loaded from the file system.
*
* Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle"}
*/
public Builder setJSBundleFile(String jsBundleFile) {
if (jsBundleFile.startsWith("assets://")) {
mJSBundleAssetUrl = jsBundleFile;
mJSBundleLoader = null;
return this;
}
return setJSBundleLoader(JSBundleLoader.createFileLoader(jsBundleFile));
}
我們先記住這個提示,後面再邊分析主載入流程邊插入介紹熱更新的原理,所以我們還是把思路先回到 XReactInstanceManagerImpl 內部類的 ReactContextInitAsyncTask 上,如下:
private final class ReactContextInitAsyncTask extends
AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> {
......
@Override
protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
......
try {
//非同步執行的重量級核心方法createReactContext,建立ReactContext
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,設定ReactContext相關
setupReactContext(result.get());
} catch (Exception e) {
mDevSupportManager.handleException(e);
} finally {
mReactContextInitAsyncTask = null;
}
......
}
......
}
可以看見,這就是典型的 AsyncTask 用法,我們先關注 doInBackground 方法,onPostExecute 方法等會回頭再看;doInBackground 中首先把上面封裝的 ReactContextInitParams 物件裡 JavaScriptExecutor.Factory 工廠物件拿到,接著呼叫了工廠類的 create 方法建立 JavaScriptExecutor 抽象類的實現類 JSCJavaScriptExecutor 物件(因為上面分析 recreateReactContextInBackground 方法時第一個引數傳入的是 new JSCJavaScriptExecutor.Factory(mJSCConfig.getConfigMap()))。接著往下執行了 createReactContext 方法,兩個引數分別是前面封裝的 ReactContextInitParams 物件中的 JSCJavaScriptExecutor 例項和 JSBundleLoader.createAssetLoader 靜態方法建立的匿名內部類 JSBundleLoader 物件(熱更新的話可能是另一個 Loader,參見前面分析);createReactContext 方法如下(有點長,但是句句核心啊):
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
......
//這貨預設不就是上面剛剛分析的"assets://" + bundleAssetName麼
mSourceUrl = jsBundleLoader.getSourceUrl();
List<ModuleSpec> moduleSpecs = new ArrayList<>();
Map<Class, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
//!!!Js層模組登錄檔,通過它把所有的JavaScriptModule註冊到CatalystInstance。我們自定義的繼承JavaScriptModule介面的Java端也是通過他來管理。
JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();
//ContextWrapper封裝類,其實就是getApplicationContext的封裝,用在ReactContext中
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
//如果是開發模式下ReactApplicationContext中有崩潰就捕獲後交給mDevSupportManager處理(出錯時彈個紅框啥玩意的都是這貨捕獲的功勞)
if (mUseDeveloperSupport) {
//mDevSupportManager例項物件來源於XReactInstanceManagerImpl構造方法中一個工廠方法,實質由useDeveloperSupport決定DevSupportManager是哪個例項。
//非開發模式情況下mDevSupportManager為DisabledDevSupportManager例項,開發模式下為DevSupportManagerImpl例項。
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
}
......
try {
//建立CoreModulesPackage(ReactPackage),RN framework的核心Module Package,主要通過createNativeModules、createJSModules和createViewManagers等方法建立本地模組,JS模組及檢視元件等。
//CoreModulesPackage封裝了通訊、除錯等核心類。
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
//當我們設定mLazyNativeModulesEnabled=true(預設false)後啟動可以得到延遲載入,感覺沒啥卵用,沒整明白有何用意。
//拼裝來自coreModulesPackage的各種module了,JS的直接add進了jsModulesBuilder對映表、Native的直接儲存在了moduleSpecs、reactModuleInfoMap中。
processPackage(
coreModulesPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
//載入我們自定義的ReactPackage,譬如自己封裝的和MainReactPackage等,mPackages就來源於我們自己定義的;整個過程同上CoreModulesPackage,進行各種拼裝module。
// TODO(6818138): Solve use-case of native/js modules overriding
for (ReactPackage reactPackage : mPackages) {
Systrace.beginSection(
TRACE_TAG_REACT_JAVA_BRIDGE,
"createAndProcessCustomReactPackage");
try {
processPackage(
reactPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
......
//!!!Java層模組登錄檔,通過它把所有的NativeModule註冊到CatalystInstance。我們自定義的繼承NativeModule介面的Java端也是通過他來管理。
NativeModuleRegistry nativeModuleRegistry;
try {
//new一個NativeModuleRegistry,其管理了NativeModule和OnBatchCompleteListener列表(JS呼叫Java結束時的回掉管理)。
nativeModuleRegistry = new NativeModuleRegistry(moduleSpecs, reactModuleInfoMap);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
}
//依據外面是否設定mNativeModuleCallExceptionHandler異常捕獲實現來決定exceptionHandler是使用外面的還是DevSupportManager。
NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
? mNativeModuleCallExceptionHandler
: mDevSupportManager;
//!!!重點建立CatalystInstance的CatalystInstanceImpl實現例項
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 {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
}
if (mBridgeIdleDebugListener != null) {
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
//關聯reactContext與catalystInstance
reactContext.initializeWithInstance(catalystInstance);
//通過catalystInstance載入js bundle檔案
catalystInstance.runJSBundle();
return reactContext;
}
可以發現,上面這段程式碼做的事情真特麼多,不過總的來說 createReactContext() 方法做的都是一些取資料組表放表的過程,核心就是通過 ReactPackage 實現類的 createNativeModules()、createJSModules() 等方法把所有 NativeModule 包裝後放入 NativeModuleRegistry 及 JavaScriptModule 包裝後放入 JavaScriptModuleRegistry,然後把這兩張對映表交給 CatalystInstanceImpl,同時包裝建立 ReactContext 物件,然後通過 CatalystInstanceImpl 的 runJSBundle() 方法把 JS bundle 檔案的 JS 程式碼載入進來等待 Task 結束以後呼叫 JS 入口進行渲染 RN。既然這樣就去看看 CatalystInstanceImpl 的 build 方法中呼叫的 CatalystInstanceImpl 構造方法到底幹了哪些鳥事,如下:
public class CatalystInstanceImpl implements CatalystInstance {
......
// C++ parts
private final HybridData mHybridData;
private native static HybridData initHybrid();
private CatalystInstanceImpl(
final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry,
final JavaScriptModuleRegistry jsModuleRegistry,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
//native C++方法,用來初始化JNI相關狀態然後返回mHybridData。
mHybridData = initHybrid();
//建立ReactNative的三個執行緒nativeModulesThread和jsThread、uiThread,都是通過Handler來管理的。
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler());
mBridgeIdleListeners = new CopyOnWriteArrayList<>();
mJavaRegistry = registry;
mJSModuleRegistry = jsModuleRegistry;
mJSBundleLoader = jsBundleLoader;
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mTraceListener = new JSProfilerTraceListener(this);
//native C++方法,用來初始化Bridge。
initializeBridge(
new BridgeCallback(this),
jsExecutor,
mReactQueueConfiguration.getJSQueueThread(),
mReactQueueConfiguration.getNativeModulesQueueThread(),
mJavaRegistry.getModuleRegistryHolder(this));
mMainExecutorToken = getMainExecutorToken();
}
private native void initializeBridge(ReactCallback callback,
JavaScriptExecutor jsExecutor,
MessageQueueThread jsQueue,
MessageQueueThread moduleQueue,
ModuleRegistryHolder registryHolder);
......
}
剛剛分析 createReactContext() 方法的總結沒錯,CatalystInstanceImpl 這貨就是個封裝總管,負責了 Java 層程式碼到 JNI 封裝初始化的任務和 Java 與 JS 呼叫的 Java 端控制中心。所以我們先看看呼叫 native initializeBridge 方法時傳入的 5 個引數吧,分別如下:
1. callback引數: CatalystInstanceImpl 的內部靜態實現類 BridgeCallback,負責相關介面回調回傳。
2. jsExecutor引數: 前面分析的 XReactInstanceManagerImpl 中賦值為 JSCJavaScriptExecutor 例項,JSCJavaScriptExecutor 中也有自己的 native initHybrid 的 C++ 方法被初始化時呼叫,具體在 OnLoad.cpp 的 JSCJavaScriptExecutorHolder 類中。
3. jsQueue引數: 來自於 mReactQueueConfiguration.getJSQueueThread(),mReactQueueConfiguration就是 CatalystInstanceImpl 中建立的 ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler()); 第一個引數來自於 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,實質為包裝相關執行緒名字、型別等,然後通過 ReactQueueConfigurationImpl 的 create 建立對應執行緒的 Handler,這裡就是名字為 js 的後臺執行緒 Handler,第二個引數為異常捕獲回撥實現。
4. moduleQueue引數: 來自於 mReactQueueConfiguration.getNativeModulesQueueThread(),mReactQueueConfiguration就是 CatalystInstanceImpl 中建立的 ReactQueueConfigurationImpl.create(
ReactQueueConfigurationSpec,
new NativeExceptionHandler()); 第一個引數來自於 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,實質為包裝相關執行緒名字、型別等,然後通過 ReactQueueConfigurationImpl 的 create 建立對應執行緒的 Handler,這裡就是名字為 native_modules 的後臺執行緒 Handler,第二個引數為異常捕獲回撥實現。
5. registryHolder引數: mJavaRegistry 物件來自於 XReactInstanceManagerImpl 中 CatalystInstanceImpl 的建造者,通過 mJavaRegistry.getModuleRegistryHolder(this) 傳遞一個 Java 層的 ModuleRegistryHolder 例項到同名的 C++ 中,具體在 mJavaRegistry.getModuleRegistryHolder(this) 的返回值處為 return new ModuleRegistryHolder(catalystInstanceImpl, javaModules, cxxModules); 而 ModuleRegistryHolder 的構造方法中呼叫了 C++ 的 initHybrid(catalystInstanceImpl, javaModules, cxxModules); 方法。
CatalystInstanceImpl 這貨會玩,自己在 Java 層直接把持住了 JavaScriptModuleRegistry 對映表,把 NativeModuleRegistry 對映表、BridgeCallback 回撥、JSCJavaScriptExecutor、js 佇列 MessageQueueThread、native 佇列 MessageQueueThread 都通過 JNI 嫁接到了 C++ 中。那我們現在先把目光轉移到 CatalystInstanceImpl.cpp 的 initializeBridge 方法上(關於 JNI 的 OnLoad 中初始化註冊模組等等就不介紹了),如下:
void CatalystInstanceImpl::initializeBridge(
jni::alias_ref<ReactCallback::javaobject> callback,
// This executor is actually a factory holder.
JavaScriptExecutorHolder* jseh,
jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
ModuleRegistryHolder* mrh) {
......
// Java CatalystInstanceImpl -> C++ CatalystInstanceImpl -> Bridge -> Bridge::Callback
// --weak--> ReactCallback -> Java CatalystInstanceImpl
......
//instance_為ReactCommon目錄下 Instance.h 中類的例項;JNI封裝規則就不介紹了,之前寫過文章的。
//第一個引數為JInstanceCallback實現類,父類在cxxreact/Instance.h中。
//第二個引數為JavaScriptExecutorHolder,實質對應java中JavaScriptExecutor,也就是上面分析java的initializeBridge方法第二個引數JSCJavaScriptExecutor。
//第三第四個引數都是java執行緒透傳到C++,純C++的JMessageQueueThread。
//第五個引數為C++的ModuleRegistryHolder的getModuleRegistry()方法。
instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback),
jseh->getExecutorFactory(),
folly::make_unique<JMessageQueueThread>(jsQueue),
folly::make_unique<JMessageQueueThread>(moduleQueue),
mrh->getModuleRegistry());
}
到此 CatalystInstance 的例項 CatalystInstanceImpl 物件也就初始化 OK 了,同時通過 initializeBridge 建立了 Bridge 連線。關於這個 Bridge 在RN 中是通過 libjsc.so 中 JSObjectRef.h 的 JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL); 來關聯的,這樣就可以在 Native 設定 JS 執行,反之同理。
由於這一小節我們只討論 RN 的載入啟動流程,所以 initializeBridge 的具體實現我們下面分析互相通訊互動時再仔細分析,故我們先把思路還是回到 XReactInstanceManagerImpl 中 createReactContext 方法的 reactContext.initializeWithInstance(catalystInstance); 一行,可以看見,這行程式碼意思就是將剛剛初始化的 catalystInstance 傳遞給全域性唯一的 reactContext 物件,同時在 reactContext 中通過 catalystInstance 拿到 JS、Native、UI 幾個 Thread 的引用,方便快速訪問使用這些物件。接著呼叫了 catalystInstance.runJSBundle(); 方法,這個方法實現如下:
@Override
public void runJSBundle() {
......
mJSBundleHasLoaded = true;
//mJSBundleLoader就是前面分析的依據不同設定決定是JSBundleLoader的createAssetLoader還是createFileLoader等靜態方法的匿名實現類。
// incrementPendingJSCalls();
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
......
}
通過註釋我們假設 Loader 是預設的,也即 JSBundleLoader 類的如下方法:
public static JSBundleLoader createAssetLoader(
final Context context,
final String assetUrl) {
return new JSBundleLoader() {
@Override
public void loadScript(CatalystInstanceImpl instance) {
instance.loadScriptFromAssets(context.getAssets(), assetUrl);
}
@Override
public String getSourceUrl() {
return assetUrl;
}
};
}
可以看見,它實質又呼叫了 CatalystInstanceImpl 的 loadScriptFromAssets 方法,我們繼續跟蹤 CatalystInstanceImpl 的這個方法吧,如下:
native void loadScriptFromAssets(AssetManager assetManager, String assetURL);
loadScriptFromAssets 既然是一個 native 方法,我們去 CatalystInstanceImpl.cpp 看下這個方法的實現,如下:
void CatalystInstanceImpl::loadScriptFromAssets(jobject assetManager,
const std::string& assetURL) {
const int kAssetsLength = 9; // strlen("assets://");
//獲取source路徑名,不計字首,這裡預設就是index.android.bundle
auto sourceURL = assetURL.substr(kAssetsLength);
//assetManager是Java傳遞的AssetManager。
//extractAssetManager是JSLoader.cpp中通過系統動態連結庫android/asset_manager_jni.h的AAssetManager_fromJava方法來獲取AAssetManager物件的。
auto manager = react::extractAssetManager(assetManager);
//通過JSLoader物件的loadScriptFromAssets方法讀檔案,得到大字串script(即index.android.bundle檔案的JS內容)。
auto script = react::loadScriptFromAssets(manager, sourceURL);
//判斷是不是Unbundle,這裡不是Unbundle,因為打包命令我們用了react.gradle的預設bundle,沒用unbundle命令(感興趣的自己分析這條路線)。
if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) {
instance_->loadUnbundle(
folly::make_unique<JniJSModulesUnbundle>(manager, sourceURL),
std::move(script),
sourceURL);
return;
} else {
//bundle命令打包的,所以走這裡。
//instance_為ReactCommon目錄下 Instance.h 中類的例項,前面分析過了。
instance_->loadScriptFromString(std::move(script), sourceURL);
}
}
看來還沒到頭,這貨又走到了 ReactCommon 目錄下 Instance 例項的 loadScriptFromString 方法去了(由此可以看出來前面 ReactNativeAndroid 目錄下的 jni 程式碼都是 Android 平臺特有的封裝,直到 ReactCommon 才是通用的),如下:
//string為index.android.bundle內容。
//sourceURL在這裡預設為index.android.bundle。
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
std::string sourceURL) {
//callback_就是initializeBridge傳進來的,實質實現是CatalystInstanceImpl的BridgeCallback。
//說白了就是回傳一個狀態,要開始搞loadScriptFromString了
callback_->incrementPendingJSCalls();
SystraceSection s("reactbridge_xplat_loadScriptFromString",
"sourceURL", sourceURL);
//厲害了,Word哥,年度大戲啊!
//nativeToJsBridge_也是Instance::initializeBridge方法裡初始化的,實現在Common的NativeToJsBridge類裡。
nativeToJsBridge_->loadApplication(nullptr, std::move(string), std::move(sourceURL));
}
媽的,沒完沒了了,繼續跟吧,到 Common 的 NativeToJsBridge.cpp 看看 loadApplication 方法吧,如下:
//unbundle傳入的是個空指標。
//startupScript為bundle檔案內容。
//startupScript為bundle檔名。
void NativeToJsBridge::loadApplication(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
//runOnExecutorQueue實質就是獲取一個MessageQueueThread,然後在其執行緒中執行一個task。
runOnExecutorQueue(
m_mainExecutorToken,
[unbundleWrap=folly::makeMoveWrapper(std::move(unbundle)),
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL=std::move(startupScriptSourceURL)]
(JSExecutor* executor) mutable {
auto unbundle = unbundleWrap.move();
if (unbundle) {
executor->setJSModulesUnbundle(std::move(unbundle));
}
//因為我們是bundle命令打包的,所以走這裡繼續執行!!!
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
});
}
靠靠靠,還不到頭,又特麼繞到 JSExecutor 的 loadApplicationScript 方法裡面去了,繼續跟吧(這個 executor 是 runOnExecutorQueue 方法中回傳的一個 map 中取的,實質是 OnLoad 中 JSCJavaScriptExecutorHolder 對應,也即 java 中 JSCJavaScriptExecutor,所以 JSExecutor 例項為 JSCExecutor.cpp 中實現),如下:
//script為bundle檔案內容,sourceURL為bundle檔名
void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) throw(JSException) {
SystraceSection s("JSCExecutor::loadApplicationScript",
"sourceURL", sourceURL);
......
//把bundle檔案和檔名等內容轉換成js可以識別的String
String jsScript = jsStringFromBigString(*script);
String jsSourceURL(sourceURL.c_str());
//使用webkit JSC去真正解釋執行Javascript了!
evaluateScript(m_context, jsScript, jsSourceURL);
//繫結橋,核心是通過getGlobalObject將JS與C++通過webkit JSC bind
bindBridge();
flush();
......
}
去他大爺的,沒完沒了了,繼續看看 bindBridge() 方法和 flush() 方法,如下:
void JSCExecutor::bindBridge() throw(JSException) {
......
auto global = Object::getGlobalObject(m_context);
auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
......
auto batchedBridge = batchedBridgeValue.asObject();
m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();
m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue").asObject();
//通過webkit JSC獲取MessageQueue.js的flushedQueue
m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject();
m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject();
}
void JSCExecutor::flush() {
SystraceSection s("JSCExecutor::flush");
//m_flushedQueueJS->callAsFunction({})即呼叫MessageQueue.js的flushedQueue方法。
//即把JS端相關通訊互動資料通過flushedQueue返回傳給callNativeModules。
callNativeModules(m_flushedQueueJS->callAsFunction({}));
}
void JSCExecutor::callNativeModules(Value&& value) {
SystraceSection s("JSCExecutor::callNativeModules");
try {
//把JS端相關通訊資料轉為JSON格式字串資料
auto calls = value.toJSONString();
//m_delegate實質為Executor.h中ExecutorDelegate類的實現類JsToNativeBridge物件。
//故callNativeModules為JsToNativeBridge.cpp中實現的方法,把calls json字串pase成格式結構。
m_delegate->callNativeModules(*this, folly::parseJson(calls), true);
} catch (...) {
......
}
}
臥槽!又繞回到了 JsToNativeBridge.cpp 的 callNativeModules 方法,那就看下吧,如下:
//executor即為前面的JSCExecutor。
//calls為被解析OK的JS端JSON通訊引數結構。
//isEndOfBatch通知是否一個批次處理OK了,這裡傳遞了true進來,說明JS檔案Loader OK了。
void callNativeModules(
JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {
//拿到token
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
//扔到nativeQueue的執行緒佇列去等待執行
m_nativeQueue->runOnQueue([this, token, calls=std::move(calls), isEndOfBatch] () mutable {
// An exception anywhere in here stops processing of the batch. This
// was the behavior of the Android bridge, and since exception handling
// terminates the whole bridge, there's not much point in continuing.
for (auto& call : react::parseMethodCalls(std::move(calls))) {
//呼叫Native registry表中的java NativeMethod方法。
m_registry->callNativeMethod(
token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
//一些類似資料庫事務操作的機制,用來告訴OK了
if (isEndOfBatch) {
m_callback->onBatchComplete();
m_callback->decrementPendingJSCalls();
}
});
}
終於尼瑪明朗了,上面這段呼叫不就是前面分析的那個回撥麼,說白了就是 CatalystInstanceImpl.java 中 CatalystInstanceImpl 構造方法中呼叫 C++ 的 initializeBridge 方法時傳入的第一個引數 BridgeCallback 麼,也就是說 JS bundle 檔案被載入完成以後 JS 端呼叫 Java 端時會觸發 Callback 的 onBatchComplete 方法,這貨最終又會觸發 OnBatchCompleteListener 介面的 onBatchComplete 方法,這不就把 JS Bundle 檔案載入完成以後回撥 Java 通知 OK 了麼,原來主要的流程是這麼回事。為了接下來不迷糊,趕緊先來一把小梳理總結,用圖說話,如下:
上面這幅圖已經囊括了我們上面那些枯燥的啟動流程的部分流程分析了,好了,從上圖可以知道我們前面貼出來的 AsyncTask 的 onPostExecute 方法還沒分析,所以我們把目光再回到 XReactInstanceManagerImpl 的那個 ReactContextInitAsyncTask 中,doInBackground 方法執行完成後返回了 Result 包裝的 reactContext,所以我們看下 onPostExecute 方法中呼叫的核心方法 setupReactContext,如下:
private void setupReactContext(ReactApplicationContext reactContext) {
......
CatalystInstance catalystInstance =
Assertions.assertNotNull(reactContext.getCatalystInstance());
//執行Native Java Module 的 initialize
catalystInstance.initialize();
//重置DevSupportManager實現類的reactContext相關
mDevSupportManager.onNewReactContextCreated(reactContext);
//記憶體狀態回撥設定
mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
//置位生命週期
moveReactContextToCurrentLifecycleState();
//核心方法!!!!
for (ReactRootView rootView : mAttachedRootViews) {
attachMeasuredRootViewToInstance(rootView, catalystInstance);
}
......
}
到此我們再追一下 mAttachedRootViews 這個列表賦值的地方吧,依舊是這個類的 attachMeasuredRootView(ReactRootView rootView) 方法,然而這個方法唯一被呼叫的地方在 ReactRootView 的 attachToReactInstanceManager() 中,再次發現 attachToReactInstanceManager 又是在 ReactRootView 已經 measure 的情況下才會觸發,所以也就是說 mAttachedRootViews 中儲存的都是 ReactRootView。那我們繼續回到 XReactInstanceManagerImpl 中 setupReactContext 方法的 attachMeasuredRootViewToInstance(rootView, catalystInstance); 裡看看,如下:
private void attachMeasuredRootViewToInstance(
ReactRootView rootView,
CatalystInstance catalystInstance) {
......
//徹底reset ReactRootView中的UI
// Reset view content as it's going to be populated by the application content from JS
rootView.removeAllViews();
rootView.setId(View.NO_ID);
//通過UIManagerModule設定根佈局為ReactRootView
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
int rootTag = uiManagerModule.addMeasuredRootView(rootView);
//設定相關tag
rootView.setRootViewTag(rootTag);
//把Java端啟動傳遞的launchOptions包裝成JS用的型別
@Nullable Bundle launchOptions = rootView.getLaunchOptions();
WritableMap initialProps = Arguments.makeNativeMap(launchOptions);
//獲取我們startReactApplication設定的JS端入口name,繼承ReactActivity的話值為getMainComponentName()設定的
String jsAppModuleName = rootView.getJSModuleName();
//包裝相關引數,rootTag告知JS端Native端的ReactRootView是哪個
WritableNativeMap appParams = new WritableNativeMap();
appParams.putDouble("rootTag", rootTag);
appParams.putMap("initialProps", initialProps);
//核心大招!!!!!React Native真正的啟動流程入口是被Java端在這裡拉起來的!!!!!!!!
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
......
}
堅持一下,分析原始碼就是個苦逼的過程,堅持下來就好了,馬上看到希望了;我們知道 AppRegistry.class 是 JS 端暴露給 Java 端的介面方法,所以 catalystInstance.getJSModule(AppRegistry.class) 實質就橋接到 JS 端程式碼去了,那就去看看 AppRegistry.js 的程式碼吧,如下:
//JS端對應程式碼,注意這個變數上面的英文已經交代很詳細啦
var AppRegistry = {
......
//我們JS端自己在index.android.js檔案中呼叫的入口就是:
//AppRegistry.registerComponent('TestRN', () => TestRN);
registerComponent: function(appKey: string, getComponentFunc: ComponentProvider): string {
runnables[appKey] = {
run: (appParameters) =>
renderApplication(getComponentFunc(), appParameters.initialProps, appParameters.rootTag)
};
return appKey;
},
......
//上面java端 AppRegistry 呼叫的 JS 端就是這個方法,索引到我們設定的appkey=TestRN字串的JS入口
runApplication: function(appKey: string, appParameters: any): void {
......
runnables[appKey].run(appParameters);
},
......
};
真他媽不容易啊,總算到頭了,原來 React Native 是這麼被啟動起來的。現在回過頭來看發現其實主啟動流程也就那麼回事,還以為很神祕嘻嘻的,現在總算被揭開了。總結一下吧,如下圖所示即為整個 React Native 載入主流成的主要情況:
到這裡 React Native 的啟動流程就分析完了,不過,我猜你看到這裡的時候一定會罵我,因為我知道上面的主流程中你會有很多疑惑,這也是我寫這篇閱讀 RN 原始碼總結最糾結的地方,因為想盡可能的將主載入流程和通訊方式分開來分析,以便做到模組化理解,但是後來發現關聯性又很強,揉一起分析更亂套,所以就有了這麼一篇很長的文章,前面就當是主流程綜述概要分析,細節在下面通訊方式分析時會繼續提及淺析,所以建議帶著上面的疑惑繼續向下看完這篇文章再回到 Part 2 RN 啟動流程框架淺析 這一部分來看一遍,這樣你的疑惑就全部揭開了。
3 RN Java 呼叫 JS 端框架淺析
這是一個悲傷的故事,看原始碼沒有伴,RN 接入也在自己一個人搞,所以搞起來總是挺慢,好在一直在堅持,原始碼也斷斷續續在工作之餘看了一個多星期,這篇文章也佔用了我一個美好的週末時光,有種說不出來的感覺,唉,不扯了,我們現在來看看 RN 中 Java 是如何呼叫 JS 程式碼的。
首先,通過上面載入流程或者以前我們自定義 Java & JS 互動模組的經歷我們知道 JS 端程式碼模組對應的 Java 端都是繼承 JavaScriptModule 來實現的(可以看上面 reactPackage.createJSModules() 方法,返回的是 JS 端給 Java 端約定好的 JS 模組 Java 實現);要說 Java 端如何呼叫 JS 端程式碼就得有個例子,我們就拿上面啟動流程中最後 CatalystInstanceImpl.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); 拉起 JS 端 index.android.js 的 JS 元件入口來分析,這就是一個典型的 Java 端呼叫 JS 端程式碼的例子,首先我們可以知道 AppRegistry.java 是繼承 JavaScriptModule 的,如下:
public interface AppRegistry extends JavaScriptModule {
void runApplication(String appKey, WritableMap appParameters);
void unmountApplicationComponentAtRootTag(int rootNodeTag);
void startHeadlessTask(int taskId, String taskKey, WritableMap data);
}
然後 AppRegistry.java 是在 CoreModulesPackage 的 createJSModules() 方法中被新增入列表的,CoreModulesPackage 又是在主啟動流程的 processPackage() 方法中被包裝後加入 JavaScriptModuleRegistry 對映表的,JavaScriptModuleRegistry 對映表又被 Java 層的 CatalystInstanceImpl 接管。所以 Java 呼叫 JS 方法都是通過 CatalystInstanceImpl.getJSModule(class).methodXXX() 來執行的(我們自己模組呼叫的話是通過 ReactContext.getJSModule(),因為 ReactContext 在主啟動流程中持有了 CatalystInstanceImpl 例項,所以 CatalystInstanceImpl 是不直接對外的),那我們就沿著這條線去觀摩一把,如下 CatalystInstanceImpl.java 的 getJSModule 方法:
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
//mMainExecutorToken來自於native C++程式碼
return getJSModule(mMainExecutorToken, jsInterface);
}
@Override
public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
//mJSModuleRegistry就是啟動流程中processPackage()方法加進去交給CatalystInstanceImpl託管的JS程式碼對映表
return Assertions.assertNotNull(mJSModuleRegistry)
.getJavaScriptModule(this, executorToken, jsInterface);
}
接著去 JavaScriptModuleRegistry 對映表中看看 getJavaScriptModule() 方法,如下:
public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
CatalystInstance instance,
ExecutorToken executorToken,
Class<T> moduleInterface) {
//module載入的快取,載入過一次且快取存在就直接從快取取
......
//獲取JavaScriptModule模組的方式,以AppRegistry模組獲取為例,略叼,動態代理生成獲取JS Module
JavaScriptModuleRegistration registration =
Assertions.assertNotNull(
mModuleRegistrations.get(moduleInterface),
"JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(executorToken, instance, registration));
instancesForContext.put(moduleInterface, interfaceProxy);
return (T) interfaceProxy;
}
從上面這段程式碼我們可以看見,getJSModule 獲取 JsMod