run-android fails with "No connected devices!" on a cold-booting emulator (waits for adb, not for sys.boot_completed)

未關閉
#2,824 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 2 天內回覆

@huytdps13400 已經在處理了。

開始於 2026年8月26日。

  • #2830 來自 @huytdps13400 —— 未關閉

評估

難度
3/5
預估耗時
1-2 天
新手友好度
72/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
冷清
技術堆疊
android, react-native, typescript
領域
cli, mobile-dev

研究方向

從 packages/cli-platform-android/src/commands/runAndroid/tryLaunchEmulator.ts 開始,檢查 launchEmulator 的 adb 就緒檢查及其 30 秒逾時。使用 issue 中的 emulator 和 adb 指令重現冷啟動耗時。完成的標準是 run-android 等待 sys.boot_completed,並留出足夠時間讓 framework 啟動,同時不會導致 Gradle 過早安裝或產生無端逾時。

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

描述

Environment

npx react-native info is not representative here (pnpm monorepo), so the relevant
versions collected by hand:

System:
    OS: macOS 26.5.2 (25F84)
    Node: 24.15.0
    Java: openjdk 17.0.20 2026-07-21 LTS
  SDKs:
    Android SDK Platform-Tools: 37.0.0 (adb 1.0.41)
    Android Emulator: 36.6.11.0 (build_id 15507667)
  npmPackages:
    react-native: 0.86.0
    @react-native-community/cli: 20.1.0
    @react-native-community/cli-platform-android: 20.1.0

Also reproduces against main — tryLaunchEmulator.ts is unchanged since
2514405b (Apr 2024), and the two relevant lines are identical in v20.2.0.

Description

When run-android has to launch the emulator itself, and that emulator does a
genuine cold boot (no quick-boot snapshot — a fresh AVD, an AVD whose
snapshot was invalidated, -no-snapshot-load, or CI), the build fails:

error Failed to install the app. Command failed with exit code 1: ./gradlew app:installDevDebug …
com.android.builder.testing.api.DeviceException: No connected devices!

with Gradle having logged Device is still booting just before.

Root cause

launchEmulator in
packages/cli-platform-android/src/commands/runAndroid/tryLaunchEmulator.ts
treats the emulator as ready the moment adb devices lists it:

const bootCheckInterval = setInterval(async () => {
  const devices = adb.getDevices(adbPath);
  const connected = port
    ? devices.find((d) => d.includes(`${port}`))
    : devices.length > 0;
  if (connected) {
    cleanup();
    resolve(true);          // ← too early
  }
}, 1000);

adb.getDevices only keeps devices whose state is device
(adb.ts#L25),
which is an honest signal — but it is a signal about adbd, not about the
Android framework. adbd starts early in boot; ActivityManager finishes much
later. run-android therefore resolves, hands off to Gradle, and Gradle's
install task refuses a device that is still booting.

Measurements

Timing one cold boot on the machine above (AVD launched at t=0):

Event t
adb devices first reports state device 9.3 s
getprop sys.boot_completed first returns 1 20.2 s

That is a ~10.9 s window in which the current check is satisfied and the
device cannot actually be installed to. Anything that fits inside that window —
Gradle configuration being warm, a cached build — loses the race. Anything that
takes longer than 10.9 s (a cold Gradle daemon, a first build) accidentally
survives it, which is why this is so often written off as flaky.

Suggested fix

Gate on sys.boot_completed, which is set once ActivityManager has finished
starting and is therefore a strictly later — and safe — signal:

adb -s <serial> shell getprop sys.boot_completed

One consequence worth calling out: with that gate in place the existing
timeout = 30 no longer budgets for "adb answers", it budgets for "the whole
framework is up". On this machine that is 20.2 s of the 30 s, leaving ~10 s of
headroom; slower hardware and CI runners exceed it routinely. The timeout needs
to grow alongside the gate, otherwise the fix converts a wrong-success into a
spurious timeout.

I have this running as a local patch and can open a PR — say the word and I'll
send it. (Happy to make the timeout a flag instead of a constant if you'd
prefer that shape.)

Reproducible Demo

npx @react-native-community/cli init BootRace --version 0.86.0
cd BootRace

# make sure no emulator is running, and force a true cold boot
adb devices          # expect: empty
emulator -list-avds  # pick one, then wipe its snapshot:
emulator @<avd> -no-snapshot-save -wipe-data &  # let it boot once, then kill it

npx react-native run-android

The failure is timing-sensitive by nature. To see the window directly, without
building anything:

emulator @<avd> -no-snapshot-load &
START=$(date +%s)
while ! adb devices | grep -q "device$"; do sleep 0.1; done
echo "adb says 'device' after $(( $(date +%s) - START ))s"
while [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" != "1" ]; do sleep 0.1; done
echo "sys.boot_completed after $(( $(date +%s) - START ))s"

Every second between those two lines is a second in which run-android believes
the emulator is ready and Gradle disagrees.

主要語言
TypeScript
星號
2.9k
分支
952
PR 合併指標
30 天內沒有已合併 PR

環境準備

從這裡開始

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

react-native-community/cli 的其他 Issue

查看 react-native-community/cli 的全部 Issue

相似的 Issue

更多 TypeScript Issue

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

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