经过了一番对ReactNative的学习之后,我们都知道只要敲入react-native run-android
神奇的JS代码就被编译成Android可以理解的View了,不得不惊叹ReactNative隐藏的神秘的力量,所以本文分析一下一个Hello,World
的JS代码是怎么变成Android控件的.
简单的看一下JS代码:
render() {
return (
Hello,World
);
}
开始分析
一开始要分析所以顺着Android的RN源码一步一步的追踪源码在ReactActivityDelegate.js
中有这么一步入口源码:
protected void loadApp(String appKey) {
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
代码中很明显直接将ReactRootView作为这个Activity的contentView,换句话说整个RN其实就是一个ReactRootView而这个ReactRootView又是直接继承了FrameLayout,所以那就好办了原理不就等于是自定义View实现了。
但是!!!!
不一样的是只是重写了onMeasure方法并没有实现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
是怎么实现的:
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle initialProperties) {
// 省略不要的相关代码
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
mReactInstanceManager.createReactContextInBackground();
}
attachToReactInstanceManager();
}
第一部分中createReactContextInBackground
这个方法我们不看主要是去加载bundle文件并解析,更多的细节分析可以查看 React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)
我们主要看attachToReactInstanceManager
这个方法,从字面的意思就是给ReactInstanceManager
绑定ReactRootView,所以跟着代码进入:
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());
}
}
经过一番初始化以及清空ReactRootView,然后最后会执行attachRootViewToInstance
这个方法。不过这里的ReactContext
还没初始化不过通过上下文的源码可以看到最后还是会走attachRootViewToInstance
这个方法,所以不管我们就是分析它就对了。
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设置了进去,再最后的runApplication
中有这么一段代码:
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
这段代码就是通过catalystInstance去执行JS中的:
AppRegistry.registerComponent('RN_Demo', () => ProptypesComponent);
到现在为止差不多我们的RN项目就启动起来了,但是每一个JS写的控件是如何转成Android识别的View呢?
View转换原理
首先还是需要从JS中找入口就是说JS的view是怎么被创建?
React-Native 源码分析二-JSX如何渲染成原生页面(下)跟着这篇文章找到了相关的入口:
UIManager.createView UIManager.updateView UIManager.manageChildren
而UIManager对应的Android源码类是UIManagerModule.java
,举个例子看看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
这个注解就对了,表明这个是个供JS调用的java方法。所以说我们的每一个JS代码中写的控件最终都是通过这个入口createView
创建一个个Android能够识别的View,具体的进一步代码实现如下:
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
这个方法最后是调用UIViewOperationQueue.enqueueCreateView
:
public void enqueueCreateView(
ThemedReactContext themedContext,
int viewReactTag,
String viewClassName,
@Nullable ReactStylesDiffMap initialProps) {
synchronized (mNonBatchedOperationsLock) {
mNonBatchedOperations.addLast(
new CreateViewOperation(
themedContext,
viewReactTag,
viewClassName,
initialProps));
}
}
最后是将一个个的CreateViewOperation
对象加入到列表中去,所以这里总结一下流程:
现在一个个JS的View已经加载到Android中了并被封装成CreateViewOperation
,从上图我们知道需要执行execute方法才能真正变成一个个Android中的View:
public void execute() {
mNativeViewHierarchyManager.createView(
mThemedContext,
mTag,
mClassName,
mInitialProps);
}
继续进入NativeViewHierarchyManager.createView
方法看看具体的实现代码:
ViewManager viewManager = mViewManagers.get(className);
View view = viewManager.createView(themedContext, mJSResponderHandler);
代码中直接调用ViewManager.createView
就直接一个个View就出来了,那么我们举个例子RCTText
也就是JS端中的Text
控件,由于每一个View都是对应一个ViewManager所以Text这个控件对应的就是ReactTextViewManager
而ViewManager.createView最后调用的就是createViewInstance方法:
@Override
public ReactTextView createViewInstance(ThemedReactContext context) {
return new ReactTextView(context);
}
很好,一个Android端能够识别的ReactTextView就出来了那么JS中的Text也就成功被初始化出来了,可是仅仅只是初始化而已并未被加入到ReactRootView中我们仍然不能看见。
View显示
我们已经有一个个对象了,所以得找绘制入口了。很好的一点是我们站在前人的分析基础上找到了当JNI C++那一层完成JS代码加载之后会回调到OnBatchCompleteListener.onBatchComplete
:
public void onBatchComplete() {
int batchId = mBatchId;
mBatchId++;
// 省略代码
mUIImplementation.dispatchViewUpdates(batchId);
}
依然直接进入到dispatchViewUpdates方法看看具体实现
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);
}
}
这里看到一个方法updateViewHierarchy
经过一番源码跟踪最后进入跟createView类似:
mOperations.add(
new UpdateLayoutOperation(parentTag, reactTag, x, y, width, height));
所以大胆的猜想UpdateLayoutOperation其实对应的操作ReactRootView中的onLayout,我们也在UIViewOperationQueue中看到一堆实现了UIOperation的类。
继续进入代码我们就发现了UIOperation.execute
执行的地方了:
for (UIOperation op : nonBatchedOperations) {
op.execute();
}
for (UIOperation op : batchedOperations) {
op.execute();
}
前面我们已经看了CreateViewOperation,这里看下UpdateLayoutOpration的execute方法最后会进入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相关的词大概就差不多结束了,因为这些就是平时常见的View自定义流程相关的。
addView原理
我们都知道要往ViewGroup中添加View都是通过addView
的方式,我们上面一些列操作并未发现这一个操作流程,原来Android中是使用yoga
这个神奇的库进行操作。
yoga的github地址:https://github.com/facebook/yoga
由于是个c++的库水平有限具体实现原理就略过了,我们大概看下例子:
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的源码中提供了一个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);
}
这样子转换了一下确实就清晰了, 这一步的操作太深了只能这么深入浅出了。
那么整体的源码分析流程就这么结束了,路漫漫其修远兮 ..
!!== 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