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

[Android] Idle apps keep the Choreographer running at ~60 doFrames/s (zero work, zero frames rendered) — four frame callbacks re-arm unconditionally

未關閉
#58,367 5 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

  • #58368 來自 @capt-muji —— 已關閉,未合併

評估

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

研究方向

從 JavaTimerManager.kt、FabricEventDispatcher.kt、NativeAnimatedModule.kt 和 FabricUIManager.java 中列出的四個重新啟用點開始,然後執行提供的 Android 重現命令和閒置追蹤命令。驗證處於閒置狀態的應用程式產生的 Choreographer doFrames 為零,同時在存在計時器、事件、動畫和 mount 工作時,它們仍能正常運作。

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

描述

Needs: Attention Needs: Repro

Summary

Every React Native Android app keeps the main-thread Choreographer armed at ~60 doFrames/second while completely idle in the foreground — no timers pending, no animations running, no mount items, zero frames rendered — on every Android version we tested (API 28 → 36). It is pure CPU/battery waste present in stock template apps with zero app code or third-party dependencies involved.

Measured on a stock [email protected] template app (npx @react-native-community/cli init --version 0.86.3), Release build, app foregrounded and settled, no interaction:

Device API doFrames / 10s idle median / p90 / max doFrame App process CPU Frames rendered (gfxinfo)
OnePlus 3T (SD820) 28 597–598 1.95 / 2.81 / 15.86 ms ~22.5% 0
OnePlus 5T (SD835) 29 599–601 0.99–1.82 / 2.11 / 14.67 ms ~9.3% 0
OPPO Find X8 (Dimensity 9400) 36 587–601 0.67–0.86 / 0.94–1.21 / 2.83 ms ~3.5–6.8% 0

The loop stops when the app is backgrounded (host pause) and restarts on resume. iOS is unaffected (Choreographer is Android-only).

Root cause

Four Android frame callbacks re-post themselves unconditionally at the end of their own doFrame, and are initially armed at host resume, with no pending-work check:

# Class Re-arm site (0.86.3)
1 JavaTimerManager$TimerFrameCallback JavaTimerManager.kt:317 — posts itself after every frame even with an empty timer queue
2 FabricEventDispatcher$ScheduleDispatchFrameCallback FabricEventDispatcher.kt:153 — doFrame re-posts unless stopped; events are actually dispatched synchronously in dispatchEvent, this callback only notifies BatchEventDispatchedListeners
3 NativeAnimatedModule$animatedFrameCallback$1 NativeAnimatedModule.kt:353 — enqueueFrameCallback() outside the hasActiveAnimations() guard (the guard protects the work, not the re-arm)
4 FabricUIManager$DispatchUIFrameCallback FabricUIManager.java:1666 — finally { schedule(); } re-schedules even when all queues drained

Runtime attribution: replacing ReactChoreographer with a logging build (log POST/RUN/REMOVE with callback class names) shows, in a 10s idle window on the stock template:

604 RUN  type=NATIVE_ANIMATED_MODULE cb=com.facebook.react.animated.NativeAnimatedModule$animatedFrameCallback$1
604 RUN  type=DISPATCH_UI            cb=com.facebook.react.fabric.FabricUIManager$DispatchUIFrameCallback
603 POST type=NATIVE_ANIMATED_MODULE cb=com.facebook.react.animated.NativeAnimatedModule$animatedFrameCallback$1
603 POST type=DISPATCH_UI            cb=com.facebook.react.fabric.FabricUIManager$DispatchUIFrameCallback

with the timers/dispatcher callbacks cycling the same way on stock builds (source-verified).

Reproduction

npx @react-native-community/cli@latest init repro --version 0.86.3 --skip-git-init
cd repro/android && ./gradlew assembleRelease
adb install -r app/build/outputs/apk/release/app-release.apk
adb shell am start -W -n com.repro/com.repro.MainActivity
# settle ≥40s, then:
adb shell atrace -t 10 -b 32768 view input -z -o /data/local/tmp/idle.atrace.gz
adb pull /data/local/tmp/idle.atrace.gz .
# → ≈600 "Choreographer#doFrame" sections for the app pid, each containing only
#   an empty "animation" stage; Android 16 also exposes DoFrameCB-IsEmptyDoFrame=1
adb shell dumpsys gfxinfo com.repro reset && sleep 10 \
  && adb shell dumpsys gfxinfo com.repro | grep "Total frames rendered"   # → 0

Control: a raw Java Activity with setContentView(new View(this)) and zero dependencies receives 0 doFrames at idle — the OS floor is clean; this is entirely framework-driven.

Validation of the fix direction

Patched-framework experiments on the stock template (Find X8):

  • Demand-gating #1 and #2 (re-arm only when work exists): app fully functional, but the loop persists (pumps #3/#4 still active) — proving each pump independently keeps the Choreographer armed.
  • Gating #3 and #4 as well: 0 doFrames at idle, ~0% CPU, app alive and foreground — the loop fully collapses (notably, exactly one dropped re-post per pump is enough to kill each permanently: each callback was keeping only itself alive).

Proposed fix

Make each pump re-arm only when it has work, re-arming lazily from its registration path (PR to follow):

  1. TimerFrameCallback.doFrame: re-post only if (timers.isNotEmpty()); re-arm from createTimer.
  2. ScheduleDispatchFrameCallback.doFrame: never re-post (one-shot per schedule request).
  3. animatedFrameCallback.doFrameGuarded: enqueueFrameCallback() inside the hasActiveAnimations() branch; re-arm in didDispatchMountItems after operation batches execute (startAnimatingNode et al.).
  4. DispatchUIFrameCallback.doFrameGuarded: re-schedule only while mount items remain pending.

The newer subsystems (AnimationBackend, EventBeat) already follow this demand-gated pattern — these four are the legacy stragglers.

Impact

Every RN Android app, on every Android version, burns main-thread CPU at vsync rate whenever its screen is merely visible — reading, idling, screen-on standby. Worst on low-end hardware (22.5% of one core measured on a 2016 SoC) where it also competes with real frame work (15.9ms worst-case idle doFrames measured). After the fix, idle RN apps are frame-silent like native apps, with no behavior change whenever work exists.

主要語言
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 摘要。