ReactNative源码分析之NativeViewHierarchyManager

当前分析的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线程中执行;

你可能感兴趣的:(ReactNative源码分析)