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

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

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

  • updateViewHierarchy:负责View视图的Yoga 布局计算
  • dispatchViewUpdates:投递到 UI 线程,对View进行更新
java 复制代码
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事件
scss 复制代码
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
  • ② 构建两个等长的平行数组
数组 类型 内容
nativePointers long[] 每个 YogaNode 对应的 C++ YGNodeRef 指针
nodes YogaNodeJNIBase[] 每个 Java 层的 YogaNode 对象
  • ③ 调用 C++ Yoga 引擎计算布局
ini 复制代码
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层使用

scss 复制代码
//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中取值

java 复制代码
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 的格式如下:

css 复制代码
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 线程操作

scss 复制代码
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 在递归过程中不断累加:

ini 复制代码
root (absoluteX=0, absoluteY=0)
  └─ childA (absoluteX=0 + root.layoutX, absoluteY=0 + root.layoutY)
       └─ grandChild (absoluteX=childA.absoluteX + childA.layoutX, ...)

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

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

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

less 复制代码
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

ini 复制代码
@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

arduino 复制代码
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都有类似操作

调用 队列 操作
createView mNonBatchedOperations CreateViewOperation
setChildren mOperations ManageChildrenOperation
manageChildren mOperations ManageChildrenOperation
onBatchComplete mOperations UpdateLayoutOperation

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

arduino 复制代码
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 管理三个独立的队列,快照时依次取出:

队列 字段 包含的操作
ViewCommand mViewCommandOperations DispatchCommandViewOperation(如 scrollTo、focus)
Batched mOperations SetChildren、ManageChildren、UpdateLayout、UpdateProperties 等
NonBatched mNonBatchedOperations CreateViewOperation mNonBatchedOperationsLock

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

ini 复制代码
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

scss 复制代码
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

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

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

阶段三:投递到 UI 线程

scss 复制代码
// 加入待执行列表
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 回调

scala 复制代码
/* 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?注释解释了:

scala 复制代码
 /**
* 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"

scss 复制代码
// 步骤 ① 执行 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

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

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

less 复制代码
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可以自动生成

scala 复制代码
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

kotlin 复制代码
@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方法。

相关推荐
caicai_xiaobai2 小时前
QT搭建安卓开发环境
android
YF02112 小时前
Android 异形屏与横屏全屏沉浸式适配技术方案
android·app
2501_941982053 小时前
通过 API 实时监听企业微信外部群变更事件并同步本地数据库
android·自动化·企业微信·rpa
白雪落青衣4 小时前
buuoj course 1详细解析
android
恋猫de小郭4 小时前
Android 发布全新性能分析器,实用性和性能大升级
android·前端·flutter
Kapaseker4 小时前
为什么 Java 的数组需要 new 出来
android·java·kotlin
黄林晴4 小时前
颠覆开发!Google AI Studio 一句话生成原生 Android App
android·google io
恋猫de小郭4 小时前
Flutter 3.44 发布啦,超级大版本更新!!!
android·flutter·ios
zb200641205 小时前
Laravel10.x重磅升级:新特性全解析
android