Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

[Android][Fabric] Fatal "Cannot remove child at index" — focus re-request inside ViewGroup.removeViewInternal throws "descendant of this view" during Fabric removal (0.86.0)

未關閉
#57,862 3 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
45/100
Issue 類型
缺陷
描述清晰度
基本清楚
活躍度
冷清
技術堆疊
android, kotlin, react-native
領域
mobile

研究方向

從 ReactViewGroup 的移除方法開始,並比較 #56182 中的無障礙處理,然後追蹤 SurfaceMountingManager.removeViewAt 和 ReactClippingViewManager.removeViewAt。當與無障礙相關的 IllegalArgumentException 不再從 Fabric 移除作業中逸出,且所描述的 Android 無障礙情境能夠避免致命的 Cannot remove child 錯誤時,即視為完成。

由索引模型根據 Issue 內容生成。

描述

Needs: Attention Needs: Repro

Description

With an accessibility service enabled, removing a focused view during a Fabric mount batch reliably (if intermittently) kills the app with:

java.lang.IllegalStateException: Cannot remove child at index N from parent ViewGroup [tag],
only M children in parent. Warning: childCount may be incorrect!
  at com.facebook.react.fabric.mounting.SurfaceMountingManager.removeViewAt(SurfaceMountingManager.kt:525)
Caused by: java.lang.IllegalArgumentException: parameter must be a descendant of this view
  at android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:6478)

ReactHostImpl.handleHostException then calls destroy() unconditionally, so this is fatal rather than recoverable.

Root cause

The IllegalArgumentException is not thrown by the removal itself — it comes from the accessibility event that the focus re-request fires inside removeViewInternal. Captured on device, read bottom-up:

MountItemDispatcher.dispatchMountItems / IntBufferBatchMountItem.execute
SurfaceMountingManager.removeViewAt(SurfaceMountingManager.kt:504)
ReactClippingViewManager.removeViewAt(ReactClippingViewManager.kt:68)
android.view.ViewGroup.removeViewAt(ViewGroup.java:5702)
android.view.ViewGroup.removeViewInternal(ViewGroup.java:5775)
android.view.View.rootViewRequestFocus(View.java:8827)
android.view.View.requestFocus -> ViewGroup.onRequestFocusInDescendants (recursion)
android.view.View.handleFocusGainInternal(View.java:8591)
android.view.View.onFocusChanged(View.java:8953)
android.view.View.sendAccessibilityEvent(View.java:9178)
android.view.ViewGroup.dispatchPopulateAccessibilityEventInternal(ViewGroup.java:3700)   (~10 deep)
android.view.ViewGroup$ChildListForAccessibility.init(ViewGroup.java:9334)
android.view.ViewGroup$ViewLocationHolder.init(ViewGroup.java:9520)
android.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.java:6407)
android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:6478)   <- throws

So: the removed child held focus → removeViewInternal calls rootViewRequestFocus() → the focus gain fires an accessibility event → ChildListForAccessibility sorts children via offsetDescendantRectToMyCoords while the removed child is still mid-detach → IllegalArgumentException.

Because removeFromArray() has already run at that point, the removal has actually succeeded and the view tree is consistent. But the exception propagates out of ViewGroup.removeViewAt, and SurfaceMountingManager.removeViewAt's catch (e: RuntimeException) converts it into the fatal IllegalStateException quoted above — reporting a childCount inconsistency that isn't the real problem.

Why #56182 doesn't cover this

#56182 added a soft-catch for exactly this IllegalArgumentException — but only around addChildrenForAccessibility, i.e. the accessibility build path. main still has nothing on the removal path, and ReactViewGroup does not override any of removeViewAt / removeViews / removeViewsInLayout / removeAllViewsInLayout.

Note this is a different bug from #57800, which is the Fabric differ emitting a Remove against a wrong parent tag and throws earlier in the same method (SurfaceMountingManager.kt:444, "Unable to remove a view from a view that is not a ViewGroup", no Caused by). This one always carries the descendant of this view cause and requires an active accessibility service; that one requires neither.

Suggested fix

Mirror the #56182 pattern onto the removal path — wrap the super calls in ReactViewGroup's removal methods and soft-catch an IllegalArgumentException whose message contains descendant of this view. Since the child is already detached when it throws, swallowing leaves the tree consistent.

We currently ship precisely that as a build-time ASM transform (RN core is a prebuilt AAR for us, so a source patch would need react.internal.buildFromSource). With it in place the throw is caught, the app survives, and no Cannot remove child follows — verified on device with the stack above.

Steps to reproduce

No deterministic reproducer — it's a race between a Fabric removal and an accessibility service's tree traversal, and we could not force it with scripted input (adb shell input) even across ~20 list remounts and ~30 fling gestures. It surfaces readily in ordinary manual use. Conditions that reproduce it for us:

  1. Android 16 or 17 device with at least one accessibility service enabled (no TalkBack needed — a password manager's autofill service is enough).
  2. A long virtualized list whose rows contain heavy focusable native views (video players, WebViews), with removeClippedSubviews enabled so that recycling produces frequent native view removals.
  3. Scroll the list, or remount it (e.g. by changing its key).
  4. Watch for Cannot remove child at index with a Caused by: parameter must be a descendant of this view.

Two notes for anyone trying to reproduce: the removed view must hold focus for rootViewRequestFocus() to run, and the default logcat ring buffer rotates too fast under WebView logging to catch it after the fact (adb logcat -G 32M helps).

React Native Version

0.86.0

Affected Platforms

Runtime - Android

Areas

Fabric - The New Renderer

Output of npx @react-native-community/cli info

React Native: 0.86.0 (new architecture, bridgeless)
React: 19.2.3
Expo: 57.0.8
Hermes: bundled
Devices observed crashing: Android 16, and Android 17 (Pixel 9 Pro, CP2A.260805.005)
AGP: 8.12.0, Gradle: 9.3.1
Accessibility services active during repro: a password manager's autofill service, plus a
third-party status-bar utility (neither is TalkBack; either alone is sufficient)

— drafted by Claude (AI)

主要語言
C++
星號
127k
分支
25.3k
PR 合併指標
30 天內沒有已合併 PR

環境準備

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

react/react-native 的其他 Issue

查看 react/react-native 的全部 Issue

相似的 Issue

更多 C++ Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。