最近公司一直在搞關于Rn平臺搭建相關的東西,以前自學都是斷斷續(xù)續(xù)的入坑撞牢,但好在基礎都還在祸穷!
從Android的角度開始分析一下反應native的基礎組件如何加載,聊下它們與原生控件間的映射關系读恃。(ps:希望能幫助一些朋友)
安卓端源碼淺析
賣個關子,安卓老司機看頁面的實現(xiàn)原理,怎么看的崎苗?RN在安卓端的加載開端也是如此!
基于這個(https://github.com/facebook/react-native/releases/tag/v0.57.5):
public class DXYReactActivity 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, “DXYReactNativeApp", 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
中實現(xiàn)。
public class UIManagerModule extends ReactContextBaseJavaModule
implements OnBatchCompleteListener, LifecycleEventListener, UIManager {
private final UIImplementation mUIImplementation;
...
@ReactMethod(isBlockingSynchronousMethod = true)
public @Nullable WritableMap getConstantsForViewManager(final String viewManagerName) {
...
// 根據(jù)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) {
...
}
// 實現(xiàn)的是reactRootView.addView()
mUIImplementation.createView(tag, className, rootViewTag, props);
}
...
}
同樣锰镀,UIManagerModule
里面也沒有太多東西娘侍,它主要是用于暴露方法供JS調用的,實現(xiàn)具體的英文由UIImplementation
來完成的被泳炉。@ReactMethod
注解的方法都可以在JS代碼中被調用到憾筏,包括:removeRootView
,createView
花鹅,measure
氧腰,measureLayout
,manageChildren
等等刨肃,可見子控件的添加古拴,測量,布局真友,刪除等操作都是由JS調用UIManagerModule
相應的方法后完成斤富。
public class UIImplementation {
...
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
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
的具體實現(xiàn),它主要做的是構造了一個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
等等等锰蓬。從類名上看,這不就是安卓的各種控件嗎眯漩?查看源碼后果然如此芹扭。
以ReactTextViewManager
為例:
public class ReactTextViewManager
extends ReactTextAnchorViewManager<ReactTextView, ReactTextShadowNode> {
...
}
public class ReactTextView extends TextView implements ReactCompoundView {
...
}
就是它對TextView
的封裝。由此可見JS代碼最終都映射到了原生的控件上赦抖。
大家可以嘗試下舱卡,一個很簡單的RN頁面(此處無圖),只有一個Text
和一個Image
队萤,通過AS上的布局檢查員可以清晰地看到轮锥,最終顯示的是封裝過的TextView
和ImageView
。
回到再@ReactMethod
注解要尔,在它JavaModuleWrapper
中舍杜,通過再NativeModuleRegistry
被放到了一個映射表里面:
public class JavaModuleWrapper {
...
private void findMethods() {
...
for (Method targetMethod : targetMethods) {
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();
jniExtendNativeModules(javaModules, cxxModules);
}
private native void jniExtendNativeModules(
Collection<JavaModuleWrapper> javaModules,
Collection<ModuleHolder> cxxModules);
...
}
最后定位到CatalystInstanceImpl
新娜,它內部初始化了ReactBridge
(jsBridge),也就是說@ReactMethod
注解的方法都放到了一個注冊表A里面供jsBridge隨時調用蝴簇。
而CatalystInstanceImpl
也是在ReactInstanceManager
內部實例化的杯活,兜兜轉轉又回到了開頭的ReactInstanceManager
,也就是說jsBridge映射到原生控件的邏輯都在它內部實現(xiàn)熬词。
小結
安卓端的加載過程大致如下:
- jsBridge到映射
UIManagerModule
中有@ReactMethod
的方法上; -
UIManagerModule
針對中的控件操作由UIImplementation
代理旁钧,完成控件的添加,測量互拾,布局歪今,刪除等操作; - 控件所有名單最終添加到
ReactRootView
中,最終由它完成總體的加載并顯示颜矿。
至此寄猩,安卓端相關的邏輯已經(jīng)差不多了,接下來看看在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
的英文怎么實現(xiàn)的:
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分類中翻譯代碼兽赁,有興趣的童鞋可以查查框架,這里就不多展開了冷守。
現(xiàn)在回到例子代碼中的基礎組件刀崖,以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
的源碼不少拍摇,對于非專業(yè)前端亮钦,看起來比較吃力,但也有捷徑授翻,從對外暴露點開始找或悲,就是也。從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()的執(zhí)行
// 這是一個C++的函數(shù)
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
會將請求封裝成一個消息放入一個請求隊列信认,然后觸發(fā)原生的方法【椋看著怎么這么像的Android中的處理程序機制嫁赏?原因很簡單,JS執(zhí)行的線程是獨立于原生代碼所在的UI線程的油挥,線程間通信最簡單的還是類似處理器這樣的方式潦蝇。
小結
RN基礎組件映射到原生在JS端的表現(xiàn)大致如下:
- JSX形式的RN基礎組件首先會被翻譯成JS代碼;
- 組件會在JS中代碼調用
UIManager
相應的方法; - 由
UIManager
通過jsBridge到映射原生方法UIManagerModule
中;
C ++源碼淺析
安卓端和JS端都已經(jīng)介紹完畢了,就像扁擔兩頭的貨物都準備完畢了深寥,就差橋了攘乒,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
中,它以委托的形式存在闰集,執(zhí)行上述代碼中的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) {
...
}
// 執(zhí)行原生模塊的方法
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
...
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
總算一條完整的映射鏈over沽讹。
總結
解讀了的Android端般卑,JS端和C ++的源碼,分析了RN基礎組件是如何一步步地映射成為原生控件的整個過程爽雄,展示了一條完整地映射蝠检。
最后整理一下整個映射的:
- 文字 - > TextView
- 圖像 - > ImageView
- TextInput - > EditText
- CheckBox - > AppCompatCheckBox
- RefreshControl - > SwipeRefreshLayout
- ScrollView - > ScrollView
- 滑塊 - > SeekBar
- Switch - > SwitchCompat