經(jīng)過了一番對(duì)ReactNative的學(xué)習(xí)之后,我們都知道只要敲入react-native run-android神奇的JS代碼就被編譯成Android可以理解的View了,不得不驚嘆ReactNative隱藏的神秘的力量,所以本文分析一下一個(gè)Hello,World的JS代碼是怎么變成Android控件的.
簡(jiǎn)單的看一下JS代碼:
render() {
return (<View >
<Text >Hello,World</Text>
</View>);
}
開始分析
一開始要分析所以順著Android的RN源碼一步一步的追蹤源碼在ReactActivityDelegate.js中有這么一步入口源碼:
protected void loadApp(String appKey) {
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
代碼中很明顯直接將ReactRootView作為這個(gè)Activity的contentView,換句話說整個(gè)RN其實(shí)就是一個(gè)ReactRootView而這個(gè)ReactRootView又是直接繼承了FrameLayout,所以那就好辦了原理不就等于是自定義View實(shí)現(xiàn)了。
但是?。。?!
不一樣的是只是重寫了onMeasure方法并沒有實(shí)現(xiàn)onLayout方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// No-op since UIManagerModule handles actually laying out children.
}
所以我們還是得回頭去看看ReactRootView中startReactApplication是怎么實(shí)現(xiàn)的:
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle initialProperties) {
// 省略不要的相關(guān)代碼
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
mReactInstanceManager.createReactContextInBackground();
}
attachToReactInstanceManager();
}
第一部分中createReactContextInBackground這個(gè)方法我們不看主要是去加載bundle文件并解析,更多的細(xì)節(jié)分析可以查看 React Native Android 源碼框架淺析(主流程及 Java 與 JS 雙邊通信)
我們主要看attachToReactInstanceManager這個(gè)方法,從字面的意思就是給ReactInstanceManager綁定ReactRootView,所以跟著代碼進(jìn)入:
public void attachRootView(ReactRootView rootView) {
UiThreadUtil.assertOnUiThread();
mAttachedRootViews.add(rootView);
// Reset view content as it's going to be populated by the application content from JS.
rootView.removeAllViews();
rootView.setId(View.NO_ID);
// If react context is being created in the background, JS application will be started
// automatically when creation completes, as root view is part of the attached root view list.
ReactContext currentContext = getCurrentReactContext();
if (mCreateReactContextThread == null && currentContext != null) {
attachRootViewToInstance(rootView, currentContext.getCatalystInstance());
}
}
經(jīng)過一番初始化以及清空ReactRootView,然后最后會(huì)執(zhí)行attachRootViewToInstance這個(gè)方法。不過這里的ReactContext還沒初始化不過通過上下文的源碼可以看到最后還是會(huì)走attachRootViewToInstance這個(gè)方法,所以不管我們就是分析它就對(duì)了。
private void attachRootViewToInstance(
final ReactRootView rootView,
CatalystInstance catalystInstance) {
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
final int rootTag = uiManagerModule.addRootView(rootView);
rootView.setRootViewTag(rootTag);
rootView.runApplication();
// 省略了部分代碼
}
得到了rootTag并給rootView設(shè)置了進(jìn)去,再最后的runApplication中有這么一段代碼:
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
這段代碼就是通過catalystInstance去執(zhí)行JS中的:
AppRegistry.registerComponent('RN_Demo', () => ProptypesComponent);
到現(xiàn)在為止差不多我們的RN項(xiàng)目就啟動(dòng)起來了,但是每一個(gè)JS寫的控件是如何轉(zhuǎn)成Android識(shí)別的View呢?
View轉(zhuǎn)換原理
首先還是需要從JS中找入口就是說JS的view是怎么被創(chuàng)建?
React-Native 源碼分析二-JSX如何渲染成原生頁面(下)跟著這篇文章找到了相關(guān)的入口:
UIManager.createView UIManager.updateView UIManager.manageChildren
而UIManager對(duì)應(yīng)的Android源碼類是UIManagerModule.java,舉個(gè)例子看看createView:
@ReactMethod
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
if (DEBUG) {
String message =
"(UIManager.createView) tag: " + tag + ", class: " + className + ", props: " + props;
FLog.d(ReactConstants.TAG, message);
PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
}
mUIImplementation.createView(tag, className, rootViewTag, props);
}
看到@ReactMethod這個(gè)注解就對(duì)了,表明這個(gè)是個(gè)供JS調(diào)用的java方法。所以說我們的每一個(gè)JS代碼中寫的控件最終都是通過這個(gè)入口createView創(chuàng)建一個(gè)個(gè)Android能夠識(shí)別的View,具體的進(jìn)一步代碼實(shí)現(xiàn)如下:
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
ReactShadowNode cssNode = createShadowNode(className);
ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
cssNode.setReactTag(tag);
cssNode.setViewClassName(className);
cssNode.setRootNode(rootNode);
cssNode.setThemedContext(rootNode.getThemedContext());
mShadowNodeRegistry.addNode(cssNode);
ReactStylesDiffMap styles = null;
if (props != null) {
styles = new ReactStylesDiffMap(props);
cssNode.updateProperties(styles);
}
handleCreateView(cssNode, rootViewTag, styles);
}
protected void handleCreateView(
ReactShadowNode cssNode,
int rootViewTag,
@Nullable ReactStylesDiffMap styles) {
if (!cssNode.isVirtual()) {
mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles);
}
}
而mNativeViewHierarchyOptimizer.handleCreateView這個(gè)方法最后是調(diào)用UIViewOperationQueue.enqueueCreateView:
public void enqueueCreateView(
ThemedReactContext themedContext,
int viewReactTag,
String viewClassName,
@Nullable ReactStylesDiffMap initialProps) {
synchronized (mNonBatchedOperationsLock) {
mNonBatchedOperations.addLast(
new CreateViewOperation(
themedContext,
viewReactTag,
viewClassName,
initialProps));
}
}
最后是將一個(gè)個(gè)的CreateViewOperation對(duì)象加入到列表中去,所以這里總結(jié)一下流程:

現(xiàn)在一個(gè)個(gè)JS的View已經(jīng)加載到Android中了并被封裝成CreateViewOperation,從上圖我們知道需要執(zhí)行execute方法才能真正變成一個(gè)個(gè)Android中的View:
public void execute() {
mNativeViewHierarchyManager.createView(
mThemedContext,
mTag,
mClassName,
mInitialProps);
}
繼續(xù)進(jìn)入NativeViewHierarchyManager.createView方法看看具體的實(shí)現(xiàn)代碼:
ViewManager viewManager = mViewManagers.get(className);
View view = viewManager.createView(themedContext, mJSResponderHandler);
代碼中直接調(diào)用ViewManager.createView就直接一個(gè)個(gè)View就出來了,那么我們舉個(gè)例子RCTText也就是JS端中的Text控件,由于每一個(gè)View都是對(duì)應(yīng)一個(gè)ViewManager所以Text這個(gè)控件對(duì)應(yīng)的就是ReactTextViewManager而ViewManager.createView最后調(diào)用的就是createViewInstance方法:
@Override
public ReactTextView createViewInstance(ThemedReactContext context) {
return new ReactTextView(context);
}
很好,一個(gè)Android端能夠識(shí)別的ReactTextView就出來了那么JS中的Text也就成功被初始化出來了,可是僅僅只是初始化而已并未被加入到ReactRootView中我們?nèi)匀徊荒芸匆姟?/p>
View顯示
我們已經(jīng)有一個(gè)個(gè)對(duì)象了,所以得找繪制入口了。很好的一點(diǎn)是我們站在前人的分析基礎(chǔ)上找到了當(dāng)JNI C++那一層完成JS代碼加載之后會(huì)回調(diào)到OnBatchCompleteListener.onBatchComplete:
public void onBatchComplete() {
int batchId = mBatchId;
mBatchId++;
// 省略代碼
mUIImplementation.dispatchViewUpdates(batchId);
}
依然直接進(jìn)入到dispatchViewUpdates方法看看具體實(shí)現(xiàn)
public void dispatchViewUpdates(int batchId) {
final long commitStartTime = SystemClock.uptimeMillis();
try {
updateViewHierarchy();
mNativeViewHierarchyOptimizer.onBatchComplete();
mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
這里看到一個(gè)方法updateViewHierarchy經(jīng)過一番源碼跟蹤最后進(jìn)入跟createView類似:
mOperations.add(
new UpdateLayoutOperation(parentTag, reactTag, x, y, width, height));
所以大膽的猜想U(xiǎn)pdateLayoutOperation其實(shí)對(duì)應(yīng)的操作ReactRootView中的onLayout,我們也在UIViewOperationQueue中看到一堆實(shí)現(xiàn)了UIOperation的類。
繼續(xù)進(jìn)入代碼我們就發(fā)現(xiàn)了UIOperation.execute執(zhí)行的地方了:
for (UIOperation op : nonBatchedOperations) {
op.execute();
}
for (UIOperation op : batchedOperations) {
op.execute();
}
前面我們已經(jīng)看了CreateViewOperation,這里看下UpdateLayoutOpration的execute方法最后會(huì)進(jìn)入NativeViewHierarchManager.updateLayout:
public synchronized void updateLayout(
int parentTag, int tag, int x, int y, int width, int height) {
//省略注釋
try {
View viewToUpdate = resolveView(tag);
viewToUpdate.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
ViewParent parent = viewToUpdate.getParent();
if (parent instanceof RootView) {
parent.requestLayout();
}
// Check if the parent of the view has to layout the view, or the child has to lay itself out.
if (!mRootTags.get(parentTag)) {
ViewManager parentViewManager = mTagsToViewManagers.get(parentTag);
ViewGroupManager parentViewGroupManager;
if (parentViewManager instanceof ViewGroupManager) {
parentViewGroupManager = (ViewGroupManager) parentViewManager;
} else {
throw new IllegalViewOperationException(
"Trying to use view with tag " + tag +
" as a parent, but its Manager doesn't extends ViewGroupManager");
}
if (parentViewGroupManager != null
&& !parentViewGroupManager.needsCustomLayoutForChildren()) {
updateLayout(viewToUpdate, x, y, width, height);
}
} else {
updateLayout(viewToUpdate, x, y, width, height);
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
}
}
這里看到MeasureSpec layout相關(guān)的詞大概就差不多結(jié)束了,因?yàn)檫@些就是平時(shí)常見的View自定義流程相關(guān)的。
addView原理
我們都知道要往ViewGroup中添加View都是通過addView的方式,我們上面一些列操作并未發(fā)現(xiàn)這一個(gè)操作流程,原來Android中是使用yoga這個(gè)神奇的庫進(jìn)行操作。
yoga的github地址:https://github.com/facebook/yoga
由于是個(gè)c++的庫水平有限具體實(shí)現(xiàn)原理就略過了,我們大概看下例子:
YogaNode root = new YogaNode();
root.setWidth(500);
root.setHeight(300);
root.setAlignItems(CENTER);
root.setJustifyContent(CENTER);
root.setPadding(ALL, 20);
YogaNode text = new YogaNode();
text.setWidth(200);
text.setHeight(25);
YogaNode image = new YogaNode();
image.setWidth(50);
image.setHeight(50);
image.setPositionType(ABSOLUTE);
image.setPosition(END, 20);
image.setPosition(TOP, 20);
root.addChildAt(text, 0);
root.addChildAt(image, 1);
而在RN的源碼中提供了一個(gè)yoga的入口:
public void addChildAt(YogaNode child, int i) {
if (child.mParent != null) {
throw new IllegalStateException("Child already has a parent, it must be removed first.");
}
if (mChildren == null) {
mChildren = new ArrayList<>(4);
}
mChildren.add(i, child);
child.mParent = this;
jni_YGNodeInsertChild(mNativePointer, child.mNativePointer, i);
}
這樣子轉(zhuǎn)換了一下確實(shí)就清晰了, 這一步的操作太深了只能這么深入淺出了。
那么整體的源碼分析流程就這么結(jié)束了,路漫漫其修遠(yuǎn)兮 ..
!!== The end ==!!
參考文章:
https://blog.csdn.net/yanbober/article/details/53157456
http://lrd.ele.me/2016/09/12/React-Native-jsx-code-analyse1/
http://lrd.ele.me/2016/09/13/React-Native-jsx-code-analyse2/
https://zhuanlan.zhihu.com/p/32263682