重新認識React Native和Android的通訊原理
此文基於react natve的 ofollow,noindex">September 2018 - revision 5 版本
在我的上一篇文章 《帶你徹底看懂React Native和Android原生控制元件之間的對映關係》 中,我已經完整地剖析了從RN元件到原生控制元件之間的對映關係,文中簡單地提到了一些通訊原理,本文我就來詳細地講解一下RN的通訊原理。
PS:網上講解RN通訊原理的相關文章很多,但良莠不齊,有的程式碼氾濫,邏輯混亂,有的過於簡單,結構不清,有的程式碼嚴重過時,已不是當前RN主流版本的原始碼。本文的目的就是想讓讀者對最新的RN通訊原理有一個清晰的認識。Let's get started!
原理簡述
RN通訊原理簡單地講就是,一方將其部分方法註冊成一個對映表,另一方再在這個對映表中查詢並呼叫相應的方法,而jsBridge擔當兩者間橋接的角色。

原始碼解析
按原理簡述中的順序,我將本節分成兩部分,一是從native(java)出發的註冊過程,二是從js出發的呼叫過程,中間還穿插了部分jsBridge中的C++內容。
註冊過程
先看官方教程中的例子:
public class ToastModule extends ReactContextBaseJavaModule { public ToastModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "ToastExample"; } @ReactMethod public void show(String message, int duration) { // 可以被js呼叫的方法 Toast.makeText(getReactApplicationContext(), message, duration).show(); } } 複製程式碼
這個例子我稍稍簡化了一下,功能很簡單,就是註冊了一個原生模組( NativeModule
)供js呼叫後彈Toast。
public class CustomToastPackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } @Override public List<NativeModule> createNativeModules( ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new ToastModule(reactContext));// 被新增到ReactPackage中 return modules; } } 複製程式碼
// YourActivity.java mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModulePath("index") .addPackage(new MainReactPackage()) .addPackage(new CustomToastPackage()) // 傳入ReactInstanceManager中 .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build() 複製程式碼
以上的程式碼都是官方教程中的程式碼。
由上面的程式碼可見 NativeModule
被新增到了 ReactPackage
中並被傳入了 ReactInstanceManager
中。寫過RN的人對 ReactInstanceManager
肯定不會陌生,寫RN所在的Activity時必然會例項化 ReactInstanceManager
,RN在Android端幾乎所有的通訊邏輯都在它內部完成。
接下來開始原始碼的分析:
// ReactInstanceManager.java NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false); CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()) .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry)// NativeModuleRegistry 會在CatalystInstanceImpl中被呼叫 .setJSBundleLoader(jsBundleLoader) .setNativeModuleCallExceptionHandler(exceptionHandler); private NativeModuleRegistry processPackages( ReactApplicationContext reactContext, List<ReactPackage> packages, boolean checkAndUpdatePackageMembership) { NativeModuleRegistryBuilder nativeModuleRegistryBuilder = new NativeModuleRegistryBuilder(reactContext, this); ... for (ReactPackage reactPackage : packages) { // ReactPackage都傳入了NativeModuleRegistry processPackage(reactPackage, nativeModuleRegistryBuilder); } ... NativeModuleRegistry nativeModuleRegistry; nativeModuleRegistry = nativeModuleRegistryBuilder.build(); ... return nativeModuleRegistry; } private void processPackage( ReactPackage reactPackage, NativeModuleRegistryBuilder nativeModuleRegistryBuilder) { ... nativeModuleRegistryBuilder.processPackage(reactPackage); ... } 複製程式碼
以上是 ReactInstanceManager
中的部分程式碼,可以看到, ReactPackage
會被傳入 NativeModuleRegistry
中, NativeModuleRegistry
內部就是一張對映表,所有註冊的 NativeModule
都會儲存在它內部供外部呼叫。而 NativeModuleRegistry
會在 CatalystInstanceImpl
中被呼叫。
看看 CatalystInstanceImpl
內部邏輯:
public class CatalystInstanceImpl implements CatalystInstance { static { // 初始化jni ReactBridge.staticInit(); } private CatalystInstanceImpl( final ReactQueueConfigurationSpec reactQueueConfigurationSpec, final JavaScriptExecutor jsExecutor, final NativeModuleRegistry nativeModuleRegistry, final JSBundleLoader jsBundleLoader, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { ... mNativeModuleRegistry = nativeModuleRegistry; // 將原生模組登錄檔傳給jsBridge initializeBridge( new BridgeCallback(this), jsExecutor, mReactQueueConfiguration.getJSQueueThread(), mNativeModulesQueueThread, mNativeModuleRegistry.getJavaModules(this), mNativeModuleRegistry.getCxxModules()); ... } // C++中執行的方法 private native void initializeBridge( ReactCallback callback, JavaScriptExecutor jsExecutor, MessageQueueThread jsQueue, MessageQueueThread moduleQueue, Collection<JavaModuleWrapper> javaModules, Collection<ModuleHolder> cxxModules); ... } 複製程式碼
可見 CatalystInstanceImpl
已經和jsBridge(即 ReactBridge
)聯絡在一起了,它用C++函式 initializeBridge
將原生模組對映表傳到jsBridge中。
再看看 CatalystInstanceImpl
在C++中的實現:
// CatalystInstanceImpl.cpp void CatalystInstanceImpl::initializeBridge( jni::alias_ref<ReactCallback::javaobject> callback, 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) { ... // 將原生模組對映表傳給ModuleRegistry.cpp moduleRegistry_ = std::make_shared<ModuleRegistry>( buildNativeModuleList( std::weak_ptr<Instance>(instance_), javaModules, cxxModules, moduleMessageQueue_)); ... } 複製程式碼
接下來是 ModuleRegistry
:
// ModuleRegistry.cpp ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules, ModuleNotFoundCallback callback) : modules_{std::move(modules)}, moduleNotFoundCallback_{callback} {} ... void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) { ... // 原生模組登錄檔被呼叫處1 modules_[moduleId]->invoke(methodId, std::move(params), callId); } MethodCallResult ModuleRegistry::callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) { ... // 原生模組登錄檔被呼叫處2 return modules_[moduleId]->callSerializableNativeHook(methodId, std::move(params)); } 複製程式碼
可見 ModuleRegistry
是原生模組對映表在C++中的位置, ModuleRegistry
中暴露出了函式 callNativeMethod
供js呼叫。

原生模組的註冊過程就這樣分析完畢了。
呼叫過程
我們先看官方文件中的呼叫原生模組的方法:
import {NativeModules} from 'react-native'; NativeModules.ToastExample.show('Awesome', 1); 複製程式碼
這樣就呼叫了原生的Toast。它主要呼叫了 NativeModules.js
, ToastExample
是 ToastModule
的 getName
方法返回的字串,而 show
是 ToastModule
中加了 ReactMethod
註解的方法。
接下來看看 ReactMethod
的原始碼:
function genModule( config: ?ModuleConfig, moduleID: number, ): ?{name: string, module?: Object} { const [moduleName, constants, methods, promiseMethods, syncMethods] = config; ... // 獲取原生方法 module[methodName] = genMethod(moduleID, methodID, methodType); ... return {name: moduleName, module}; } function genMethod(moduleID: number, methodID: number, type: MethodType) { let fn = null; if (type === 'promise') { // 非同步呼叫 fn = function(...args: Array<any>) { return new Promise((resolve, reject) => { BatchedBridge.enqueueNativeCall( moduleID, methodID, args, data => resolve(data), errorData => reject(createErrorFromErrorData(errorData)), ); }); }; } else if (type === 'sync') { // 同步呼叫 fn = function(...args: Array<any>) { return global.nativeCallSyncHook(moduleID, methodID, args); }; } } let NativeModules: {[moduleName: string]: Object} = {}; if (global.nativeModuleProxy) { NativeModules = global.nativeModuleProxy; } else if (!global.nativeExtensions) { // 初始化jsBridge const bridgeConfig = global.__fbBatchedBridgeConfig; ... // 呼叫原生模組 const info = genModule(config, moduleID); if (!info) { return; } if (info.module) { NativeModules[info.name] = info.module; } ... } module.exports = NativeModules; 複製程式碼
NativeModules
通過 genModule
獲取到原生模組,又通過 genMethod
呼叫原生模組的方法。
呼叫原生方法分同步和非同步兩種方式。
以同步呼叫為例,它呼叫了 global.nativeCallSyncHook
,即 JSIExecutor.cpp
註冊的C++的方法:
// JSIExecutor.cpp // 註冊了nativeCallSyncHook方法供js呼叫 runtime_->global().setProperty( *runtime_, "nativeCallSyncHook", Function::createFromHostFunction( *runtime_, PropNameID::forAscii(*runtime_, "nativeCallSyncHook"), 1, [this]( jsi::Runtime&, const jsi::Value&, const jsi::Value* args, size_t count) { return nativeCallSyncHook(args, count); })); Value JSIExecutor::nativeCallSyncHook(const Value* args, size_t count) { ... // 呼叫委託,即ModuleRegistry,的callSerializableNativeHook函式 MethodCallResult result = delegate_->callSerializableNativeHook( *this, static_cast<unsigned int>(args[0].getNumber()), static_cast<unsigned int>(args[1].getNumber()), dynamicFromValue(*runtime_, args[2])); if (!result.hasValue()) { return Value::undefined(); } return valueFromDynamic(*runtime_, result.value()); } 複製程式碼
JSIExecutor.cpp
中是通過委託來實現的,最終呼叫的還是 ModuleRegistry.cpp
// ModuleRegistry.cpp MethodCallResult ModuleRegistry::callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) { ... // 原生模組被呼叫處 return modules_[moduleId]->callSerializableNativeHook(methodId, std::move(params)); } 複製程式碼
最後又到了 ModuleRegistry
,也就是註冊過程的終點,呼叫過程也就結束了。

至此,註冊過程和呼叫過程無縫銜接,一個完整的通訊過程已經躍然紙上。