当前分析的ReactNative版本为0.61.5:
看这边文章前最好先了解UIManagerModule、UIImplementation、UIViewOperationQueue类;
1.ReactNative源码分析之UIManagerModule.
2.ReactNative源码分析之UIViewOperationQueue.
3.ReactNative源码分析之UIImplementation.
我们看NativeViewHierarchyManager的名字就知道,它其实是一个管理类,管理所有的View。
先来看下它的私有字段:
private final SparseArray<View> mTagsToViews;
private final SparseArray<ViewManager> mTagsToViewManagers;
private final ViewManagerRegistry mViewManagers;
从这几个字段我们就能猜测,它管理着所有的ViewManager和对应的View,映射关系是通过int值关联,这个int值其实就是ViewTag。
我们找几个方法看看具体实现:
public final synchronized View resolveView(int tag) {
View view = mTagsToViews.get(tag);
if (view == null) {
throw new IllegalViewOperationException(
"Trying to resolve view with tag " + tag + " which doesn't exist");
}
return view;
}
这个是根据ViewTag,查找对应的View。
public final synchronized ViewManager resolveViewManager(int tag) {
ViewManager viewManager = mTagsToViewManagers.get(tag);
if (viewManager == null) {
boolean alreadyDropped = Arrays.asList(mDroppedViewArray).contains(tag);
throw new IllegalViewOperationException(
"ViewManager for tag "
+ tag
+ " could not be found.\n View already dropped? "
+ alreadyDropped
+ ".\nLast index "
+ mDroppedViewIndex
+ " in last 100 views"
+ mDroppedViewArray.toString());
}
return viewManager;
}
这个是通过ViewTag,查找对应的ViewManager。
在之前的文章提到UIViewOperationQueue类,所有对View的UI操作本质上是由NativeViewHierarchyManager代理执行,想操作哪个View,那就传入该View的ViewTag。
通过NativeViewHierarchyManager管理的ViewTag、View,ViewManager映射关系,最终直接操作到View和ViewManager相应的方法上。
举个例子,创建View:
public synchronized void createView(
ThemedReactContext themedContext,
int tag,
String className,
@Nullable ReactStylesDiffMap initialProps) {
UiThreadUtil.assertOnUiThread();
SystraceMessage.beginSection(
Systrace.TRACE_TAG_REACT_VIEW, "NativeViewHierarchyManager_createView")
.arg("tag", tag)
.arg("className", className)
.flush();
try {
ViewManager viewManager = mViewManagers.get(className);
View view = viewManager.createView(themedContext, null, null, mJSResponderHandler);
mTagsToViews.put(tag, view);
mTagsToViewManagers.put(tag, viewManager);
Log.i(TAG, "createView tag:"+tag + " view:"+view.getClass().getSimpleName() + " count:"+mTagsToViews.size());
// Use android View id field to store React tag. This is possible since we don't inflate
// React views from layout xmls. Thus it is easier to just reuse that field instead of
// creating another (potentially much more expensive) mapping from view to React tag
view.setId(tag);
if (initialProps != null) {
viewManager.updateProperties(view, initialProps);
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
}
}
通过tag,查找对应的ViewManager,通过ViewManager创建View,初始化View属性,最后将View保存到tag和View、ViewManager映射关系中,最后返回。
再来看看更新View属性的代码:
public synchronized void updateProperties(int tag, ReactStylesDiffMap props) {
UiThreadUtil.assertOnUiThread();
try {
ViewManager viewManager = resolveViewManager(tag);
View viewToUpdate = resolveView(tag);
if (props != null) {
viewManager.updateProperties(viewToUpdate, props);
}
} catch (IllegalViewOperationException e) {
FLog.e(TAG, "Unable to update properties for view tag " + tag, e);
}
}
通过tag,查找对应的ViewManager和View,再通过这两个类更新其属性。
再来看看更新View的Layout:
public synchronized void updateLayout(
int parentTag, int tag, int x, int y, int width, int height) {
UiThreadUtil.assertOnUiThread();
SystraceMessage.beginSection(
Systrace.TRACE_TAG_REACT_VIEW, "NativeViewHierarchyManager_updateLayout")
.arg("parentTag", parentTag)
.arg("tag", tag)
.flush();
try {
View viewToUpdate = resolveView(tag);
Log.i(TAG,"updateLayout tag:"+viewToUpdate.getId() + " viewName:"+viewToUpdate.getClass().getSimpleName() + " x:"+x + " y:"+y + " width:"+width + " height:"+height);
// Even though we have exact dimensions, we still call measure because some platform views
// (e.g.
// Switch) assume that method will always be called before onLayout and onDraw. They use it to
// calculate and cache information used in the draw pass. For most views, onMeasure can be
// stubbed out to only call setMeasuredDimensions. For ViewGroups, onLayout should be stubbed
// out to not recursively call layout on its children: React Native already handles doing
// that.
//
// Also, note measure and layout need to be called *after* all View properties have been
// updated
// because of caching and calculation that may occur in onMeasure and onLayout. Layout
// operations should also follow the native view hierarchy and go top to bottom for
// consistency
// with standard layout passes (some views may depend on this).
viewToUpdate.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
// We update the layout of the ReactRootView when there is a change in the layout of its
// child.
// This is required to re-measure the size of the native View container (usually a
// FrameLayout) that is configured with layout_height = WRAP_CONTENT or layout_width =
// WRAP_CONTENT
//
// This code is going to be executed ONLY when there is a change in the size of the Root
// View defined in the js side. Changes in the layout of inner views will not trigger an
// update
// on the layout of the Root View.
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);
IViewManagerWithChildren parentViewManagerWithChildren;
if (parentViewManager instanceof IViewManagerWithChildren) {
parentViewManagerWithChildren = (IViewManagerWithChildren) parentViewManager;
} else {
throw new IllegalViewOperationException(
"Trying to use view with tag "
+ parentTag
+ " as a parent, but its Manager doesn't implement IViewManagerWithChildren");
}
if (parentViewManagerWithChildren != null
&& !parentViewManagerWithChildren.needsCustomLayoutForChildren()) {
updateLayout(viewToUpdate, x, y, width, height);
}
} else {
updateLayout(viewToUpdate, x, y, width, height);
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
}
}
private void updateLayout(View viewToUpdate, int x, int y, int width, int height) {
if (mLayoutAnimationEnabled && mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) {
mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height);
} else {
viewToUpdate.layout(x, y, x + width, y + height);
}
}
我们看到,核心逻辑就是先通过tag,查找对应的View,然后直接操作View的layout方法。
其他数对UI操作的方法都是如此,总结如下:
1.通过tag查找对应的View和ViewManager;
2.根据相应的方法,操作对应的View和ViewManager;
3.最后需要注意,所有的操作都需要在UI线程中执行;