Native Android Runtime Debugging
Use this skill when the core problem is not app architecture or visual design, but runtime truth: did the current APK/AAB build, install, launch, and render on the intended emulator or device — and, once that's proven, why is it slow, freezing, or leaking memory?
This skill owns stale-build suspicion, emulator drift, malformed APKs, ADB failures, Gradle cache corruption, ProGuard/R8 stripping, Compose recomposition issues, ANR/jank/memory/startup performance triage, and the proof loop required before trusting screenshots, UI behavior, or downstream API/auth debugging.
Platform Currency (as of 2026-07-11)
- Android 17 (API 37) shipped 2026-06-16; Android 16 (API 36) is the prior release. Verify at developer.android.com/about/versions before citing a specific version as latest — this changes roughly annually.
- Google Play target API policy: new apps and app updates must target API level 36 (Android 16) or higher as of the 2026-08-31 deadline, with a one-time extension to 2026-11-01 available by request. Re-verify at developer.android.com/google/play/requirements/target-sdk rather than hardcoding a number.
- 16KB page-size support is mandatory for app updates shipping native (
.so) libraries as of 2026-05-01. An app whose ELF LOAD segments are still 4KB-aligned runs in a 16KB backcompat mode; test on a 16KB-page-size emulator image, not just the default. Verify current enforcement at developer.android.com/guide/practices/page-sizes.
- AGP 9.x is the current stable Android Gradle Plugin series (crossed the 9.0 major boundary in early 2026); it raises the minimum Gradle version and enables built-in Kotlin support by default. Verify current minimums at developer.android.com/build/releases/gradle-plugin-roadmap before assuming an AGP-8-era Gradle wrapper still works.
- Android Studio current stable is the Quail series (2026.1.x); the prior Otter series (2025.2.x) is now legacy. Verify the live release name/version at developer.android.com/studio/releases — Google renames each yearly series and this drifts within months.
- Perfetto, not Systrace, is the current system- and app-tracing stack; Systrace is retired. See references/performance-triage.md.
Quick Reference
| Symptom |
First Move |
Notes |
| Screenshot does not match source |
Uninstall + clean build + install + launch |
Assume stale APK first |
INSTALL_FAILED_UPDATE_INCOMPATIBLE |
adb uninstall <pkg> then reinstall |
Signing key mismatch between builds |
| App crashes immediately on launch |
Check logcat for first FATAL EXCEPTION |
Missing Activity in manifest or ProGuard stripping |
ClassNotFoundException / NoSuchMethodError at runtime |
Check ProGuard/R8 keep rules |
Minification removed referenced class or method |
| Emulator stuck on boot animation |
Cold boot AVD or wipe data |
Snapshot corruption is common after SDK updates |
BUILD FAILED with dependency resolution |
Check libs.versions.toml and repositories block |
Version catalog mismatch or missing repo |
| Gradle sync fails in Android Studio |
./gradlew --stop && ./gradlew --refresh-dependencies |
Daemon or cache corruption |
adb: device not found |
adb kill-server && adb start-server && adb devices |
USB debugging off or emulator not connected |
| App shows old layout after Compose changes |
Clean build; check recomposition stability |
Incremental build may not invalidate Compose output |
| Resources not found at runtime |
Inspect APK with aapt2 dump resources |
Missing from merged manifest or wrong resource qualifier |
minSdk version error on install |
Check device API level vs minSdk in build.gradle.kts |
APK requires higher API than device provides |
| KSP/KAPT annotation processing errors |
Check processor version matches Kotlin version |
KSP is tightly coupled to the Kotlin compiler version |
| Logcat shows nothing from the app |
Filter by PID: adb logcat --pid=$(adb shell pidof <pkg>) |
Default logcat is too noisy to be useful |
| App works on emulator but crashes on device |
Check for x86-only native libs (.so) |
Emulator runs x86; most devices run ARM |
| Compose UI renders but interactions do nothing |
Check clickable modifier order and state hoisting |
Modifier order determines hit-testing; state must be hoisted |
CalledFromWrongThreadException after StateFlow update |
Move _uiState.value = ... back onto the main dispatcher |
Keep withContext(Dispatchers.IO) { ... } pure: return a value, mutate state outside the block |
ConcurrentModificationException inside SnapshotStateObserver |
Upgrade to Compose UI 1.10.1+ OR eliminate off-main state mutation |
Compose 1.10.0-rc01 fixed a reentrant-modification race; 1.10.1 fixed pausable composition + LookaheadScope |
Nested Popup pinned to screen top instead of anchor |
Upgrade to Compose UI 1.10.1+ |
PopupPositionProvider absolute-coordinate bug, fixed in 1.10.0 / 1.10.1 |
Argument type mismatch: Function0<Unit> vs @Composable ComposableFunction0<Unit> |
Verify kotlin("plugin.compose") is applied and version-locked to the Kotlin compiler |
Kotlin 2.x ships the Compose compiler bundled; missing/stale plugin declaration skips the composable transform |
Release crash SerializationException: Serializer for class 'X' is not found or ExceptionInInitializerError |
Add explicit keep rules for @Serializable classes; run a release-variant smoke test |
kotlinx-serialization 1.9.0+ + R8 full mode strips reflectively-referenced serializers |
LazyColumn recomposes whole list on every unrelated update |
Hoist derived lists to ViewModel; remember(id) { } around item callbacks |
Strong Skipping Mode (Kotlin 2.x) compares unstable params by reference, not structural equality |
| System shows "App isn't responding" or Play Console reports an ANR cluster |
Read the ANR trace first, do not open a profiler yet |
Blocked-thread stack is already in the trace; classify by which watchdog fired (input/broadcast/service) |
| Scrolling stutters or animation skips frames, app otherwise responsive |
adb shell dumpsys gfxinfo <pkg> framestats, then Perfetto if the cause isn't obvious |
This is jank, not an ANR — different tool, different budget math (16.67ms @60Hz, 8.33ms @120Hz) |
| App feels slower over a session or gets OOM-killed |
adb shell dumpsys meminfo <pkg> trend, then LeakCanary (debug) or a heap dump (release) |
Confirm monotonic growth before assuming a leak; one-time cache warm-up is not a leak |
| Cold start feels slow but no measurement exists yet |
Macrobenchmark StartupTimingMetric with explicit StartupMode.COLD |
Do not guess a fix before there's a baseline number; warm/hot starts have different budgets than cold |
| Dropped frame blamed on "GC" without checking the trace |
Open the Perfetto thread-state track for that frame |
Binder/IPC contention produces the same visible symptom as a GC pause and is commonly misdiagnosed as GC |
When to Use This Skill
Use this skill to:
- Prove a fresh uninstall/install/launch loop for a native Android app
- Diagnose stale installs or stale screenshots in emulator-driven workflows
- Inspect built APK/AAB contents when installation or launch fails
- Debug emulator boot, ADB connectivity, and device state drift
- Investigate Gradle build failures, dependency resolution, and cache corruption
- Triage R8/ProGuard stripping errors that surface only at runtime
- Debug Compose recomposition, stability, and incremental compilation artifacts
- Classify and triage ANRs, jank, memory leaks, and slow startup using Perfetto, Macrobenchmark, LeakCanary, and StrictMode
- Establish runtime truth before routing to feature implementation, design, or test skills
Core Workflow
- Discover the project entrypoint:
build.gradle.kts (root and app module), applicationId, build variant (debug/release), target device or emulator.
- Check environment:
ANDROID_HOME set, JDK version matches AGP requirement, Gradle wrapper present, adb devices shows the target, emulator booted or device connected.
- Build the app:
./gradlew assembleDebug.
Confirm output ends with BUILD SUCCESSFUL.
- Inspect the APK:
aapt2 dump badging app/build/outputs/apk/debug/app-debug.apk.
Verify applicationId, versionCode, minSdk, declared activities, and expected resources.
- Remove stale installs:
adb uninstall <applicationId>.
- Install the fresh build:
adb install -r app/build/outputs/apk/debug/app-debug.apk.
- Launch and capture proof:
adb shell am start -n <applicationId>/<fully.qualified.Activity>, then capture logcat output and a screenshot (adb exec-out screencap -p > proof.png).
- Only after the app is freshly running, debug feature behavior, design, auth, or API issues.
- Route onward:
ASCII Flow
Android runtime failure
-> Capture exact device, API, build, repro, and logcat window
-> Prove fresh install and launch state
-> Classify: crash, ANR, lifecycle, Compose, network, storage, or release
-> Inspect stack, logs, profiler, and UI hierarchy evidence
-> Patch the smallest failing path
-> Rerun same repro and keep before/after proof
Runtime Proof Loop
- Prefer one bounded loop:
discover -> check env -> clean build -> inspect APK -> uninstall -> install -> launch -> capture evidence -> then debug.
- If any step fails, stop there and fix that layer before moving deeper.
- Do not trust screenshots from an emulator session that has not been tied to the current build.
- Do not trust "BUILD SUCCESSFUL" on its own; install and launch proof still matter.
- Verify launch at three levels: the process started (
adb shell pidof <pkg>), the correct Activity is in the foreground (adb shell dumpsys activity top), and the expected content rendered (screenshot or UI automator dump).
- If a screenshot contradicts the expected state, check the installed APK's
versionCode against the build output before trusting the capture.
See references/runtime-proof-loop.md.
Stale-Build Heuristics
- If the UI on screen does not match current source, suspect stale APK before any other explanation.
- If the app keeps showing an old layout after rebuild, uninstall the installed app and reinstall the fresh APK.
- If Gradle says build succeeded but the app looks old, check the APK timestamp and path — the wrong variant or module may have been built.
- If the emulator was already running and UI state is surprising, re-prove install and launch before reasoning about app state.
- If incremental compilation is suspected (source moved between modules, annotation processor stale, Compose compiler version mismatch), do a full clean build before anything else.
- If the Gradle daemon has been running for hours with changing
jvmargs or plugin versions, stop it with ./gradlew --stop and rebuild.
See references/stale-build-triage.md.
APK/AAB Health Checklist
When installation or launch fails, inspect the built artifact directly:
Gradle Troubleshooting Patterns
Gradle is the most common source of build failures. Common patterns: dependency resolution, version catalog drift, KSP/KAPT version mismatches, AGP compatibility, and configuration cache invalidation.
See references/gradle-build-troubleshooting.md.
Compose Debugging
Jetpack Compose introduces recomposition, stability, and compiler-level concerns that do not exist in View-based UI. When Compose UI behaves unexpectedly — interactions silently fail, UI does not update, or performance is poor — use Compose-specific debugging before blaming app logic.
See references/compose-debugging.md.
Performance Triage: ANR, Jank, Memory, Startup
Once build/install/launch is proven, "the app is slow" or "the app freezes" is not one problem — it is an ANR, jank, a memory leak, or a slow start, and each has a different first move. Classify before reaching for a profiler:
- System ANR dialog or a Play Console ANR cluster exists -> read the ANR trace, find the blocked thread and which watchdog (input/broadcast/service) fired.
- Stutter or skipped frames while the app stays responsive -> jank. Start with
framestats, escalate to Perfetto.
- The app slows down over a session or gets OOM-killed -> memory. Confirm a growth trend with
dumpsys meminfo before assuming a leak.
- The complaint is specifically about time-to-first-frame after tapping the icon -> startup. Measure with Macrobenchmark before changing code.
- None of the above -> it is a correctness bug, not a performance problem; route to normal crash/log triage.
When not to microbenchmark: a microbenchmark measures one function's CPU cost in isolation. It cannot see cold-start AOT/JIT/class-loading cost (use Macrobenchmark instead), cannot see multi-frame jank pipeline stages (use Perfetto/framestats first), and should never run before a trace has confirmed the function is actually on the critical path.
Common misdiagnosis to catch: a dropped frame during an IPC-heavy screen looks identical to a GC pause from the outside. Check the Perfetto thread-state track — GC pauses show explicit GC markers; binder contention shows the calling thread blocked on a transaction to another process. Do not write "it's GC" into a report without checking the trace.
See references/performance-triage.md for the full decision tree, ANR thresholds, frame budget math, Perfetto/Macrobenchmark/StrictMode/LeakCanary usage, ART GC behavior, current Play Console vitals thresholds, and more misdiagnosis patterns.
ProGuard/R8 Triage
R8 (the default code shrinker) can remove classes, methods, and fields that are referenced only via reflection, serialization, or framework conventions. When release builds crash with ClassNotFoundException, NoSuchMethodError, or silent data corruption, suspect R8 stripping before app logic.
See references/proguard-r8-triage.md.
Route Elsewhere
- Use software-android-native once runtime truth is established and the task becomes feature implementation, Kotlin architecture, or Compose layout.
- Use qa-testing-android once the app is buildable and installable and the task becomes test execution, device matrix, or flake control.
- Use qa-debugging for general debugging methodology not specific to Android build/install/launch.
- Use software-mobile for platform choice, iOS, or cross-platform tradeoffs.
Navigation
References
| Resource |
Purpose |
| references/runtime-proof-loop.md |
Canonical build/install/launch verification loop |
| references/stale-build-triage.md |
Heuristics for stale APKs, Gradle cache, and emulator drift |
| references/gradle-build-troubleshooting.md |
Dependency resolution, version catalogs, AGP, and daemon issues |
| references/compose-debugging.md |
Recomposition tracking, stability, compiler reports, and common pitfalls |
| references/proguard-r8-triage.md |
R8 stripping, keep rules, mapping files, and library-specific rules |
| references/performance-triage.md |
ANR/jank/memory/startup decision tree, Perfetto, Macrobenchmark, StrictMode, LeakCanary, ART GC, Play Console vitals, misdiagnoses |
| data/sources.json |
Primary Android platform and tooling sources |
Templates
| Template |
Purpose |
| assets/template-android-runtime-debug-request.md |
Short request format for proof-first runtime debugging |
Related Skills
| Skill |
Purpose |
| software-android-native |
Native Android implementation and architecture after runtime truth exists |
| qa-testing-android |
Espresso, UI Automator, Compose testing, and device matrix after installability is proven |
| qa-debugging |
General debugging methodology not specific to Android runtime |
| software-mobile |
Mobile platform choice and cross-platform tradeoffs |
Fact-Checking
- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
- Prefer developer.android.com for ADB, aapt2, AGP, and build system behavior.
- Prefer upstream Gradle documentation for wrapper, daemon, caching, and dependency resolution.
- Prefer developer.android.com/topic/performance for ANR thresholds, vitals thresholds, tracing (Perfetto), and Macrobenchmark/Baseline Profile guidance — these are Google-controlled policy numbers and tool defaults that move.
- Prefer perfetto.dev and square.github.io/leakcanary for Perfetto and LeakCanary specifics respectively.
- Treat repo-specific build variants, applicationId, module structure, and Gradle properties as local facts that must be discovered, not assumed.
Learnings Loop
Before applying this skill on a non-trivial task, read learnings.consolidated.md in this directory (and learnings.md if present).
After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to learnings.md via agents-skills-feedback-loop/scripts/append_learning.py. Do not modify SKILL.md itself.
1---
2name: software-android-runtime-debugging
3description: "Build/install/launch proof, ANR/jank/memory triage, and stale-build debugging for native Android apps. Use when runtime truth, performance, or crash root cause is in doubt."
4compatibility: Portable core. Works on Claude Code and Codex.
5version: "1.1"
6last_validated: 2026-07-11
7---
8
9# Native Android Runtime Debugging
10
11Use this skill when the core problem is not app architecture or visual design, but runtime truth: did the current APK/AAB build, install, launch, and render on the intended emulator or device — and, once that's proven, why is it slow, freezing, or leaking memory?
12
13This skill owns stale-build suspicion, emulator drift, malformed APKs, ADB failures, Gradle cache corruption, ProGuard/R8 stripping, Compose recomposition issues, ANR/jank/memory/startup performance triage, and the proof loop required before trusting screenshots, UI behavior, or downstream API/auth debugging.
14
15## Platform Currency (as of 2026-07-11)
16
17- **Android 17 (API 37)** shipped 2026-06-16; Android 16 (API 36) is the prior release. Verify at [developer.android.com/about/versions](https://developer.android.com/about/versions) before citing a specific version as latest — this changes roughly annually.
18- **Google Play target API policy**: new apps and app updates must target API level 36 (Android 16) or higher as of the 2026-08-31 deadline, with a one-time extension to 2026-11-01 available by request. Re-verify at [developer.android.com/google/play/requirements/target-sdk](https://developer.android.com/google/play/requirements/target-sdk) rather than hardcoding a number.
19- **16KB page-size support** is mandatory for app updates shipping native (`.so`) libraries as of 2026-05-01. An app whose ELF `LOAD` segments are still 4KB-aligned runs in a 16KB backcompat mode; test on a 16KB-page-size emulator image, not just the default. Verify current enforcement at [developer.android.com/guide/practices/page-sizes](https://developer.android.com/guide/practices/page-sizes).
20- **AGP 9.x** is the current stable Android Gradle Plugin series (crossed the 9.0 major boundary in early 2026); it raises the minimum Gradle version and enables built-in Kotlin support by default. Verify current minimums at [developer.android.com/build/releases/gradle-plugin-roadmap](https://developer.android.com/build/releases/gradle-plugin-roadmap) before assuming an AGP-8-era Gradle wrapper still works.
21- **Android Studio** current stable is the Quail series (2026.1.x); the prior Otter series (2025.2.x) is now legacy. Verify the live release name/version at [developer.android.com/studio/releases](https://developer.android.com/studio/releases) — Google renames each yearly series and this drifts within months.
22- **Perfetto**, not Systrace, is the current system- and app-tracing stack; Systrace is retired. See [references/performance-triage.md](references/performance-triage.md).
23
24## Quick Reference
25
26| Symptom | First Move | Notes |
27|--------|------------|-------|
28| Screenshot does not match source | Uninstall + clean build + install + launch | Assume stale APK first |
29| `INSTALL_FAILED_UPDATE_INCOMPATIBLE` | `adb uninstall <pkg>` then reinstall | Signing key mismatch between builds |
30| App crashes immediately on launch | Check logcat for first `FATAL EXCEPTION` | Missing Activity in manifest or ProGuard stripping |
31| `ClassNotFoundException` / `NoSuchMethodError` at runtime | Check ProGuard/R8 keep rules | Minification removed referenced class or method |
32| Emulator stuck on boot animation | Cold boot AVD or wipe data | Snapshot corruption is common after SDK updates |
33| `BUILD FAILED` with dependency resolution | Check `libs.versions.toml` and repositories block | Version catalog mismatch or missing repo |
34| Gradle sync fails in Android Studio | `./gradlew --stop && ./gradlew --refresh-dependencies` | Daemon or cache corruption |
35| `adb: device not found` | `adb kill-server && adb start-server && adb devices` | USB debugging off or emulator not connected |
36| App shows old layout after Compose changes | Clean build; check recomposition stability | Incremental build may not invalidate Compose output |
37| Resources not found at runtime | Inspect APK with `aapt2 dump resources` | Missing from merged manifest or wrong resource qualifier |
38| `minSdk` version error on install | Check device API level vs `minSdk` in build.gradle.kts | APK requires higher API than device provides |
39| KSP/KAPT annotation processing errors | Check processor version matches Kotlin version | KSP is tightly coupled to the Kotlin compiler version |
40| Logcat shows nothing from the app | Filter by PID: `adb logcat --pid=$(adb shell pidof <pkg>)` | Default logcat is too noisy to be useful |
41| App works on emulator but crashes on device | Check for x86-only native libs (.so) | Emulator runs x86; most devices run ARM |
42| Compose UI renders but interactions do nothing | Check clickable modifier order and state hoisting | Modifier order determines hit-testing; state must be hoisted |
43| `CalledFromWrongThreadException` after StateFlow update | Move `_uiState.value = ...` back onto the main dispatcher | Keep `withContext(Dispatchers.IO) { ... }` pure: return a value, mutate state outside the block |
44| `ConcurrentModificationException` inside `SnapshotStateObserver` | Upgrade to Compose UI 1.10.1+ OR eliminate off-main state mutation | Compose 1.10.0-rc01 fixed a reentrant-modification race; 1.10.1 fixed pausable composition + `LookaheadScope` |
45| Nested `Popup` pinned to screen top instead of anchor | Upgrade to Compose UI 1.10.1+ | `PopupPositionProvider` absolute-coordinate bug, fixed in 1.10.0 / 1.10.1 |
46| `Argument type mismatch: Function0<Unit> vs @Composable ComposableFunction0<Unit>` | Verify `kotlin("plugin.compose")` is applied and version-locked to the Kotlin compiler | Kotlin 2.x ships the Compose compiler bundled; missing/stale plugin declaration skips the composable transform |
47| Release crash `SerializationException: Serializer for class 'X' is not found` or `ExceptionInInitializerError` | Add explicit keep rules for `@Serializable` classes; run a release-variant smoke test | kotlinx-serialization 1.9.0+ + R8 full mode strips reflectively-referenced serializers |
48| `LazyColumn` recomposes whole list on every unrelated update | Hoist derived lists to ViewModel; `remember(id) { }` around item callbacks | Strong Skipping Mode (Kotlin 2.x) compares unstable params by **reference**, not structural equality |
49| System shows "App isn't responding" or Play Console reports an ANR cluster | Read the ANR trace first, do not open a profiler yet | Blocked-thread stack is already in the trace; classify by which watchdog fired (input/broadcast/service) |
50| Scrolling stutters or animation skips frames, app otherwise responsive | `adb shell dumpsys gfxinfo <pkg> framestats`, then Perfetto if the cause isn't obvious | This is jank, not an ANR — different tool, different budget math (16.67ms @60Hz, 8.33ms @120Hz) |
51| App feels slower over a session or gets OOM-killed | `adb shell dumpsys meminfo <pkg>` trend, then LeakCanary (debug) or a heap dump (release) | Confirm monotonic growth before assuming a leak; one-time cache warm-up is not a leak |
52| Cold start feels slow but no measurement exists yet | Macrobenchmark `StartupTimingMetric` with explicit `StartupMode.COLD` | Do not guess a fix before there's a baseline number; warm/hot starts have different budgets than cold |
53| Dropped frame blamed on "GC" without checking the trace | Open the Perfetto thread-state track for that frame | Binder/IPC contention produces the same visible symptom as a GC pause and is commonly misdiagnosed as GC |
54
55## When to Use This Skill
56
57Use this skill to:
58
59- Prove a fresh uninstall/install/launch loop for a native Android app
60- Diagnose stale installs or stale screenshots in emulator-driven workflows
61- Inspect built APK/AAB contents when installation or launch fails
62- Debug emulator boot, ADB connectivity, and device state drift
63- Investigate Gradle build failures, dependency resolution, and cache corruption
64- Triage R8/ProGuard stripping errors that surface only at runtime
65- Debug Compose recomposition, stability, and incremental compilation artifacts
66- Classify and triage ANRs, jank, memory leaks, and slow startup using Perfetto, Macrobenchmark, LeakCanary, and StrictMode
67- Establish runtime truth before routing to feature implementation, design, or test skills
68
69## Core Workflow
70
711. Discover the project entrypoint: `build.gradle.kts` (root and app module), `applicationId`, build variant (debug/release), target device or emulator.
722. Check environment:
73 `ANDROID_HOME` set, JDK version matches AGP requirement, Gradle wrapper present, `adb devices` shows the target, emulator booted or device connected.
743. Build the app: `./gradlew assembleDebug`.
75 Confirm output ends with `BUILD SUCCESSFUL`.
764. Inspect the APK: `aapt2 dump badging app/build/outputs/apk/debug/app-debug.apk`.
77 Verify `applicationId`, `versionCode`, `minSdk`, declared activities, and expected resources.
785. Remove stale installs:
79 `adb uninstall <applicationId>`.
806. Install the fresh build:
81 `adb install -r app/build/outputs/apk/debug/app-debug.apk`.
827. Launch and capture proof:
83 `adb shell am start -n <applicationId>/<fully.qualified.Activity>`, then capture logcat output and a screenshot (`adb exec-out screencap -p > proof.png`).
848. Only after the app is freshly running, debug feature behavior, design, auth, or API issues.
859. Route onward:
86 - native feature or architecture work -> [software-android-native](../software-android-native/SKILL.md)
87 - test execution and device matrix -> [qa-testing-android](../qa-testing-android/SKILL.md)
88 - general debugging methodology -> [qa-debugging](../qa-debugging/SKILL.md)
89
90## ASCII Flow
91
92```text
93Android runtime failure
94 -> Capture exact device, API, build, repro, and logcat window
95 -> Prove fresh install and launch state
96 -> Classify: crash, ANR, lifecycle, Compose, network, storage, or release
97 -> Inspect stack, logs, profiler, and UI hierarchy evidence
98 -> Patch the smallest failing path
99 -> Rerun same repro and keep before/after proof
100```
101
102## Runtime Proof Loop
103
104- Prefer one bounded loop:
105 discover -> check env -> clean build -> inspect APK -> uninstall -> install -> launch -> capture evidence -> then debug.
106- If any step fails, stop there and fix that layer before moving deeper.
107- Do not trust screenshots from an emulator session that has not been tied to the current build.
108- Do not trust "BUILD SUCCESSFUL" on its own; install and launch proof still matter.
109- Verify launch at three levels: the process started (`adb shell pidof <pkg>`), the correct Activity is in the foreground (`adb shell dumpsys activity top`), and the expected content rendered (screenshot or UI automator dump).
110- If a screenshot contradicts the expected state, check the installed APK's `versionCode` against the build output before trusting the capture.
111
112See [references/runtime-proof-loop.md](references/runtime-proof-loop.md).
113
114## Stale-Build Heuristics
115
116- If the UI on screen does not match current source, suspect stale APK before any other explanation.
117- If the app keeps showing an old layout after rebuild, uninstall the installed app and reinstall the fresh APK.
118- If Gradle says build succeeded but the app looks old, check the APK timestamp and path — the wrong variant or module may have been built.
119- If the emulator was already running and UI state is surprising, re-prove install and launch before reasoning about app state.
120- If incremental compilation is suspected (source moved between modules, annotation processor stale, Compose compiler version mismatch), do a full clean build before anything else.
121- If the Gradle daemon has been running for hours with changing `jvmargs` or plugin versions, stop it with `./gradlew --stop` and rebuild.
122
123See [references/stale-build-triage.md](references/stale-build-triage.md).
124
125## APK/AAB Health Checklist
126
127When installation or launch fails, inspect the built artifact directly:
128
129- [ ] `aapt2 dump badging <apk>` — verify `applicationId`, `versionCode`, `minSdk`, `targetSdk`, declared activities and permissions
130- [ ] Launcher Activity declared with correct intent filter in merged manifest (`app/build/intermediates/merged_manifests/`)
131- [ ] Expected resources present: `aapt2 dump resources <apk>`
132- [ ] For apps with native code: `.so` files present for correct ABIs (`lib/arm64-v8a/`, `lib/armeabi-v7a/`, `lib/x86_64/`)
133- [ ] For release builds: `mapping.txt` exists alongside the APK for crash symbolication
134
135## Gradle Troubleshooting Patterns
136
137Gradle is the most common source of build failures. Common patterns: dependency resolution, version catalog drift, KSP/KAPT version mismatches, AGP compatibility, and configuration cache invalidation.
138
139See [references/gradle-build-troubleshooting.md](references/gradle-build-troubleshooting.md).
140
141## Compose Debugging
142
143Jetpack Compose introduces recomposition, stability, and compiler-level concerns that do not exist in View-based UI. When Compose UI behaves unexpectedly — interactions silently fail, UI does not update, or performance is poor — use Compose-specific debugging before blaming app logic.
144
145See [references/compose-debugging.md](references/compose-debugging.md).
146
147## Performance Triage: ANR, Jank, Memory, Startup
148
149Once build/install/launch is proven, "the app is slow" or "the app freezes" is not one problem — it is an ANR, jank, a memory leak, or a slow start, and each has a different first move. Classify before reaching for a profiler:
150
1511. System ANR dialog or a Play Console ANR cluster exists -> read the ANR trace, find the blocked thread and which watchdog (input/broadcast/service) fired.
1522. Stutter or skipped frames while the app stays responsive -> jank. Start with `framestats`, escalate to Perfetto.
1533. The app slows down over a session or gets OOM-killed -> memory. Confirm a growth trend with `dumpsys meminfo` before assuming a leak.
1544. The complaint is specifically about time-to-first-frame after tapping the icon -> startup. Measure with Macrobenchmark before changing code.
1555. None of the above -> it is a correctness bug, not a performance problem; route to normal crash/log triage.
156
157**When not to microbenchmark**: a microbenchmark measures one function's CPU cost in isolation. It cannot see cold-start AOT/JIT/class-loading cost (use Macrobenchmark instead), cannot see multi-frame jank pipeline stages (use Perfetto/`framestats` first), and should never run before a trace has confirmed the function is actually on the critical path.
158
159**Common misdiagnosis to catch**: a dropped frame during an IPC-heavy screen looks identical to a GC pause from the outside. Check the Perfetto thread-state track — GC pauses show explicit GC markers; binder contention shows the calling thread blocked on a transaction to another process. Do not write "it's GC" into a report without checking the trace.
160
161See [references/performance-triage.md](references/performance-triage.md) for the full decision tree, ANR thresholds, frame budget math, Perfetto/Macrobenchmark/StrictMode/LeakCanary usage, ART GC behavior, current Play Console vitals thresholds, and more misdiagnosis patterns.
162
163## ProGuard/R8 Triage
164
165R8 (the default code shrinker) can remove classes, methods, and fields that are referenced only via reflection, serialization, or framework conventions. When release builds crash with `ClassNotFoundException`, `NoSuchMethodError`, or silent data corruption, suspect R8 stripping before app logic.
166
167See [references/proguard-r8-triage.md](references/proguard-r8-triage.md).
168
169## Route Elsewhere
170
171- Use [software-android-native](../software-android-native/SKILL.md) once runtime truth is established and the task becomes feature implementation, Kotlin architecture, or Compose layout.
172- Use [qa-testing-android](../qa-testing-android/SKILL.md) once the app is buildable and installable and the task becomes test execution, device matrix, or flake control.
173- Use [qa-debugging](../qa-debugging/SKILL.md) for general debugging methodology not specific to Android build/install/launch.
174- Use [software-mobile](../software-mobile/SKILL.md) for platform choice, iOS, or cross-platform tradeoffs.
175
176## Navigation
177
178### References
179
180| Resource | Purpose |
181|----------|---------|
182| [references/runtime-proof-loop.md](references/runtime-proof-loop.md) | Canonical build/install/launch verification loop |
183| [references/stale-build-triage.md](references/stale-build-triage.md) | Heuristics for stale APKs, Gradle cache, and emulator drift |
184| [references/gradle-build-troubleshooting.md](references/gradle-build-troubleshooting.md) | Dependency resolution, version catalogs, AGP, and daemon issues |
185| [references/compose-debugging.md](references/compose-debugging.md) | Recomposition tracking, stability, compiler reports, and common pitfalls |
186| [references/proguard-r8-triage.md](references/proguard-r8-triage.md) | R8 stripping, keep rules, mapping files, and library-specific rules |
187| [references/performance-triage.md](references/performance-triage.md) | ANR/jank/memory/startup decision tree, Perfetto, Macrobenchmark, StrictMode, LeakCanary, ART GC, Play Console vitals, misdiagnoses |
188| [data/sources.json](data/sources.json) | Primary Android platform and tooling sources |
189
190### Templates
191
192| Template | Purpose |
193|----------|---------|
194| [assets/template-android-runtime-debug-request.md](assets/template-android-runtime-debug-request.md) | Short request format for proof-first runtime debugging |
195
196### Related Skills
197
198| Skill | Purpose |
199|-------|---------|
200| [software-android-native](../software-android-native/SKILL.md) | Native Android implementation and architecture after runtime truth exists |
201| [qa-testing-android](../qa-testing-android/SKILL.md) | Espresso, UI Automator, Compose testing, and device matrix after installability is proven |
202| [qa-debugging](../qa-debugging/SKILL.md) | General debugging methodology not specific to Android runtime |
203| [software-mobile](../software-mobile/SKILL.md) | Mobile platform choice and cross-platform tradeoffs |
204
205## Fact-Checking
206
207- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
208- Prefer developer.android.com for ADB, aapt2, AGP, and build system behavior.
209- Prefer upstream Gradle documentation for wrapper, daemon, caching, and dependency resolution.
210- Prefer developer.android.com/topic/performance for ANR thresholds, vitals thresholds, tracing (Perfetto), and Macrobenchmark/Baseline Profile guidance — these are Google-controlled policy numbers and tool defaults that move.
211- Prefer perfetto.dev and square.github.io/leakcanary for Perfetto and LeakCanary specifics respectively.
212- Treat repo-specific build variants, applicationId, module structure, and Gradle properties as local facts that must be discovered, not assumed.
213
214## Learnings Loop
215
216Before applying this skill on a non-trivial task, read `learnings.consolidated.md` in this directory (and `learnings.md` if present).
217
218After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to `learnings.md` via `agents-skills-feedback-loop/scripts/append_learning.py`. Do not modify `SKILL.md` itself.
219