From android
Surfaces StrictMode policy violations in Android apps like main-thread I/O, leaked closeables/activities causing jank, ANRs, memory creep. Temporarily install in Application.onCreate with penaltyLog, monitor logcat for violations.
npx claudepluginhub himattm/skills --plugin androidThis skill uses the workspace's default tool permissions.
StrictMode is the only built-in tool that fires for the **silent killers** — operations that don't throw but ruin perceived performance:
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Executes ctx7 CLI to fetch up-to-date library documentation, manage AI coding skills (install/search/generate/remove/suggest), and configure Context7 MCP. Useful for current API refs, skill handling, or agent setup.
Share bugs, ideas, or general feedback.
StrictMode is the only built-in tool that fires for the silent killers — operations that don't throw but ruin perceived performance:
A Log.d probe won't surface these. An espresso test won't fail. The app runs — just badly. StrictMode logs each violation with a stack trace pointing at the exact call.
Application.onCreate or first activityandroid-probe-logging or read adb logcat *:Eandroid-reproduce-as-testandroid-probe-loggingBefore installing the probe, confirm:
# 1. There's an Application subclass to install StrictMode in
grep -rE 'class\s+\w+\s*:\s*Application|extends\s+Application' \
app/src/main/java app/src/main/kotlin 2>/dev/null
# Expect at least one match. If empty, the project uses the default
# android.app.Application — you'll need to create a subclass and wire
# it via android:name in AndroidManifest.xml before installing the probe.
# 2. The manifest declares it
grep 'android:name' app/src/main/AndroidManifest.xml
# 3. minSdk — affects which detect* methods are available
grep -E 'minSdk' app/build.gradle* gradle/libs.versions.toml 2>/dev/null
API-level gates. Most .detect* methods are available from API 21+; the more recent ones gate at higher levels:
| Method | Min API |
|---|---|
detectUnsafeIntentLaunch | 31 |
detectIncorrectContextUse | 31 |
detectImplicitDirectBoot | 29 |
detectCredentialProtectedWhileLocked | 29 |
detectNonSdkApiUsage | 28 |
If your project's minSdk is below the level where a method is available, the call will compile but NoSuchMethodError at runtime on older devices. Wrap newer methods with Build.VERSION.SDK_INT >= ... checks, or just stick to the universal set in the example above.
Already-configured StrictMode. If the project already calls StrictMode.setThreadPolicy / setVmPolicy somewhere (often in a debug Application), don't double-install — your probe will overwrite the team's setup. Either: (a) sentinel-comment the team's lines, replace with the probe's, restore after; or (b) extend the existing policy instead of replacing it. The cleanup gate (git diff against the file) is the safety net either way.
Java codebase. The .detectDiskReads()...build() chain is identical in Java; only differences are super.onCreate() syntax and the // AGENT_STRICTMODE_<id>: comment placement. The cleanup grep for the sentinel works the same.
.penaltyLog() onlyAt the top of Application.onCreate, before your own initialization:
override fun onCreate() {
super.onCreate()
// AGENT_STRICTMODE_<id>: temporary probe — remove before commit
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.detectCustomSlowCalls()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.detectActivityLeaks()
.detectFileUriExposure()
.penaltyLog()
.build()
)
// ... existing init ...
}
Critical rules:
.penaltyLog(). Never .penaltyDeath() for a probe — death penalty crashes the app on first violation, which masks every later one.AGENT_STRICTMODE_<id>) so the cleanup grep finds it.Application.onCreate so it covers your own init code. Note: ContentProvider.onCreate runs before Application.onCreate and won't be caught here — if you suspect violations during ContentProvider init (Room, WorkManager initializers, etc.), install in attachBaseContext instead, or add a Configuration.Provider with StrictMode set up before initialization.Cold-start the app for cold-start violations. For specific flows, navigate to the screen / trigger the action that you suspect.
adb logcat -c
android run # cold start
# ... drive the suspect flow ...
adb logcat -d -s StrictMode > /tmp/strictmode-<id>.log
The -s StrictMode filter captures every policy violation with stack.
Each violation looks like:
StrictMode policy violation; ~duration=120 ms: android.os.strictmode.DiskReadViolation
at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk
at java.io.UnixFileSystem.checkAccess
at java.io.File.exists
at com.example.app.ConfigLoader.loadFromCache(ConfigLoader.kt:42)
at com.example.app.MyApplication.onCreate(MyApplication.kt:18)
Look for:
DiskReadViolation, NetworkViolation, LeakedClosableViolation, InstanceCountViolation for activity leaks).~duration= — anything over a few ms on the main thread is jank-relevant.For multi-violation logs (>30 lines), delegate to a Sonnet sub-agent:
Read
/tmp/strictmode-<id>.log. Group violations by type and first project-package frame. Return the top 3 unique violations withfile:lineand the violation class. Under 80 words.model: "sonnet".
| Violation | Typical fix |
|---|---|
DiskReadViolation / DiskWriteViolation on main | Move to Dispatchers.IO coroutine, WorkManager, or LifecycleScope |
NetworkViolation on main | Move to a coroutine on Dispatchers.IO, or use OkHttp/Ktor async |
LeakedClosableViolation | Wrap in use { } or close in a finally |
LeakedRegistrationObjects | Unregister receivers / listeners in onDestroy / onStop |
InstanceCountViolation (activity leak) | Find the static / singleton holding the activity ref; usually a callback registered without unregister |
Re-run after each fix until logcat is clean.
rg 'AGENT_STRICTMODE_'
git diff app/src/main/java/.../MyApplication.kt
Both must show that the temporary setThreadPolicy / setVmPolicy block is completely removed. If the project already had a debug-only StrictMode setup, restore it to its pre-probe state — the diff against git should be exactly the lines you didn't touch.
If the project should have StrictMode behind BuildConfig.DEBUG permanently, that's a separate change — propose it explicitly, don't smuggle it in under the probe.
When the violation log is overwhelming (cold start can produce 100+), narrow:
Scope to one suspect operation with permitAll() baseline + targeted detect:
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.permitAll()
.detectDiskReads()
.penaltyLog()
.build()
)
Wrap a single suspect block with allowThreadDiskReads / allowThreadDiskWrites to confirm a specific call is the offender:
val original = StrictMode.allowThreadDiskReads()
try {
suspectCall()
} finally {
StrictMode.setThreadPolicy(original)
}
If wrapping silences the violation, you found it.
| Mistake | Fix |
|---|---|
| Skipping the cleanup gate | rg 'AGENT_STRICTMODE_' must return zero before commit; the temporary block must be fully removed |
Using .penaltyDeath() | Crashes on first violation, masks later ones — always .penaltyLog() for a probe |
Installing after super.onCreate() | Misses framework-level violations during app init; install first |
| Reading 100+ violation logcat inline | Delegate to a Sonnet sub-agent with grouping criteria |
| Fixing the first violation, declaring done | Re-run after each fix — fixes often unmask later violations |
| Smuggling permanent StrictMode in under "probe" cleanup | Probe = remove. Permanent debug StrictMode is a separate, explicit change |
Wrapping production code in allowThreadDiskReads to "silence" | That's hiding the bug, not fixing it; move the work off the main thread |
Forgetting to clear adb logcat -c before the run | Mixes prior-run violations into the diagnosis |