---
name: android-anr-perf
description: Stage 4 of /android-review. Checks an Android diff for ANR and performance risk on low-end devices (main thread, locks, unscoped work, unbounded loops, draw/touch hot paths, Compose recomposition, startup, memory). Runs only when invoked.
disable-model-invocation: true
argument-hint: "[diff range, e.g. main...HEAD or <sha>^!]"
---

# android-anr-perf — is this safe on a slow device?

> From Shubham Singh Bisht's Android helpers — https://shubhamsinghbisht.com/android-roadmap/#helpers

Review only the diff, against a screen that can hold thousands of items on a
low-end device. Report; never edit.

## 1. Range

`$ARGUMENTS` if given, else the nearest base
(`scan_diff.sh --base`: main/master/develop or the `release-*` branch this
branch is fewest commits ahead of) to the working tree,
including untracked `.kt`/`.java` files.

## 2. Run the scanner first

The scanner is `scripts/scan_diff.sh` inside this skill's base directory (the
"Base directory for this skill" shown when it loads). Run it from the repo root:

```bash
<base directory>/scripts/scan_diff.sh $ARGUMENTS
```

Each output line is a **lead**: open the file at that line and decide whether it
breaks a rule below. A lead that is fine (e.g. `.get()` on a `Map`, `runBlocking`
in a `main()` tool) is dropped silently. Then read the whole diff for the rules
the scanner cannot see (ANR-4, PERF-2, Compose hot paths).

Two scanner settings can be tuned per repo with environment variables:

- `SCAN_STARTUP`: regex for startup files (PERF-3). Default matches
  `*Application.kt` and `MainActivity.kt`.
- `SCAN_BIG_LIST`: regex for the name of your app's large collection (ANR-5).
  Default `objects|items`, so `page.items.forEach { … }` is a lead.

`scan_diff.sh --self-test` checks the scanner against `scripts/fixtures/`.

## 3. Rules

| ID | Rule |
|---|---|
| ANR-1 | No disk, network, DB, bitmap decode, large JSON parse or `SharedPreferences.commit()` on the main thread. |
| ANR-2 | No unscoped work: `viewModelScope` / `lifecycleScope` / an injected scope. No new `GlobalScope`, no fire-and-forget `Thread {}`. |
| ANR-3 | Locks stay tiny and never wrap I/O; the main thread never waits on a lock a worker holds. |
| ANR-4 | Heavy per-tap work runs off main with a generation ticket so only the latest request publishes (see the sketch below). |
| ANR-5 | Loops over every item on a screen or page are filtered or bounded, e.g. an autosave that only needs images filters to images before it loops. |
| PERF-1 | Draw/touch hot paths allocate nothing, log nothing, lay out nothing: custom View `onDraw` / `onTouchEvent`, `drawWithContent`, `pointerInput`, `Canvas {}`. |
| PERF-2 | Compose: read state in the narrowest scope, stable params, `remember` derived values, keys on lazy lists, no rebuilding rows per redraw. |
| PERF-3 | Startup: nothing new in `Application.onCreate`, the launcher Activity's `onCreate` or the first screen's creation unless the first frame needs it; defer it. If the app already wraps startup in trace sections (`Trace.beginSection` / `trace("…")`), new startup work gets its own section. |
| PERF-4 | Memory: bitmaps sized to their view and released; no `Context` / `View` / `Activity` held in singletons or companion objects; large copies streamed, not loaded whole. |
| PERF-5 | New main-looper `Handler` / `postDelayed` chains are cancelled with the lifecycle; prefer coroutines. |

ANR-4 sketch:

```kotlin
private var generation = 0L

fun onTap(input: Input) {
    val ticket = ++generation                 // main thread only
    scope.launch {
        val result = withContext(io) { heavyWork(input) }
        if (ticket == generation) publish(result)   // stale results are dropped
    }
}
```

## 4. Output

```
### Stage 4 — android-anr-perf
| file:line | rule | why (one line) | suggested change |
|---|---|---|---|
| … | ANR-5 | forEach over every item on autosave | filter to the item type it needs first |

perf-sensitive: yes — ANR-5 in handleAutoSave, touches a screen-size loop
```

No findings → `stage 4: nothing found`, still followed by the
`perf-sensitive:` line.

`perf-sensitive: yes` when any of ANR-4, ANR-5, PERF-1..5 fired; or the diff
touches custom View draw/touch code, Compose `Canvas` / `pointerInput`,
floating or overlay UI, or the startup files; or the change exists to fix
performance (its commit messages or problem statement cite ms, jank, freeze,
ANR, slow, or a perf: prefix): a perf fix needs numbers. Otherwise `no`.
