Knowledge vault (Orion/PARA) migrated from the pre-Orion 484vault on 2026-10-01. Deliberately orphaned: prior history contained a plaintext password and stays local-only on branch archive/pre-boilerplate-history. Secrets and live Hermes state are gitignored.
54 lines
No EOL
3.2 KiB
Markdown
54 lines
No EOL
3.2 KiB
Markdown
```
|
|
Here's the full playbook — symptoms, how we cornered each agent, and the exact
|
|
commands. Paste the "Recipe" part into any agent for another app.
|
|
What was wrong in SatsAmp (3 stacked causes)
|
|
1. Main-thread disk I/O inside composition. StationRepository (~290ms) and
|
|
StreamRepository (~130ms) read SharedPreferences + parse JSON in their
|
|
constructors. Hilt builds singletons lazily, so that cost landed inside the
|
|
first page-visit's tap frame.
|
|
2. Tab taps animated through every intermediate page, composing/disposing
|
|
heavy screens mid-flight, plus the selected-indicator tracked settledPage
|
|
(lags the animation).
|
|
3. Debug-build tax. First-touch class verification + JIT + shader compile
|
|
produced 400-1100ms frames that only exist in debuggable builds. Release (R8 +
|
|
install-time AOT) collapsed p99 from 900ms → 18-65ms.
|
|
Also fixed along the way: an equalizer composable running
|
|
rememberInfiniteTransition in every list row even when paused, one unkeyed
|
|
LazyColumn, missing row placement animations.
|
|
Recipe: diagnose jank in any Android app
|
|
4. Setup. adb devices, note the device. Get tap coordinates once: adb shell
|
|
uiautomator dump /sdcard/ui.xml, pull the bounds of the buttons you'll drive.
|
|
5. Measure per interaction, not lifetime. Stats accumulate — always reset
|
|
right before the action, sleep, then dump:
|
|
adb shell dumpsys gfxinfo <pkg> reset > /dev/null
|
|
adb shell input tap <x> <y>; sleep 2
|
|
adb shell dumpsys gfxinfo <pkg> | grep -E "Total frames|Janky
|
|
frames:|95th|99th|Slow UI|Slow issue|input latency"
|
|
Key fields: Slow UI thread vs GPU percentiles (in the full dump: 90th gpu
|
|
percentile). GPU > 2ms + slow UI thread = CPU-side problem, stop looking at
|
|
overdraw/shaders.
|
|
6. Idle test — rule out runaway animation. Reset, sleep 6, dump. Total frames
|
|
rendered: 0 = rendering is on-demand, nothing spins. Dozens+ frames idle =
|
|
find the rememberInfiniteTransition / ticker that's always mounted.
|
|
7. Isolate tap path vs content path. Tap the already-selected tab. Clean (0
|
|
janky) = the stall is incoming-page work, not ripple/nav handling.
|
|
8. StrictMode — catches main-thread I/O with stack traces. Add in
|
|
Application.onCreate, gated on debuggable, penaltyLog() only (never crash):
|
|
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
|
|
.detectDiskReads().detectDiskWrites().detectNetwork()
|
|
.detectCustomSlowCalls().penaltyLog().build())
|
|
Drive the app, then adb shell logcat -d | grep StrictMode. Look for app frames
|
|
(at com.yourapp...) and durations — ours showed repository constructors at 130-
|
|
290ms firing from hvilViewModel() during composition.
|
|
9. Composition timing probe. Wrap page content: remember { nanoTime() } +
|
|
SideEffect { log(elapsed) }. Ours showed 6-24ms — proving composition was
|
|
innocent and the stall was elsewhere in the frame.
|
|
10. Per-frame table for position. adb shell dumpsys gfxinfo <pkg> framestats,
|
|
find the Flags= rows missed vsync; compare
|
|
PerformTraversalsStart vs IntendedVsync — a big gap before traversals start
|
|
- dumpsys gfxinfo without reset shows lifetime stats — always reset per
|
|
experiment.
|
|
- am profile start/stop produced 0-byte files whenever a tap occurred mid-
|
|
trace on this device; in-app probes (StrictMode, FrameMetrics, SideEffect
|
|
timers) were more reliable than device-side
|
|
``` |