閱讀以前請先查看準備工做javascript
這篇開始將分析 React Native 0.56-stable 分支 在 Android 端的啓動過程是怎麼樣。java
用過 React Native 的小夥伴都知道,React Native 是可使用 JS 來編寫一份代碼,並能夠同時跑在 Android 和 iOS 兩個平臺上。那其實核心思想就是 JS 跟 Android 之間創建起一種通訊機制。react
對於 Android 調用 JS 代碼的方法有如下兩種:android
WebView
的 loadUrl()
函數WebView
的 evaluateJavascript()
函數對於 JS 調用 Android 代碼的方法有如下三種:c++
WebView
的 addJavascriptInterface()
進行對象映射WebViewClient
的 shouldOverrideUrlLoading()
方法回調攔截 urlWebChromeClient
的 onJsAlert()
、onJsConfirm()
、onJsPrompt()
方法回調攔截 JS 對話框 alert()
、confirm()
、prompt()
消息可是以上都是要基於 WebView
才能夠實現,可是 React Native 並無使用 WebView
來實現 JS 和 Android 間的通訊,而是採用 JavaScriptCore 來實現 JS 的解析。編程
那分析 React Native 的啓動過程,也就是分析 React Native 是如何讓 Android 與 JavaScriptCore 進行關聯的api
查看 React Native 項目 RNTester 下的 Android Demo。緩存
文中全部代碼實例都只會截取核心代碼,想查看完整代碼請直接查看源碼。網絡
public class RNTesterApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public String getJSMainModuleName() {
// js 入口文件地址
return "RNTester/js/RNTesterApp.android";
}
@Override
public @Nullable String getBundleAssetName() {
// js bundle打包後 放在 asset 目錄下的文件名稱
return "RNTesterApp.android.bundle";
}
...
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
};
複製代碼
再看 RNTesterActivity.java
的代碼app
public class RNTesterActivity extends ReactActivity {
...
@Override
protected String getMainComponentName() {
// 用來返回要顯示的js端的組件的名稱,這個要和 js 端註冊的 Component 名稱一一對應
return "RNTesterApp";
}
}
複製代碼
有點 React Native 的小夥伴知道,上面的 getJSMainModuleName
,getMainComponentName
,必需要跟 JS 代碼保持一直,不然運行程序會找不到對應的 JS 代碼,可是爲何要保持一直,這個疑問咱們先記下,繼續日後面看。
能夠看到 RNTesterActivity
是集成自 ReactActivity
。 ReactActivity
是 rn 中頁面顯示的入口,負責頁面的顯示。
進入 ReactActivity
的 onCreate
中發現 ReactActivity
只是一個空殼子,全部的邏輯都交給 ReactActivityDelegate
類實現,這是典型的代理模式,這樣作的好處:
FragmentActivity
也一樣可使用,不用維護兩套邏輯查看 ReactActivityDelegate
中 onCreate
發現最終是調用了 loadApp
函數
protected void loadApp(String appKey) {
...
mReactRootView = createRootView();
// rn 啓動入口
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
// ReactRootView 做爲 ReactActivity 的根 View
getPlainActivity().setContentView(mReactRootView);
}
複製代碼
這個函數主要實現兩個功能:
ReactRootView
,並將這個 view 設置爲當前 Activity 的根 viewReactRootView 繼承 FrameLayout,它主要負責 native 端事件(鍵盤事件、touch事件、頁面大小變化等)的監聽並將結果傳遞給 js 端以及負責頁面元素的從新繪製。涉及東西將多,以後會專門進行分析。
經過 startReactApplication
方法名也可知,這裏纔是 RN 啓動的入口處。
這裏開始 RN 中的關鍵類就會陸續登場了。這裏咱們先簡單進行一下相關介紹,讓讀者有個印象。這裏大部分的核心類都是採用面向接口編程的思想,括號中是接口對應的實現類。
rn 的 java 端的控制器,它主要的功能是建立和管理 CatalystInstance,ReactContext 實例並和 ReactActivity 的生命週期保持一致
jsc 橋樑接口類,爲 java 和 js 相互通訊提供環境。在 c++ 也有其對應的實現類
是 JavaScriptExecutor 的子類,是 js 執行器
bundle.js 文件加載器,在 rn 中有三種加載方式:一、加載本地文件;二、加載網絡文件,並將文件緩存;三、加載網絡文件,用於 debug 調試。前面 Demo Applicatoin 類中的 getBundleAssetName
最終也是會轉化爲 JSBundleLoader
NativeModule的包裝類,主要是爲了實現 module 的懶加載,因爲 rn 中 native module 比較多,爲了節省成本,rn 中採用時懶加載的策略,只有相應的 module 使用時才進行建立。
接口類,用於 java 調用 js 的接口,在 rn 中沒有實現類,具體如何使用後面再介紹
JavaScriptModule 的註冊表。
NativeModule 的註冊表,用於管理 NativeModule 列表。
java 暴露給 js 調用的 api 接口,若是想建立本身的 module,須要繼承這個接口。
組件配置接口類,經過 createNativeModules、createJSModules 和 createViewManagers 等API去建立本地模塊,JS 模塊及視圖組件等。 ReactPackage 分爲 rn 核心的 CoreModulesPackage 和業務方可選的基礎 MainReactPackage 類,其中 CoreModulesPackage 封裝了大部分通訊功能。
整個啓動流程重要建立實例之一就是ReactContext, ReactContext繼承於ContextWrapper,是ReactNative應用的上下文,經過getContext()去得到,經過它能夠訪問ReactNative核心類的實現。
以上就是 RN 中反覆出現的關鍵類。接着跟着源碼走
// ReactRootView.java
public void startReactApplication( ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle initialProperties) {
...
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
//
mReactInstanceManager.createReactContextInBackground();
}
attachToReactInstanceManager();
...
}
複製代碼
咱們此次先來看 attachToReactInstanceManager
函數,看代碼這個函數會在 ReactContext 建立以後纔會調用。繼續深刻發現最終到了 ReactInstanceManager 的調用
// ReactInstanceManager.java
private void attachRootViewToInstance( final ReactRootView rootView, CatalystInstance catalystInstance) {
...
// 最終調用 AppRegistry.js 的 runApplication 方法
rootView.invokeJSEntryPoint();
...
}
複製代碼
發現最終又回到了 ReactRootView 中
// ReactRootView.java
private void defaultJSEntryPoint() {
...
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
return;
}
CatalystInstance catalystInstance = reactContext.getCatalystInstance();
...
String jsAppModuleName = getJSModuleName();
// 調用 js module
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
}
複製代碼
在這裏咱們發現了一個很是眼熟的類名 AppRegistry.class
, 這個不就是在全部入口 js 中都要寫的一行代碼中的 AppRegistry
AppRegistry.registerComponent('RNTesterApp', () => RNTesterApp);
複製代碼
因此 defaultJSEntryPoint
函數最終調起了 js 的入口。 再看 defaultJSEntryPoint
函數,發現裏面同時用到了 ReactContext
, CatalystInstance
,ReactInstanceManager
。因此驗證了他們三者相互之間的重要關係。也說明了 React Native 的啓動重要的就是要建立 ReactContext
。至於 Java 是如何調用的 JS,咱們稍後再分析,如今咱們來分析 ReactContext
是如何建立的。
沿着 createReactContextInBackground
的函數流,會發現最終不論是採用加載網絡仍是本地的 bundle 文件最終都是會到達 runCreateReactContextOnNewThread
方法中
// ReactInstanceManager.java
private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) {
...
mCreateReactContextThread =
new Thread(
new Runnable() {
@Override
public void run() {
...
// 核心部分,建立 ReactContext
final ReactApplicationContext reactApplicationContext =
createReactContext(
initParams.getJsExecutorFactory().create(),
initParams.getJsBundleLoader());
mCreateReactContextThread = null;
ReactMarker.logMarker(PRE_SETUP_REACT_CONTEXT_START);
...
Runnable setupReactContextRunnable =
new Runnable() {
@Override
public void run() {
try {
// 最終會調用 attachRootViewToInstance 即調用 defaultJSEntryPoint 啓動 js 入口
setupReactContext(reactApplicationContext);
} catch (Exception e) {
mDevSupportManager.handleException(e);
}
}
};
...
UiThreadUtil.runOnUiThread(maybeRecreateReactContextRunnable);
}
});
// 啓動新線程
mCreateReactContextThread.start();
}
複製代碼
這個函數主要作了如下幾件事:
defaultJSEntryPoint
方法來啓動 js 的入口程序接下來纔是整個 RN 的硬骨頭,只要將其啃下,對 RN 底層的實現也就理解了。由於這裏主要涉及到了 C++ 層面的核心實現,且代碼實現較長,故不會將代碼都貼出來,仍是但願讀者能夠結合着源碼和這篇文章來分析這塊。
先上createReactContext
函數代碼
private ReactApplicationContext createReactContext( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
...
// 將核心 CoreModulesPackage 和 業務基礎 MainReactPackage 中的 NativeModule 添加到註冊表中
NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()) // 初始化 native 線程隊列及 js 線程隊列
.setJSExecutor(jsExecutor) // js 執行器
.setRegistry(nativeModuleRegistry) // 本地提供給 JS 調用的 NativeModule 註冊表
.setJSBundleLoader(jsBundleLoader) // bundle 信息類
.setNativeModuleCallExceptionHandler(exceptionHandler);
...
final CatalystInstance catalystInstance;
...
catalystInstance = catalystInstanceBuilder.build();
...
// 加載 js bundle 文件
catalystInstance.runJSBundle();
reactContext.initializeWithInstance(catalystInstance);
return reactContext;
}
複製代碼
這個函數主要作了如下功能:
CatalystInstanceImpl 做爲整個 RN 中舉足輕重的角色,來看一下它的構造函數。
private CatalystInstanceImpl( final ReactQueueConfigurationSpec reactQueueConfigurationSpec, final JavaScriptExecutor jsExecutor, final NativeModuleRegistry nativeModuleRegistry, final JSBundleLoader jsBundleLoader, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
// 找到於java相對應的c++類並調用其構造方法生成對象
mHybridData = initHybrid();
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);
// 調用的是 c++ 中對應的實現
initializeBridge(
new BridgeCallback(this),
jsExecutor,
mReactQueueConfiguration.getJSQueueThread(),
mNativeModulesQueueThread,
mNativeModuleRegistry.getJavaModules(this),
mNativeModuleRegistry.getCxxModules());
// JSGlobalContextRef 的內存地址
mJavaScriptContextHolder = new JavaScriptContextHolder(getJavaScriptContext());
}
複製代碼
構造函數中有兩個須要關注的地方
在繼續以前須要講解一下 Native 層的核心類
NativeToJsBridge是Java調用JS的橋樑,用來調用JS Module,回調Java
JsToNativeBridge是JS調用Java的橋樑,用來調用Java Module
native 層的 js 執行器
CatalystInstanceImpl 在 c++ 層對應的實現
能夠看做是 NativeToJsBridge 的代理類,最終的處理都是交由了 NativeToJsBridge
從這裏開始將會討論 Native 層對應的操做,上面提到 createReactContext
函數裏進行了 CatalystInstanceImpl
的初始化,而最終的核心實際上是在 Native 進行的。看一下 Native 層中 CatalystInstanceImpl
的 initializeBridge
函數
// CatalystInstanceImpl.cpp
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> nativeModulesQueue,
jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {
moduleMessageQueue_ = std::make_shared<JMessageQueueThread>(nativeModulesQueue);
// 在 native 層將 JavaNativeModule 和 CxxNativeModule 保存到 註冊表裏
moduleRegistry_ = std::make_shared<ModuleRegistry>(
buildNativeModuleList(
std::weak_ptr<Instance>(instance_),
javaModules,
cxxModules,
moduleMessageQueue_));
instance_->initializeBridge(
folly::make_unique<JInstanceCallback>(
callback,
moduleMessageQueue_),
jseh->getExecutorFactory(),
folly::make_unique<JMessageQueueThread>(jsQueue),
moduleRegistry_);
}
複製代碼
這個函數主要作了如下幾個功能:
Instance.cpp
的 initializeBridge 方法中// Instance.cpp
void Instance::initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry) {
callback_ = std::move(callback); // 含有
moduleRegistry_ = std::move(moduleRegistry);
// 在 js 線程隊列中初始化 NativeToJsBridge
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
// 初始化 NativeToJsBridge
nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
std::lock_guard<std::mutex> lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
});
...
}
複製代碼
這個函數主要就是在 js 的線程隊列中初始化 NativeToJsBridge
,上面也提升了 NativeToJsBridge
類是 Java 調用 JS 的橋樑,用來調用 JS Module,回調 Java
// NativeToJsBridge.cpp
NativeToJsBridge::NativeToJsBridge(
JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback)
: m_destroyed(std::make_shared<bool>(false))
, m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)) // 初始化 JsToNativeBrdige 打通 js 調用 native 的橋樑
, m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)) // js 的執行器
, m_executorMessageQueueThread(std::move(jsQueue)) // js 線程隊列
{}
複製代碼
NativeToJsBridge
的構造函數主要是初始化了通訊所需的關鍵類
m_delegate
是 JsToNativeBridge
,用於 JS 調用 Native 函數,和 NativeToJsBridge 一塊兒做爲鏈接 java 和 js 通訊的橋樑
m_executor
是對應於 JSCExecutor
對象,JSCExecutor 構造函數中對 js 的執行環境進行初始化,而且向 JavaScriptCore 中註冊了幾個 c++ 的方法供 js 端調用
到這裏 initializeBridge 整個函數就所有介紹完畢了, 總結一句就是在 ReactInstanceManager
的 createReactContext
函數裏初始化 CatalystInstanceImpl
時,會經過 JNI 在 Native 層中初始化 Java 與 JS 通訊所需的關鍵類,將通訊環境搭建完成。
繼續查看 createReactContext
的源碼,看到在 CatalystInstanceImpl
初始化以後會接着調用 CatalystInstanceImpl
的 runJSBundle
方法。經過函數的名字能夠直接這個方法將會真正的去加載 JS Bundle 文件。
查看 runJSBundle
的源碼發現最終是走到了 JSBundleLoader
的 loadScript
函數裏。 JSBundleLoader
上面介紹過,是來管理 JS Bundle 的加載方式,無論採用哪一種加載方式,最終都是回到CatalystInstanceImpl
進行處理。
private native void jniLoadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously);
private native void jniLoadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously);
private native void jniLoadScriptFromDeltaBundle(String sourceURL, NativeDeltaClient deltaClient, boolean loadSynchronously);
複製代碼
能夠看到 CatalystInstanceImpl
中對應上面提到的三種加載 JS Bundle 的方式,而都是在 Native 層進行處理。
僅需進行代碼追蹤,會發現三種方法都是在 NativeToJsBridge
中進行了統一處理
// NativeToJsBridge.cpp
void NativeToJsBridge::loadApplication(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue( // js 線程隊列
[bundleRegistryWrap=folly::makeMoveWrapper(std::move(bundleRegistry)),
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL=std::move(startupScriptSourceURL)]
(JSExecutor* executor) mutable {
auto bundleRegistry = bundleRegistryWrap.move();
if (bundleRegistry) {
executor->setBundleRegistry(std::move(bundleRegistry));
}
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
});
}
複製代碼
executor 對應的 JSCExecutor
在上面已經說過, 是 JS 的執行器。
// JSCExecutor.cpp
void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) {
...
//JavaScriptCore函數,執行js代碼
evaluateScript(m_context, jsScript, jsSourceURL);
...
flush();
...
}
}
複製代碼
loadApplicationScript
函數代碼較多,可是最核心的就是 flush
函數
void JSCExecutor::flush() {
SystraceSection s("JSCExecutor::flush");
if (m_flushedQueueJS) {
callNativeModules(m_flushedQueueJS->callAsFunction({}));
return;
}
// When a native module is called from JS, BatchedBridge.enqueueNativeCall()
// is invoked. For that to work, require('BatchedBridge') has to be called,
// and when that happens, __fbBatchedBridge is set as a side effect.
auto global = Object::getGlobalObject(m_context);
auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
// So here, if __fbBatchedBridge doesn't exist, then we know no native calls
// have happened, and we were able to determine this without forcing
// BatchedBridge to be loaded as a side effect.
if (!batchedBridgeValue.isUndefined()) {
// If calls were made, we bind to the JS bridge methods, and use them to
// get the pending queue of native calls.
bindBridge();
callNativeModules(m_flushedQueueJS->callAsFunction({}));
} else if (m_delegate) {
// If we have a delegate, we need to call it; we pass a null list to
// callNativeModules, since we know there are no native calls, without
// calling into JS again. If no calls were made and there's no delegate,
// nothing happens, which is correct.
callNativeModules(Value::makeNull(m_context));
}
}
複製代碼
flush
函數主要就是檢查是否已經加載過 js bundle,創建了鏈接橋樑。若是沒有就調用 bindBridge
進行鏈接。
void JSCExecutor::bindBridge() throw(JSException) {
SystraceSection s("JSCExecutor::bindBridge");
std::call_once(m_bindFlag, [this] {
// 獲取 js 中的 global 對象
auto global = Object::getGlobalObject(m_context);
// 獲取存儲在 global 中的 MessageQueue 對象
auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
if (batchedBridgeValue.isUndefined()) {
auto requireBatchedBridge =
global.getProperty("__fbRequireBatchedBridge");
if (!requireBatchedBridge.isUndefined()) {
batchedBridgeValue = requireBatchedBridge.asObject().callAsFunction({});
}
if (batchedBridgeValue.isUndefined()) {
throw JSException(
"Could not get BatchedBridge, make sure your bundle is packaged correctly");
}
}
// 在 native 中保存 MessageQueue 關鍵的函數對象
auto batchedBridge = batchedBridgeValue.asObject();
m_callFunctionReturnFlushedQueueJS =
batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();
m_invokeCallbackAndReturnFlushedQueueJS =
batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue")
.asObject();
m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject();
m_callFunctionReturnResultAndFlushedQueueJS =
batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue")
.asObject();
});
}
複製代碼
這個函數主要實現如下功能: 一、從 js 執行環境中取出全局變量 fbBatchedBridge 放到 global 變量中 二、將 global 中某些特定的函數對象映射到 C++ 對象中,這樣咱們就能夠經過 C++ 對象調用 js 的代碼,假設咱們想要調用 js 端 fbBatchedBridge 的 flushQueue 方法,在 C++ 中就可使用 m_flushedQueueJS->callAsFunction() 就能夠實現,那麼 fbBatchedBridge 在 js 端究竟是個什麼東西那?
查看 BatchBridge.js
中能夠找到 __fbBatchedBridge
的定義
const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue();
Object.defineProperty(global, '__fbBatchedBridge', {
configurable: true,
value: BatchedBridge,
});
module.exports = BatchedBridge;
複製代碼
能夠看出 __fbBatchedBridge
指的就是 JS 中的 MessageQueue 對象,這樣就實現了 Android 端的消息隊列和 JS 端消息隊列的連通。
對上面的源碼流程作了個核心方法的調用流程圖