diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java index 15088ad95ec8c3..3d8f8efdd478d8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java @@ -1457,6 +1457,7 @@ private boolean haveExceededNonBatchedFrameTime(long frameTimeNanos) { @ThreadConfined(UI) public void doFrameGuarded(long frameTimeNanos) { int deletedViews = 0; + Stack localChildren = new Stack<>(); try { while (!mReactTagsToRemove.empty()) { int reactTag = mReactTagsToRemove.pop(); @@ -1473,6 +1474,8 @@ public void doFrameGuarded(long frameTimeNanos) { continue; } + localChildren.clear(); + ViewState thisViewState = getNullableViewState(reactTag); if (thisViewState != null) { View thisView = thisViewState.mView; @@ -1491,7 +1494,7 @@ public void doFrameGuarded(long frameTimeNanos) { while ((nextChild = ((ViewGroup) thisView).getChildAt(numChildren)) != null) { int childId = nextChild.getId(); childrenAreManaged = childrenAreManaged || getNullableViewState(childId) != null; - mReactTagsToRemove.push(nextChild.getId()); + localChildren.push(nextChild.getId()); numChildren++; } // Removing all at once is more efficient than removing one-by-one @@ -1516,13 +1519,21 @@ public void doFrameGuarded(long frameTimeNanos) { } } if (childrenAreManaged) { - // Push tag onto the stack so we reprocess it after all children - mReactTagsToRemove.push(reactTag); - } else { - mTagToViewState.remove(reactTag); - onViewStateDeleted(thisViewState); + // Push tags onto the stack so we process all children + mReactTagsToRemove.addAll(localChildren); } + // Immediately remove tag and notify listeners. + // Note that this causes RemoveDeleteTree to call onViewStateDeleted + // in a top-down matter (parents first) vs a bottom-up matter (leaf nodes first). + // Hopefully this doesn't matter but you should ensure that any custom + // onViewStateDeleted logic is resilient to both semantics. + // In the initial version of RemoveDeleteTree we attempted to maintain + // the bottom-up event listener behavior but this causes additional + // memory pressure as well as complexity. + mTagToViewState.remove(reactTag); + onViewStateDeleted(thisViewState); + // Circuit breaker: after processing every N tags, check that we haven't // exceeded the max allowed time. Since we don't know what other work needs // to happen on the UI thread during this frame, and since this works tends to be @@ -1542,6 +1553,7 @@ public void doFrameGuarded(long frameTimeNanos) { // other mounting instructions have been executed, all in-band Remove // instructions have already had a chance to execute here. mErroneouslyReaddedReactTags.clear(); + mReactTagsToRemove.clear(); } } }