本篇文章继续分析onBatchComplete方法,onBatchComplete 是 JS 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在子节点之后执行
absoluteX 和 absoluteY 在递归过程中不断累加:
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方法。