帶你徹底看懂 React Native 和 Android 原生控制元件之間的對映關係
此文基於react natve的 ofollow,noindex">September 2018 - revision 5 版本
本人學校畢業後就當了安卓爬坑專業戶,3年來總算爬習慣了,不料今年掉進了RN這個天坑,從此開始了我的悲慘人生。。。Anyway,RN的思想還是值得學習的,今天就從Android的角度開始分析一下react native的基礎元件如何載入,看看它們與原生控制元件間的對映關係。
Android端原始碼淺析
安卓老司機看頁面的實現原理,必然首先看 Activity
,其次看 View
,RN在安卓端的載入開端也是如此。
以下是截至此文釋出前最新的RN官方教程中的例子(RN官方教程和RN原始碼一樣,一日三變,習慣就好) :
public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler { private ReactRootView mReactRootView; private ReactInstanceManager mReactInstanceManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mReactRootView = new ReactRootView(this); mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModulePath("index") .addPackage(new MainReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null); setContentView(mReactRootView); } }
從上面的程式碼中可以看出,承載RN頁面顯示的也是一個普通的 Activity
,但 setContentView
中傳入的卻是一個特定的 ReactRootView
,也就是說載入全部在這個 ReactRootView
中完成。 ReactInstanceManager
類似於一個代理,承接了IO,通訊,佈局及其他一些邏輯性操作,下文中還會提到。
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView, MeasureSpecProvider { ... @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // No-op since UIManagerModule handles actually laying out children. } }
上面的程式碼省略了大部分與本文無關的程式碼,但也可以看出 ReactRootView
並沒有三頭六臂,它只不過是一個很普通的繼承自 SizeMonitoringFrameLayout
( FrameLayout
)的控制元件容器,而且它的 onLayout
方法是空的,從註釋中可以看出子控制元件的佈局在 UIManagerModule
中實現。
public class UIManagerModule extends ReactContextBaseJavaModule implements OnBatchCompleteListener, LifecycleEventListener, UIManager { private final UIImplementation mUIImplementation; ... @ReactMethod(isBlockingSynchronousMethod = true) public @Nullable WritableMap getConstantsForViewManager(final String viewManagerName) { ... // 根據viewManagerName獲取ViewManager的對映 return computeConstantsForViewManager(viewManagerName); } @Override public <T extends SizeMonitoringFrameLayout & MeasureSpecProvider> int addRootView( final T rootView, WritableMap initialProps, @Nullable String initialUITemplate) { ... // 獲取ReactRootView物件的引用,以便於再裡面新增View mUIImplementation.registerRootView(rootView, tag, themedRootContext); ... } // 該註解的方法都是可以在js程式碼中呼叫的 @ReactMethod public void createView(int tag, String className, int rootViewTag, ReadableMap props) { if (DEBUG) { ... } // 實現的是reactRootView.addView() mUIImplementation.createView(tag, className, rootViewTag, props); } ... }
同樣, UIManagerModule
裡面也沒有太多東西,它主要是用於暴露方法供js呼叫的,具體實現是由 UIImplementation
來完成的。被 @ReactMethod
註解的方法都可以在js程式碼中被呼叫到,包括: removeRootView
, createView
, measure
, measureLayout
, manageChildren
等等,可見子控制元件的add,measure,layout,remove等操作都是由js呼叫 UIManagerModule
相應的方法後完成。
public class UIImplementation { ... public void createView(int tag, String className, int rootViewTag, ReadableMap props) { //構建ReactShadowNode ReactShadowNode cssNode = createShadowNode(className); ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag); Assertions.assertNotNull(rootNode, "Root node with tag " + rootViewTag + " doesn't exist"); cssNode.setReactTag(tag); cssNode.setViewClassName(className); cssNode.setRootTag(rootNode.getReactTag()); cssNode.setThemedContext(rootNode.getThemedContext()); mShadowNodeRegistry.addNode(cssNode); ... } ... }
以上就是 createView
的具體實現,它主要做的是構造了一個 ReactShadowNode
。
再看看 createShadowNode
:
protected ReactShadowNode createShadowNode(String className) { ViewManager viewManager = mViewManagers.get(className); return viewManager.createShadowNodeInstance(mReactContext); }
它是通過 className
獲取到 ViewManager
。問題來了, ViewManager
是什麼?看它的原始碼可知它是一個抽象類,從它的原始碼很難看出它是幹什麼用的,但一看繼承自它的子類就豁然開朗了,它的子類包括 ReactTextInputManager
, ReactTextViewManager
, ReactImageManager
, SwipeRefreshLayoutManager
, ReactCheckBoxManager
, ReactProgressBarViewManager
, ReactScrollViewManager
等等等。從類名上看,這不就是Android的各種控制元件嗎?檢視原始碼後果然如此。
以 ReactTextViewManager
為例:
public class ReactTextViewManager extends ReactTextAnchorViewManager<ReactTextView, ReactTextShadowNode> { ... }
public class ReactTextView extends TextView implements ReactCompoundView { ... }
它就是對 TextView
的封裝。由此可見js程式碼最終都對映到了原生的控制元件上。
我寫了一個很簡單的RN頁面,只有一個 Text
和一個 Image
,通過AS上的Layout Inspector可以清晰地看到,最終顯示的是封裝過的 TextView
和 ImageView
。

Layout Inspector
再回到 @ReactMethod
註解,它在 JavaModuleWrapper
中被獲取,再通過 NativeModuleRegistry
被放到了一個對映表裡面:
public class JavaModuleWrapper { ... private void findMethods() { ... for (Method targetMethod : targetMethods) { // 獲取@ReactMethod註解 ReactMethod annotation = targetMethod.getAnnotation(ReactMethod.class); ... } } }
public class NativeModuleRegistry { /* package */ Collection<JavaModuleWrapper> getJavaModules(JSInstance jsInstance) { ArrayList<JavaModuleWrapper> javaModules = new ArrayList<>(); // 生成對映表 for (Map.Entry<String, ModuleHolder> entry : mModules.entrySet()) { if (!entry.getValue().isCxxModule()) { javaModules.add(new JavaModuleWrapper(jsInstance, entry.getValue())); } } return javaModules; } }
public class CatalystInstanceImpl implements CatalystInstance { static { // jni ReactBridge.staticInit(); } @Override public void extendNativeModules(NativeModuleRegistry modules) { mNativeModuleRegistry.registerModules(modules); Collection<JavaModuleWrapper> javaModules = modules.getJavaModules(this); Collection<ModuleHolder> cxxModules = modules.getCxxModules(); // 將原生方法的對映表傳給jsBridge jniExtendNativeModules(javaModules, cxxModules); } // C++的方法 private native void jniExtendNativeModules( Collection<JavaModuleWrapper> javaModules, Collection<ModuleHolder> cxxModules); ... }
最後定位到 CatalystInstanceImpl
,它內部初始化了 ReactBridge
(jsBridge),也就是說 @ReactMethod
註解的方法都放到了一個登錄檔裡面供jsBridge隨時呼叫。
而 CatalystInstanceImpl
也是在 ReactInstanceManager
內部例項化的,兜兜轉轉又回到了開頭的 ReactInstanceManager
,也就是說jsBridge對映到原生控制元件的邏輯都在它內部實現。
小結
Android端的載入過程大致如下:
- jsBridge對映到
UIManagerModule
中有@ReactMethod
的方法上; -
UIManagerModule
中針對控制元件的操作由UIImplementation
代理,完成控制元件的add,measure,layout,remove等操作; - 所有控制元件最終新增到
ReactRootView
中,最終由它完成總體的載入並顯示。
至此,Android端相關的邏輯已經差不多了,接下來看看在js端又是怎麼對映的。
js端原始碼淺析
先來一段上文中提到過的RN頁面的程式碼:
type Props = {}; class App extends Component<Props> { render() { return ( <View style={styles.container}> <Image style={styles.image} source={require('./img.png')}> </Image> <Text style={styles.welcome}>Welcome to React Native!</Text> </View> ); } } export default App;
css程式碼不是重點,所以被我省略了,上面只有js和,JSX,一種js的語法糖,所有基礎元件都會以JSX的形式置於 Component
的 render
方法中。
接下來看看 Component
是怎麼實現的:
const Component = class extends RealComponent { render() { const name = RealComponent.displayName || RealComponent.name; return React.createElement( name.replace(/^(RCT|RK)/,''), this.props, this.props.children, ); } };
最終JSX會在 React.createElement
方法中被翻譯成js程式碼,有興趣的童鞋可以查查React框架,這裡就不多展開了。
現在回到例子程式碼中的基礎元件,以 Text
為例,看看它的原始碼:
... const RCTVirtualText = UIManager.getViewManagerConfig('RCTVirtualText') == null ? RCTText : createReactNativeComponentClass('RCTVirtualText', () => ({ validAttributes: { ...ReactNativeViewAttributes.UIView, isHighlighted: true, maxFontSizeMultiplier: true, }, uiViewClassName: 'RCTVirtualText', })); const Text = ( props: TextProps, forwardedRef: ?React.Ref<'RCTText' | 'RCTVirtualText'>, ) => { return <TouchableText {...props} forwardedRef={forwardedRef} />; }; const TextToExport = React.forwardRef(Text); TextToExport.displayName = 'Text'; TextToExport.propTypes = DeprecatedTextPropTypes; module.exports = (TextToExport: Class<NativeComponent<TextProps>>);
Text
的原始碼不少,對於非專業前端,看起來比較吃力,但也有捷徑,從對外暴露點開始找,也就是從 module.exports
開始,到 TextToExport
,再到 Text
,再到 RCTVirtualText
,最後定位到了 UIManager.getViewManagerConfig
。
UIManager.getViewManagerConfig = function(viewManagerName: string) { if ( viewManagerConfigs[viewManagerName] === undefined && UIManager.getConstantsForViewManager ) { try { viewManagerConfigs[ viewManagerName ] = UIManager.getConstantsForViewManager(viewManagerName); } catch (e) { viewManagerConfigs[viewManagerName] = null; } } ... };
看到 getConstantsForViewManager
,是不是覺得很眼熟?沒錯,它就是上一板塊Android原始碼中提到的 UIManagerModule
中的方法,讓我們再來回顧一下java原始碼:
@ReactMethod(isBlockingSynchronousMethod = true) public @Nullable WritableMap getConstantsForViewManager(final String viewManagerName) { ... return computeConstantsForViewManager(viewManagerName); } private @Nullable WritableMap computeConstantsForViewManager(final String viewManagerName) { ViewManager targetView = viewManagerName != null ? mUIImplementation.resolveViewManager(viewManagerName) : null; if (targetView == null) { return null; } SystraceMessage.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIManagerModule.getConstantsForViewManager") .arg("ViewManager", targetView.getName()) .arg("Lazy", true) .flush(); try { Map<String, Object> viewManagerConstants = UIManagerModuleConstantsHelper.createConstantsForViewManager( targetView, null, null, null, mCustomDirectEvents); if (viewManagerConstants != null) { return Arguments.makeNativeMap(viewManagerConstants); } return null; } finally { SystraceMessage.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE).flush(); } }
這個方法的作用就是從快取中獲取 ViewManager
物件,裝入 WritableMap
後回傳給了js,而 WritableMap
在js中以物件的形式存在。
再回到 UIManager
,它除了可以呼叫 getConstantsForViewManager
,上個板塊提到的被 @ReactMethod
註解的方法諸如 removeRootView
, createView
, measure
, measureLayout
等等在js中的對映都是由它來呼叫,也就是說js呼叫原生控制元件的對映都由 UIManager
來完成。
再看一眼 UIManager
的原始碼:
const NativeModules = require('NativeModules'); const {UIManager} = NativeModules; ... module.exports = UIManager;
看來 UIManager
只不過是對 NativeModules
的二次封裝。寫過RN的童鞋對此肯定不陌生,寫js和原生通訊的相關程式碼中肯定會用到 NativeModules
,它是js和原生程式碼通訊的橋樑。
至於 NativeModules
和C++的互動過程,這裡就簡單講一下, NativeModules
內部的有一個 BatchedBridge
(即 MessageQueue
)的物件:
class MessageQueue { // js註冊的回撥,供原生程式碼呼叫 _lazyCallableModules: {[key: string]: (void) => Object}; // js呼叫原生程式碼請求的快取列表 _queue: [number[], number[], any[], number]; // js呼叫原生方法的請求 enqueueNativeCall( moduleID: number, methodID: number, params: any[], onFail: ?Function, onSucc: ?Function, ) { ... // 把請求打包成一個Message,放入快取列表 this._queue[MODULE_IDS].push(moduleID); this._queue[METHOD_IDS].push(methodID); this._queue[PARAMS].push(params); if ( global.nativeFlushQueueImmediate && (now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS || this._inCall === 0) ) { var queue = this._queue; this._queue = [[], [], [], this._callID]; this._lastFlush = now; // 如果是同步請求,則請求的message立即入列,否則等待flushedQueue()的執行 // 這是一個C++的函式 global.nativeFlushQueueImmediate(queue); } } // 將快取的請求列表全部入列 flushedQueue() { this.__guard(() => { this.__callImmediates(); }); const queue = this._queue; this._queue = [[], [], [], this._callID]; return queue[0].length ? queue : null; } // 註冊回撥介面 registerCallableModule(name: string, module: Object) { this._lazyCallableModules[name] = () => module; } ... }
它內部儲存了js中對外暴露的方法和模組的對映表供jsBridge呼叫,如果需要呼叫原生程式碼中的方法, MessageQueue
會將請求封裝成一個Message放入一個請求佇列,然後觸發原生的方法。看著怎麼這麼像Android中的Handler機制?原因很簡單,js執行的執行緒是獨立於原生程式碼所在的UI執行緒的,執行緒間通訊最簡單的還是類似Handler這樣的方式。
小結
RN基礎元件對映到原生在js端的表現大致如下:
- JSX形式的RN基礎元件首先會被翻譯成js程式碼;
- 元件會在js程式碼中呼叫
UIManager
相應的方法; - 由
UIManager
通過jsBridge對映到原生方法UIManagerModule
中;
C++原始碼淺析
Android端和js端都已經介紹完畢了,就像扁擔兩頭的貨物都準備完畢了,就差根扁擔了,jsBridge就是這根扁擔。
先來看一下與 CatalystInstanceImpl.java
對應的 CatalystInstanceImpl.cpp
:
void CatalystInstanceImpl::registerNatives() { registerHybrid({ // jniExtendNativeModules就是CatalystInstanceImpl.java中那個傳入原生方法對映表的native方法 // 它被指向了extendNativeModules方法 makeNativeMethod("jniExtendNativeModules", CatalystInstanceImpl::extendNativeModules), ... }); JNativeRunnable::registerNatives(); } void CatalystInstanceImpl::extendNativeModules( jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules, jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) { // 註冊對映表 moduleRegistry_->registerModules(buildNativeModuleList( std::weak_ptr<Instance>(instance_), javaModules, cxxModules, moduleMessageQueue_)); }
可見 CatalystInstanceImpl
的這部分程式碼就是用來註冊原生方法的對映表的。
再來看看js中呼叫C++的方法 nativeFlushQueueImmediate
,以下程式碼位於 JSIExecutor.cpp
中:
runtime_->global().setProperty( *runtime_, "nativeFlushQueueImmediate", Function::createFromHostFunction( *runtime_, PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"), 1, [this]( jsi::Runtime&, const jsi::Value&, const jsi::Value* args, size_t count) { if (count != 1) { throw std::invalid_argument( "nativeFlushQueueImmediate arg count must be 1"); } // 呼叫已註冊的原生模組 callNativeModules(args[0], false); return Value::undefined(); }));
以下程式碼位於 JsToNativeBridge.cpp
中,它以委託的形式存在,執行上述程式碼中的 callNativeModules
:
void callNativeModules( JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override { ... for (auto& call : parseMethodCalls(std::move(calls))) { // 執行已註冊的原生模組中的方法 m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId); } ... }
最後殊途同歸都到了 ModuleRegistry.cpp
:
// 註冊原生模組 void ModuleRegistry::registerModules(std::vector<std::unique_ptr<NativeModule>> modules) { ... } // 執行原生模組的方法 void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) { ... modules_[moduleId]->invoke(methodId, std::move(params), callId); }
至此,一條完整的對映鏈已經全部講完。
總結
本文以一般看原始碼的順序來展開,依次解讀了Android端,js端和C++的原始碼,分析了RN基礎元件是如何一步步地對映成為原生控制元件的整個過程,展示了一條完整地對映鏈條。
最後整理一下整個對映的鏈條:

對映鏈條
以下是一些常用的rn元件與Android原生控制元件之間的對應關係:
- Text -> TextView
- Image -> ImageView
- TextInput -> EditText
- CheckBox -> AppCompatCheckBox
- RefreshControl -> SwipeRefreshLayout
- ScrollView -> ScrollView
- Slider -> SeekBar
- Switch -> SwitchCompat