2019-03-27
在上一篇中我们学习了 Yoga,这为分析 React Native 的视图层打了很好的基础。在本篇中我们分析一个 View 在 JavaScript 中写下,是如何被展示成 Android 原生的 View 的。
首先我们在 React Native 应用的 JavaScript 组件中定义一个简单的组件:
export default () => { return <View style={{ backgroundColor: 'pink', width: 200, height: 200}}/> }
这个组件会经由 JS → Native Bridge 转换为一下消息:
UIManager.createView([343,"RCTView",31,{"backgroundColor":-16181,"width":200,"height":200}])
其中:
这条 message 最终会触发到 com.facebook.react.uimanager.UIManagerModule#createView 方法,它的参数签名为:
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
各个参数的值为:
参数 | 值 |
---|---|
tag | 343 |
className | RCTView |
rootViewTag | 31 |
props | { NativeMap: {"height":200,"width":200,"backgroundColor":-16181} } |
其中:
UIManagerModule.createView 会继续调用 com.facebook.react.uimanager.UIImplementation#createView,它的方法签名与前者一致:
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
这里面这些参数都有什么作用呢?在我们继续分析的过程中会一一揭晓。
下面我们来看在这个方法中都做了哪些事情:
首先会创建 ReactShadowNode:
ReactShadowNode cssNode = createShadowNode(className); ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
其中:
RCTView
,UIImplementation 会用它从 mViewManagers 找出已经注册的 ViewManager,并执行这个 Manager 的 createShadowNodeInstance 方法之后对创建的新节点进行一些设置:
// 保存 tag cssNode.setReactTag(tag); // 保存类名 cssNode.setViewClassName(className); // 保存父布局 cssNode.setRootTag(rootNode.getReactTag()); // 保存 ReactContext cssNode.setThemedContext(rootNode.getThemedContext());
当这些步骤都完成后,将新 node 添加到 mShadowNodeRegistry 中:
mShadowNodeRegistry.addNode(cssNode);
之后保存 props 信息:
ReactStylesDiffMap styles = null; if (props != null) { styles = new ReactStylesDiffMap(props); cssNode.updateProperties(styles); }
之后会调用 handleCreateView 方法:
protected void handleCreateView( ReactShadowNode cssNode, int rootViewTag, @Nullable ReactStylesDiffMap styles) { if (!cssNode.isVirtual()) { mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles); } }
其中:
在 NativeViewHierarchyOptimizer.handleCreateView 中也没有真正创建视图,而是向队列中入队一个操作:
mUIViewOperationQueue.enqueueCreateView( themedContext, node.getReactTag(), node.getViewClass(), initialProps);
UIViewOperationQueue 是一个任务队列,用于执行 JS batch bridge 中执行的指令。
还记得我们操作 JS 侧的 BatchedBridge 时的用法吗?常用的方法是:
在 Native 层,实现这个任务队列和批量执行的类想必已经猜到了,就是 UIViewOperationQueue。其中批量执行的方法对应于它的 flushPendingBatches 方法。
下面先让我们看下 UIViewOperationQueue 的成员变量。
成员变量 | 类型 | 说明 |
---|---|---|
mNativeViewHierarchyManager | NativeViewHierarchyManager | 布局相关的实际操作 |
mDispatchUIFrameCallback | DispatchUIFrameCallback | UI 更新回调 |
mOperations | ArrayList<UIOperation> | 任务队列 |
mDispatchUIRunnables | ArrayList<Runnable> | 积攒的一批一批任务 |
enqueueCreateView 方法会创建一个 CreateViewOperation,并插入 mOperations 中。
我们来看下 CreateViewOperation 的代码:
private final class CreateViewOperation extends ViewOperation { private final ThemedReactContext mThemedContext; private final String mClassName; private final @Nullable ReactStylesDiffMap mInitialProps; public CreateViewOperation( ThemedReactContext themedContext, int tag, String className, @Nullable ReactStylesDiffMap initialProps) { super(tag); mThemedContext = themedContext; mClassName = className; mInitialProps = initialProps; Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag); } @Override public void execute() { Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag); mNativeViewHierarchyManager.createView( mThemedContext, mTag, mClassName, mInitialProps); } }
其中:
下面我们就来看是如何真正创建 View 的。
// 取出 ViewManager ViewManager viewManager = mViewManagers.get(className); // 创建视图 // 实际调用 com.facebook.react.views.view.ReactViewManager#createViewInstance View view = viewManager.createView(themedContext, mJSResponderHandler); // 保存视图 // 还是通过 tag 关联,现在跟 tag 关联的有 ReactShadowNode 和 View mTagsToViews.put(tag, view); // viewManager 和 tag 关联 mTagsToViewManagers.put(tag, viewManager); // 用 Android id 来保存 React Node Tag view.setId(tag); // 更新 Props if (initialProps != null) { viewManager.updateProperties(view, initialProps); }
在这里问一个问题:经过上面的代码后,我们创建的这个 View 会出现在屏幕上吗?
答案是并没有,我们虽然创建了 View 的实例,但是它还并没有被摆到屏幕上。
那么在哪里被摆到屏幕上呢?在此我们知道肯定会有一个统一的地方,统一的时机,来遍历视图树,将 View 摆放到正确的位置上。
让我们再回到 JS → Native Bridge 上面,前面我们分析了 UIManager.createView 消息的解析。
当绘制一个新 React 组件时,会创建多条 UIManager.createView 和 UIManager.setChildren 命令。
在这些描述绘制的 message 发完后,最后会发一条 manageChildren 消息:
UIManager.manageChildren([209,[],[],[299],[1],[]])
这条消息会触发最终的页面绘制。
这条消息将会触发到 com.facebook.react.uimanager.UIManagerModule#manageChildren 方法。
它的方法签名如下:
public void manageChildren( int viewTag, @Nullable ReadableArray moveFrom, @Nullable ReadableArray moveTo, @Nullable ReadableArray addChildTags, @Nullable ReadableArray addAtIndices, @Nullable ReadableArray removeFrom) {
结合文档和前面的 message log,我们来看各个参数的作用:
参数名称 | 值 | 作用 |
---|---|---|
viewTag | 209 | 父 View 的 Tag |
moveFrom | [] | 父 View 中要移出的视图 |
moveTo | [] | 与 moveFrom 已启用,移出后放到父 View 的序列 |
addChildTags | [299] | 要添加进入父 View 的视图 |
addAtIndices | [1] | 与 addChildTags 一起用,添加到的序列 |
removeFrom | [] | 需要从父视图中删除的序列的视图 |
从中我们可以得出结论,需要做的是向 209 中添加一个 tag 为 299 的 View。
经过一段与前面相同的辗转(这里虽然带过,但是内部的内容很多,ShadowTree 的层级创建就是在这一过程中),最后会向任务队列中插入一个 ManageChildrenOperation:
private final class ManageChildrenOperation extends ViewOperation { private final @Nullable int[] mIndicesToRemove; private final @Nullable ViewAtIndex[] mViewsToAdd; private final @Nullable int[] mTagsToDelete; public ManageChildrenOperation( int tag, @Nullable int[] indicesToRemove, @Nullable ViewAtIndex[] viewsToAdd, @Nullable int[] tagsToDelete) { super(tag); mIndicesToRemove = indicesToRemove; mViewsToAdd = viewsToAdd; mTagsToDelete = tagsToDelete; } @Override public void execute() { mNativeViewHierarchyManager.manageChildren( mTag, mIndicesToRemove, mViewsToAdd, mTagsToDelete); } }
其中:最终执行的是 mNativeViewHierarchyManager.manageChildren,它会调用 ViewGroup 的 addView 方法,进行实际的 View 层级关联。
本文是对 View 是如何创建的这个大过程的一个粗粒度的梳理,其中有很多步骤是不详尽的。其中有些过程可能梳理地不对,如有错误之处欢迎指正。
在后续的文章中会逐渐细化这些过程。