ReactNative 源码分析12——Native View创建流程onBatchComplete

4 阅读9分钟

本篇文章继续分析onBatchComplete方法,onBatchCompleteJS Batch 结束的信号 —— 当一个 JS 执行批次中所有 Native 调用(createView、setChildren 等)执行完毕后,C++ Bridge 触发此回调,最终引发 Yoga 布局计算 + UI 线程刷出操作。

它里面调用mUIImplementation.dispatchViewUpdates,2 个核心函数

  • updateViewHierarchy:负责View视图的Yoga 布局计算
  • dispatchViewUpdates:投递到 UI 线程,对View进行更新
public void dispatchViewUpdates(int batchId) {
  final long commitStartTime = SystemClock.uptimeMillis();
  try {
    // ① Yoga 布局计算
    updateViewHierarchy();
    mNativeViewHierarchyOptimizer.onBatchComplete();
    // ② 投递到 UI 线程
    mOperationsQueue.dispatchViewUpdates(batchId, commitStartTime, mLastCalculateLayoutTime);
  } finally {
    Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
  }
}

updateViewHierarchy

updateViewHierarchy主要做了如下 3 件事:

  • calculateRootLayout:调用C++ Yoga 引擎对进行Flexbox布局计算
  • applyUpdatesRecursive:Flexbox布局计算完后将这些布局值翻译成 UI 线程操作
  • mEventDispatcher.dispatchEvent:分发JS元素的onLayout事件
protected void updateViewHierarchy() {
    for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) {
      int tag = mShadowNodeRegistry.getRootTag(i);
      //获取root节点对应的ReactShadowNode
      ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag);

      if (cssRoot.getWidthMeasureSpec() != null && cssRoot.getHeightMeasureSpec() != null) {
        try {
          notifyOnBeforeLayoutRecursive(cssRoot);
        } finally {
          Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
        }
        // ① Flexbox布局计算
        calculateRootLayout(cssRoot);

        try {
          List<ReactShadowNode> onLayoutNodes = new ArrayList<>();
          // ② Flexbox布局计算完后将这些布局值翻译成 UI 线程操作
          applyUpdatesRecursive(cssRoot, 0f, 0f, onLayoutNodes);
          // ③ 
          for (ReactShadowNode node : onLayoutNodes) {
            mEventDispatcher.dispatchEvent(
                OnLayoutEvent.obtain(
                    -1, /* surfaceId not used in classic renderer */
                    node.getReactTag(),
                    node.getScreenX(),
                    node.getScreenY(),
                    node.getScreenWidth(),
                    node.getScreenHeight()));
          }

        } finally {
          Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
        }

        if (mLayoutUpdateListener != null) {
          mOperationsQueue.enqueueLayoutUpdateFinished(cssRoot, mLayoutUpdateListener);
        }
      }
    }
}

calculateRootLayout

calculateRootLayout经过多层调用到YogaNodeJNIBase.calculateLayout

  • ① BFS 遍历整棵树,this表示树根节点,最终将一个树转化为ArrayList
  • ② 构建两个等长的平行数组
数组类型内容
nativePointerslong[]每个 YogaNode 对应的 C++ YGNodeRef 指针
nodesYogaNodeJNIBase[]每个 Java 层的 YogaNode 对象
  • ③ 调用 C++ Yoga 引擎计算布局
public void calculateLayout(float width, float height) {
  long[] nativePointers = null;
  YogaNodeJNIBase[] nodes = null;

  freeze(null);

  //①
  ArrayList<YogaNodeJNIBase> n = new ArrayList<>();
  n.add(this);
  for (int i = 0; i < n.size(); ++i) {
    final YogaNodeJNIBase parent = n.get(i);
    List<YogaNodeJNIBase> children = parent.mChildren;
    if (children != null) {
      for (YogaNodeJNIBase child : children) {
        child.freeze(parent);
        n.add(child);
      }
    }
  }
  //②  
  nodes = n.toArray(new YogaNodeJNIBase[n.size()]);
  nativePointers = new long[nodes.length];
  for (int i = 0; i < nodes.length; ++i) {
    nativePointers[i] = nodes[i].mNativePointer;
  }
  // ③ 调用 C++ Yoga 引擎计算布局
  YogaNative.jni_YGNodeCalculateLayoutJNI(mNativePointer, width, height, nativePointers, nodes);
}

jni_YGNodeCalculateLayoutJNI是个JNI函数,Yoga 引擎计算布局是非常复杂的,这里我们不分析细节,直接看这个JNI方法最终结果

  • 步骤②中计算得到了元素Width、Height、坐标、Margin等信息,存储在float arr[18]中
  • 步骤③中将将C++中的arr数组回写到obj对象中的arr字段,obj就是Java层的YogaNodeJNIBase

jni_YGNodeCalculateLayoutJNI方法的核心作用就是调用Yoga 引擎进行计算,然后将数据 回传 到YogaNodeJNIBase.arr中供后续Java层使用

//YGJNIVanilla.cpp
static void YGTransferLayoutOutputsRecursive(JNIEnv* env, jobject thiz, YGNodeRef root) {
    // 跳过没有新布局的节点(优化:只更新脏节点)
    if (!YGNodeGetHasNewLayout(root)) return;

    auto obj = YGNodeJobject(root);  // 通过映射表找到 Java 对象
    if (!obj) return;

    // ① 收集本节点的边信息
    auto edgesSet = YGNodeEdges{root};
    bool marginFieldSet = edgesSet.has(YGNodeEdges::MARGIN);
    bool paddingFieldSet = edgesSet.has(YGNodeEdges::PADDING);
    bool borderFieldSet = edgesSet.has(YGNodeEdges::BORDER);

    // ② 构建 float[] 数组
    int fieldFlags = edgesSet.get() | HAS_NEW_LAYOUT;
    float arr[18];  // 最多 18 个 float(6基础 + 4margin + 4padding + 4border)
    arr[0]  = fieldFlags;                       // 边标志位 + HAS_NEW_LAYOUT
    arr[1]  = YGNodeLayoutGetWidth(root);       // layoutWidth
    arr[2]  = YGNodeLayoutGetHeight(root);      // layoutHeight
    arr[3]  = YGNodeLayoutGetLeft(root);        // layoutX
    arr[4]  = YGNodeLayoutGetTop(root);         // layoutY
    arr[5]  = YGNodeLayoutGetDirection(root);   // layoutDirection
    if (marginFieldSet) {
        arr[6]  = YGNodeLayoutGetMargin(root, YGEdgeLeft);
        arr[7]  = YGNodeLayoutGetMargin(root, YGEdgeTop);
        arr[8]  = YGNodeLayoutGetMargin(root, YGEdgeRight);
        arr[9]  = YGNodeLayoutGetMargin(root, YGEdgeBottom);
    }
    // padding, border 类似...

    // ③ 创建 Java float[] 并赋给 YogaNodeJNIBase.arr 字段
    jfloatArray arrFinal = env->NewFloatArray(arrSize);
    env->SetFloatArrayRegion(arrFinal, 0, arrSize, arr);
    env->SetObjectField(obj, arrField, arrFinal);  // ★ 写入 Java 对象的 arr 字段

    // ④ 清除 hasNewLayout 标记
    YGNodeSetHasNewLayout(root, false);

    // ⑤ 递归处理子节点
    for (size_t i = 0; i < YGNodeGetChildCount(root); i++) {
        YGTransferLayoutOutputsRecursive(env, thiz, YGNodeGetChild(root, i));
    }
}

我们看看YogaNodeJNIBase,它里面都是从arr中取值

private static final byte LAYOUT_EDGE_SET_FLAG_INDEX = 0;
private static final byte LAYOUT_WIDTH_INDEX = 1;
private static final byte LAYOUT_HEIGHT_INDEX = 2;
private static final byte LAYOUT_LEFT_INDEX = 3;
private static final byte LAYOUT_TOP_INDEX = 4;
private static final byte LAYOUT_DIRECTION_INDEX = 5;
private static final byte LAYOUT_MARGIN_START_INDEX = 6;
private static final byte LAYOUT_PADDING_START_INDEX = 10;
private static final byte LAYOUT_BORDER_START_INDEX = 14;

@Override
public float getLayoutX() {
  return arr != null ? arr[LAYOUT_LEFT_INDEX] : 0;
}

@Override
public float getLayoutY() {
  return arr != null ? arr[LAYOUT_TOP_INDEX] : 0;
}

@Override
public float getLayoutWidth() {
  return arr != null ? arr[LAYOUT_WIDTH_INDEX] : 0;
}

@Override
public float getLayoutHeight() {
  return arr != null ? arr[LAYOUT_HEIGHT_INDEX] : 0;
}

C++层返回的arr 的格式如下:

arr[0]  = flag (HAS_NEW_LAYOUT | MARGIN | PADDING | BORDER)
arr[1]  = layoutWidth
arr[2]  = layoutHeight
arr[3]  = layoutLeft (X)
arr[4]  = layoutTop  (Y)
arr[5]  = layoutDirection
arr[6]  = marginLeft     ┐
arr[7]  = marginTop      │ 仅当 flag & MARGIN
arr[8]  = marginRight    │
arr[9]  = marginBottom   ┘
arr[10] = paddingLeft    ┐
arr[11] = paddingTop     │ 仅当 flag & PADDING
arr[12] = paddingRight   │
arr[13] = paddingBottom  ┘
arr[14] = borderLeft     ┐
arr[15] = borderTop      │ 仅当 flag & BORDER
arr[16] = borderRight    │
arr[17] = borderBottom   ┘

如果某个节点没有设置 margin,则 flag & MARGIN == 0,后续 padding 和 border 的 index 会向前偏移 4,保持紧凑。

applyUpdatesRecursive

Yoga 布局已经完成,每个 ShadowNode 的 YogaNode 中已经有了 x/y/width/height,applyUpdatesRecursive 的职责是将这些布局值翻译成 UI 线程操作

protected void applyUpdatesRecursive(
    ReactShadowNode cssNode,
    float absoluteX,
    float absoluteY,
    List<ReactShadowNode> onLayoutNodes) {
  if (!cssNode.hasUpdates()) {
    return;
  }

  if (cssNode.dispatchUpdatesWillChangeLayout(absoluteX, absoluteY)
      && cssNode.shouldNotifyOnLayout()
      && !mShadowNodeRegistry.isRootNode(cssNode.getReactTag())) {
    onLayoutNodes.add(cssNode);
  }

  Iterable<? extends ReactShadowNode> cssChildren = cssNode.calculateLayoutOnChildren();
  if (cssChildren != null) {
    for (ReactShadowNode cssChild : cssChildren) {
      applyUpdatesRecursive(
          cssChild,
          absoluteX + cssNode.getLayoutX(),
          absoluteY + cssNode.getLayoutY(),
          onLayoutNodes);
    }
  }

  cssNode.dispatchUpdates(absoluteX, absoluteY, mOperationsQueue, mNativeViewHierarchyOptimizer);

  cssNode.markUpdateSeen();
  mNativeViewHierarchyOptimizer.onViewUpdatesCompleted(cssNode);
}

该方法使用递归算法:先递归子节点(DFS 后序),后处理当前节点,这是因为:

  • 子节点的布局值先被处理和记录
  • 当前节点的 dispatchUpdates 在子节点之后执行

absoluteXabsoluteY 在递归过程中不断累加:

root (absoluteX=0, absoluteY=0)
  └─ childA (absoluteX=0 + root.layoutX, absoluteY=0 + root.layoutY)
       └─ grandChild (absoluteX=childA.absoluteX + childA.layoutX, ...)

这样每个节点都知道自己在屏幕上的绝对坐标(相对于根节点)。除此之外,如果JS中节点设置了**onLayout** 事件, 那么还会将其添加到onLayoutNodes数组中

<View
  onLayout={(event) => {
    console.log(event.nativeEvent.layout);
    // { x: 0, y: 44, width: 360, height: 640 }
  }}
>

当递归调用结束后会向JS端发送调用事件

for (ReactShadowNode node : onLayoutNodes) {
    mEventDispatcher.dispatchEvent(
        OnLayoutEvent.obtain(
            -1, /* surfaceId not used in classic renderer */
            node.getReactTag(),
            node.getScreenX(),
            node.getScreenY(),
            node.getScreenWidth(),
            node.getScreenHeight()));
  }

我们继续回来看cssNode.dispatchUpdates,它核心作用:将布局变化翻译成操作队列,每个节点( DFS 后序)都会调用dispatchUpdates

@Override
public void dispatchUpdates(
    float absoluteX,
    float absoluteY,
    UIViewOperationQueue uiViewOperationQueue,
    @Nullable NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) {
  if (hasNewLayout()) {
    float layoutX = getLayoutX();
    float layoutY = getLayoutY();
    int newAbsoluteLeft = Math.round(absoluteX + layoutX);
    int newAbsoluteTop = Math.round(absoluteY + layoutY);
    int newAbsoluteRight = Math.round(absoluteX + layoutX + getLayoutWidth());
    int newAbsoluteBottom = Math.round(absoluteY + layoutY + getLayoutHeight());

    int newScreenX = Math.round(layoutX);
    int newScreenY = Math.round(layoutY);
    int newScreenWidth = newAbsoluteRight - newAbsoluteLeft;
    int newScreenHeight = newAbsoluteBottom - newAbsoluteTop;

    boolean layoutHasChanged =
        newScreenX != mScreenX
|| newScreenY != mScreenY
|| newScreenWidth != mScreenWidth
|| newScreenHeight != mScreenHeight;

    mScreenX = newScreenX;
    mScreenY = newScreenY;
    mScreenWidth = newScreenWidth;
    mScreenHeight = newScreenHeight;

    if (layoutHasChanged) {
      // TODO: T26400974 ReactShadowNode should not depend on nativeViewHierarchyOptimizer
if (nativeViewHierarchyOptimizer != null) {
        nativeViewHierarchyOptimizer.handleUpdateLayout(this);
      } else {
        uiViewOperationQueue.enqueueUpdateLayout(
            getParent().getReactTag(),
            getReactTag(),
            getScreenX(),
            getScreenY(),
            getScreenWidth(),
            getScreenHeight(),
            getLayoutDirection());
      }
    }
  }
}

在这个方法中会使用到前面Yoga 引擎计算出来的各种属性,最终会调用enqueueUpdateLayout

public void enqueueUpdateLayout(
    int parentTag,
    int reactTag,
    int x,
    int y,
    int width,
    int height,
    YogaDirection layoutDirection) {
  mOperations.add(
      new UpdateLayoutOperation(parentTag, reactTag, x, y, width, height, layoutDirection));
}

这里向mOperations队列添加了UpdateLayoutOperation操作,这个步骤相信大家已经不陌生了,在前面将的createView、setChildren、manageChildren都有类似操作

调用队列操作
createViewmNonBatchedOperationsCreateViewOperation
setChildrenmOperationsManageChildrenOperation
manageChildrenmOperationsManageChildrenOperation
onBatchCompletemOperationsUpdateLayoutOperation

updateLayout中最终完成Native View的measure和layout

public synchronized void updateLayout(
    int parentTag, int tag, int x, int y, int width, int height, YogaDirection layoutDirection) {
  try {
    View viewToUpdate = resolveView(tag);

    viewToUpdate.setLayoutDirection(LayoutDirectionUtil.toAndroidFromYoga(layoutDirection));
    //调用view进行测量
    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);
      IViewManagerWithChildren parentViewManagerWithChildren;
      if (parentViewManager instanceof IViewManagerWithChildren) {
        parentViewManagerWithChildren = (IViewManagerWithChildren) parentViewManager;
      } else {
      }
      if (parentViewManagerWithChildren != null
          && !parentViewManagerWithChildren.needsCustomLayoutForChildren()) {
        //调用view进行layout
        updateLayout(viewToUpdate, x, y, width, height);
      }
    } else {
      updateLayout(viewToUpdate, x, y, width, height);
    }
  } finally {
    Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
  }
}

dispatchViewUpdates

这个方法的核心职责:将 ShadowNode 树上积攒的所有 UI 操作,打包成一个 Runnable ,投递到 UI 线程在下一帧执行。它本身不执行任何 UI 操作,只做"快照 + 投递"。

dispatchViewUpdates 管理三个独立的队列,快照时依次取出:

队列字段包含的操作
ViewCommandmViewCommandOperationsDispatchCommandViewOperation(如 scrollTo、focus)
BatchedmOperationsSetChildren、ManageChildren、UpdateLayout、UpdateProperties 等
NonBatchedmNonBatchedOperationsCreateViewOperationmNonBatchedOperationsLock

阶段一:快照队列(Native Modules 线程)

public void dispatchViewUpdates(
    final int batchId, final long commitStartTime, final long layoutTime) {

    final long dispatchViewUpdatesTime = SystemClock.uptimeMillis();
    final long nativeModulesThreadCpuTime = SystemClock.currentThreadTimeMillis();

    // ① 快照 ViewCommand 队列
    final ArrayList<DispatchCommandViewOperation> viewCommandOperations;
    if (!mViewCommandOperations.isEmpty()) {
        viewCommandOperations = mViewCommandOperations;
        mViewCommandOperations = new ArrayList<>();  // 替换为新空列表
    } else {
        viewCommandOperations = null;
    }

    // ② 快照 Batched 队列
    final ArrayList<UIOperation> batchedOperations;
    if (!mOperations.isEmpty()) {
        batchedOperations = mOperations;
        mOperations = new ArrayList<>();
    } else {
        batchedOperations = null;
    }

    // ③ 快照 NonBatched 队列
    final ArrayDeque<UIOperation> nonBatchedOperations;
    synchronized (mNonBatchedOperationsLock) {
        if (!mNonBatchedOperations.isEmpty()) {
            nonBatchedOperations = mNonBatchedOperations;
            mNonBatchedOperations = new ArrayDeque<>();
        } else {
            nonBatchedOperations = null;
        }
    }

阶段二:构建 Runnable

Runnable runOperations = new Runnable() {
    @Override
    public void run() {
        try {
            long runStartTime = SystemClock.uptimeMillis();

            // ★★★ 执行顺序严格:ViewCommand → NonBatched → Batched ★★★

            // 步骤 ① 执行 ViewCommand(带重试机制)
            if (viewCommandOperations != null) {
                for (DispatchCommandViewOperation op : viewCommandOperations) {
                    try {
                        op.executeWithExceptions();
                    } catch (RetryableMountingLayerException e) {
                        // 允许重试一次
                        if (op.getRetries() == 0) {
                            op.incrementRetries();
                            mViewCommandOperations.add(op);  // 放入下一批
                        } else {
                            ReactSoftExceptionLogger.logSoftException(TAG, ...);
                        }
                    } catch (Throwable e) {
                        ReactSoftExceptionLogger.logSoftException(TAG, e);
                    }
                }
            }

            // 步骤 ② 执行 NonBatched(CreateView)
            if (nonBatchedOperations != null) {
                for (UIOperation op : nonBatchedOperations) {
                    op.execute();
                }
            }

            // 步骤 ③ 执行 Batched(SetChildren、ManageChildren、UpdateLayout 等)
            if (batchedOperations != null) {
                for (UIOperation op : batchedOperations) {
                    op.execute();
                }
            }

            // 清除布局动画(动画只对当前批次生效)
            mNativeViewHierarchyManager.clearLayoutAnimation();

        } catch (Exception e) {
            mIsInIllegalUIState = true;  // ★ 标记非法状态,后续操作全部拒绝
            throw e;
        }
    }
};

为什么执行顺序必须是 ViewCommand → NonBatched → Batched

顺序操作类型包含为什么是这个顺序
ViewCommandscrollTo、focus、setText 等纯命令式操作,不依赖 View 是否存在(失败了可重试)
NonBatchedCreateView必须先执行,后续操作依赖 View 已存在
BatchedSetChildren、ManageChildren、UpdateLayout、UpdateProperties需要 View 已经创建好

如果 ②③ 反过来,SetChildren 会试图把一个还不存在的 View 挂到父节点上,直接崩溃。

阶段三:投递到 UI 线程

// 加入待执行列表
synchronized (mDispatchRunnablesLock) {
    mDispatchUIRunnables.add(runOperations);
}

// 两种投递方式
if (!mIsDispatchUIFrameCallbackEnqueued) {
    // 方式 A:Choreographer 未注册,直接 post 到 UI 线程
    UiThreadUtil.runOnUiThread(
        new GuardedRunnable(mReactApplicationContext) {
            @Override
            public void runGuarded() {
                flushPendingBatches();
            }
        });
}
// 方式 B:Choreographer 已注册,等下一帧自动执行

方式 B是在如下方法中注册Choreographer 回调

/* package */ void resumeFrameCallback() {
  mIsDispatchUIFrameCallbackEnqueued = true;
  if (!ReactNativeFeatureFlags.enableFabricRendererExclusively()) {
    ReactChoreographer.getInstance()
        .postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback);
  }
}

// 每帧执行
private class DispatchUIFrameCallback extends GuardedFrameCallback {
    public void doFrameGuarded(long frameTimeNanos) {
        dispatchPendingNonBatchedOperations(frameTimeNanos);  // 分帧执行 CreateView
        flushPendingBatches();                                 // 批量执行
        ReactChoreographer.getInstance()
            .postFrameCallback(DISPATCH_UI, this);             // 重新注册下一帧
    }
}

为什么用 Choreographer 而不直接 post?注释解释了:

 /**
* Choreographer FrameCallback responsible for actually dispatching view updates on the UI thread
* that were enqueued via { @link #dispatchViewUpdates(int)}. The reason we don't just enqueue
* directly to the UI thread from that method is to make sure our Runnables actually run before
* the next traversals happen:
*
* <p>ViewRootImpl#scheduleTraversals (which is called from invalidate, requestLayout, etc) calls
* Looper#postSyncBarrier which keeps any UI thread looper messages from being processed until
* that barrier is removed during the next traversal. That means, depending on when we get updates
* from JS and what else is happening on the UI thread, we can sometimes try to post this runnable
* after ViewRootImpl has posted a barrier.
*
* <p>Using a Choreographer callback (which runs immediately before traversals), we guarantee we
* run before the next traversal.
*/
private class DispatchUIFrameCallback extends GuardedFrameCallback 


ViewRootImpl.scheduleTraversals() 会调用 Looper.postSyncBarrier(),阻止 UI 线程的普通 Message 被处理。如果 dispatchViewUpdates 的 Runnable 恰好在 barrier 之后 post,它要等到下一帧 Traversal 结束才能执行,等于延迟两帧。
Choreographer 的 doFrame 在 Traversal 之前执行,保证操作在当帧完成。

想要真正了解原因,需要了解Handler的同步屏障机制,可以看我的《Android10 Framework—Handler消息系统—6.同步屏障》一文。

阶段四:执行构建的Runnable

flushPendingBatches中执行“阶段二构建的Runable”

// 步骤 ① 执行 ViewCommand(带重试机制)
if (viewCommandOperations != null) {
    for (DispatchCommandViewOperation op : viewCommandOperations) {
        try {
            op.executeWithExceptions();
        } catch (RetryableMountingLayerException e) {
            // 允许重试一次
            if (op.getRetries() == 0) {
                op.incrementRetries();
                mViewCommandOperations.add(op);  // 放入下一批
            } else {
                ReactSoftExceptionLogger.logSoftException(TAG, ...);
            }
        } catch (Throwable e) {
            ReactSoftExceptionLogger.logSoftException(TAG, e);
        }
    }
}

// 步骤 ② 执行 NonBatched(CreateView)
if (nonBatchedOperations != null) {
    for (UIOperation op : nonBatchedOperations) {
        op.execute();
    }
}

// 步骤 ③ 执行 Batched(SetChildren、ManageChildren、UpdateLayout 等)
if (batchedOperations != null) {
    for (UIOperation op : batchedOperations) {
        op.execute();
    }
}

在我前面的文章中已经分析了mNonBatchedOperations、mOperations队列中Operations的执行,现在看看mViewCommandOperations的执行,它最终调用到mNativeViewHierarchyManager.dispatchCommand

public synchronized void dispatchCommand(
    int reactTag, String commandId, @Nullable ReadableArray args) {
  ...
  ViewManager viewManager = resolveViewManager(reactTag);
  viewManager.receiveCommand(view, commandId, args);
}

可以看到它是将调用分发到各个组件viewManager的receiveCommand实现方法中

public void receiveCommand(@NonNull T root, String commandId, @Nullable ReadableArray args) {
  final ViewManagerDelegate<T> delegate = getDelegate();
  if (delegate != null) {
    delegate.receiveCommand(root, commandId, args);
  }
}

所有组件都必须提供delegate的实现,如果开发者自定义组件通过RN提供的Codegen可以自动生成

public class PhNativeAdManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & PhNativeAdManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
  public PhNativeAdManagerDelegate(U viewManager) {
    super(viewManager);
  }

  @Override
  public void receiveCommand(T view, String commandName, @Nullable ReadableArray args) {
    switch (commandName) {
      case "update" :
        mViewManager.update(view);
        break;
    }
  }
}

在receiveCommand方法中会回调真正自定义的ViewManager

@ReactModule(name = ReactAdViewManager.REACT_CLASS)
class ReactAdViewManager(context: ReactApplicationContext) : SimpleViewManager<ReactAdView>(),
    PhNativeAdManagerInterface<ReactAdView> {
    companion object {
        const val TAG = "ReactAdViewManager"
const val REACT_CLASS = "PhNativeAd"
}

    private val delegate: PhNativeAdManagerDelegate<ReactAdView, ReactAdViewManager> =
        PhNativeAdManagerDelegate(this)

    override fun getDelegate(): ViewManagerDelegate<ReactAdView> = delegate

override fun getName(): String = REACT_CLASS

override fun createViewInstance(context: ThemedReactContext): ReactAdView {
         return ReactAdView(context)
    }

    override fun onDropViewInstance(view: ReactAdView) {
        super.onDropViewInstance(view)
        LogUtils.d(ReactAdView.Companion.TAG, "onDropViewInstance ${view.hashCode()} " )
        view.destoryAd()
    }

    override fun update(view: ReactAdView?) {
    }
}

到这里就实现了:JS调用ReactAdView.update时能调用到APP端ViewManager中实现的update方法。