StrictMode is a developer tool built into the Android SDK that detects and reports accidental I/O operations and network calls on the main thread of the application in real time. It doesn't fix errors but acts as a detector — throwing exceptions or writing to LogCat when configured policies are violated. According to Google, 2024, proper StrictMode configuration can detect up to 80% of performance issues before the app is released.
Key Takeaways
StrictMode is an API included in the Android SDK since API Level 9 (Android 2.3 Gingerbread). Its task is to detect at runtime the accidental execution of heavy operations on the main (UI) thread that could block UI rendering. The main thread handles user input processing, layout calculation, and rendering — any block longer than 16 ms results in a frame drop.
StrictMode follows the “fail fast” principle — detect the problem as early as possible, ideally at the moment of its first appearance. Instead of waiting for user complaints about slowdowns, the developer receives a signal (logs, dialog, or crash) right at the development stage. The tool doesn't require additional libraries or Gradle configuration — just a few lines of code in Application.onCreate, and it works automatically on all devices.
StrictMode is designed for all Android developers, regardless of experience. For beginners, it helps form good habits (don't make network requests on the UI thread); for experienced developers, it automates quality control in the CI/CD pipeline. Large projects (Google, Uber, Spotify) include StrictMode in debug builds with penaltyDeath, and disable it in release builds via the BuildConfig.DEBUG check.
StrictMode intercepts system calls that could block the thread and compares them against the set of active policies. If a call matches a policy and is executed on the main thread, StrictMode applies the specified penalty. The interception mechanism is implemented via an in-process hook — it doesn't use reflection and operates with minimal overhead.
When the StrictMode policy is activated, it injects its handler into the entry points of system calls (FileInputStream, FileOutputStream, Socket, URLConnection). When the application calls, for example, URLConnection.openStream on the main thread, StrictMode checks the current thread — if it's the main thread, the tool triggers. In Android 6.0+, the mechanism is enhanced: network calls on the main thread generate NetworkOnMainThreadException even without StrictMode, but StrictMode also allows controlling disk I/O.
Each policy can have its own penalty type or combination: penaltyLog — writes to LogCat with a stack trace, penaltyDialog — shows a dialog to the user (debug only), penaltyDeath — throws an exception and crashes the app, penaltyDropBox — saves data to DropBoxManager for later analysis. For CI/CD pipelines, penaltyDeath is recommended — it ensures that no merge with violations goes unnoticed.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
StrictMode divides policies into two levels: ThreadPolicy (thread-level — what cannot be done on the main thread) and VmPolicy (virtual machine — memory and resource leaks). Both levels are configured independently and work in parallel.
At the thread level, StrictMode controls four types of violations: disk reads (detectDiskReads), disk writes (detectDiskWrites), network operations (detectNetwork), and custom slow calls (detectCustomSlowCalls). disk_read triggers on any reading of SharedPreferences, SQLite, or files on the main thread. network triggers on HTTP requests, WebSocket, and Socket connections. In Android 11+, detectUnbufferedIO was added for detecting unbuffered I/O.
VmPolicy controls leaks at the ART virtual machine level: detectActivityLeaks (activities that were not destroyed), detectLeakedClosableObjects (unclosed Cursor, Stream, Socket), detectLeakedRegistrationObjects (unregistered BroadcastReceiver, ServiceConnection). If VmPolicy detects that an Activity was created but not destroyed after onDestroy was called, it outputs a full stack trace — saving hours of memory leak debugging.
| Policy | Level | What It Detects |
|---|---|---|
| detectDiskReads | Thread | Reading SharedPrefs, SQLite, files on the UI thread |
| detectDiskWrites | Thread | Writing to SharedPrefs, SQLite, files on the UI thread |
| detectNetwork | Thread | Any network operations on the UI thread |
| detectActivityLeaks | VM | Activities surviving onDestroy |
| detectLeakedClosableObjects | VM | Unclosed Cursor, Stream, Socket |
Through detectCustomSlowCalls, you can mark your own methods as “suspicious” and receive a warning when a specified threshold is exceeded. For example, if your loadUserProfile() method usually takes 5 ms but sometimes takes 200 ms — wrap it in StrictMode.noteSlowCall(“loadUserProfile”). If the duration exceeds the threshold (default 2000 ms), StrictMode will generate a penalty. The threshold is configured via setSlowCallDurationThreshold.
Basic StrictMode configuration takes 10 lines of code and is done in the onCreate method of a custom Application class. The main rule: StrictMode is enabled only in debug builds — in release builds it slows down the app and can create false positives.
Create a class extending Application, register it in AndroidManifest.xml via the android:name attribute, and add StrictMode configuration. ThreadPolicy.Builder includes all detectors and all penalty types (except dialog — it works only when a debugger is attached). VmPolicy.Builder adds detectors for Activity leaks and Closable objects. For large projects (100+ screens), it's recommended to configure VmPolicy with penaltyDeath on the Activity Leaks detector — it's strict but effective.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
For automatic control in CI/CD, use penaltyDeath — if any test violates the policy, the app will crash with an exception. Combine with Android Test Orchestrator so each test runs in a clean process. For UI tests (Espresso, Compose Test), write a custom TestRule that intercepts StrictMode violations and turns them into assertion failures. Example: in @Before, enable StrictMode, and in @After, check that there were no violations.
By default, the threshold for customSlowCall is 2000 ms, for disk_read and disk_write — no threshold (any operation triggers). Through setSlowCallDurationThreshold and setSlowIoDurationThreshold, you can set your own values in milliseconds. If your app legitimately reads SharedPreferences on the main thread (small config), increase the threshold to 10–20 ms — this will filter out fast reads while keeping slow ones.
StrictMode is a powerful but finicky tool. Incorrect configuration leads to millions of false positives, causing developers to stop paying attention to them. Below are proven practices gathered from the experience of large Android teams.
This is a hard rule: StrictMode should NEVER be active in release builds. Use the BuildConfig.DEBUG flag or a custom buildConfigField. In release builds, many third-party libraries legitimately perform operations on the main thread (SDK initialization, cache writing), and StrictMode will create false positives. Moreover, penaltyDialog in a release build will show a dialog to the end user — which is unacceptable.
For small projects (1–10 screens), configure penaltyLog — logs are enough for manual analysis. For medium projects (10–50 screens), add penaltyDeath on network and customSlowCalls. For large projects (50+ screens), enable the full set of policies with penaltyDeath in CI/CD, and penaltyLog for local development. This gradation prevents overloading developers with false crashes while strictly controlling quality in the pipeline.
Some libraries (Firebase, Crashlytics, Adjust) legitimately perform background operations that StrictMode might incorrectly detect. Solutions: add the library to a whitelist via penaltyListener, update the library to a version with explicit background thread switching, or use StrictMode.vmPolicy. In Android 11+, StrictMode.OnVmViolationListener was introduced for programmatic filtering of violations by stack trace.
// Filtering false positives via penaltyListener
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyListener { violation ->
val stack = violation.stackTraceToString()
if ("com.google.firebase" !in stack) {
logViolation(violation)
}
}
.build()
)
StrictMode is not the only quality control tool in the Android ecosystem. To understand its place, let's compare it with Android Lint, Android Profiler, and Perfetto across key criteria: check time, analysis depth, and automation.
| Criterion | StrictMode | Android Lint | Profiler / Perfetto |
|---|---|---|---|
| Check Time | Runtime (while the app is running) | Compile time (before launch) | Runtime (post-mortem) |
| What It Checks | Disk, network, leaks | XML, code, resources | CPU, memory, network, power |
| Automation | CI/CD via penaltyDeath | Gradle task + lint-baseline | Requires manual analysis |
| Depth | UI thread only and leaks | Static code analysis | Full performance picture |
| False Positives | Medium (depends on libraries) | Low (configured rules) | None (actual measurements) |
The best strategy is to combine all three approaches: Android Lint catches obvious errors at compile time (e.g., a forgotten IdleHandler), StrictMode detects problems at runtime, and Android Profiler / Perfetto is used for deep analysis when the first two tools don't provide answers. In real projects (Google Maps, Instagram), StrictMode is introduced in the second week of development — right after setting up the basic architecture.
Let's look at two real scenarios where StrictMode helps detect and fix performance issues: reading SharedPreferences on the main thread and Activity leak through an unregistered callback.
At app startup, StrictMode with the detectDiskReads policy will detect reading SharedPreferences on the main thread. Solution: load configuration asynchronously via CoroutineScope or cache it in memory at startup. SharedPreferences synchronously reads an XML file from disk — even with a small file (1–2 KB), the operation takes 1–5 ms, and up to 20 ms on cheap devices, which can lead to frame drops.
// ❌ Problem code — reading SharedPrefs on UI thread
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// StrictMode detectDiskReads → VIOLATION!
prefs = getSharedPreferences("config", MODE_PRIVATE)
}
}
// ✅ Fixed code — reading via Coroutine
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfigAsync()
}
}
StrictMode with VmPolicy.detectActivityLeaks will detect an Activity that has exited the stack (finish was called) but the Activity object remains in memory due to a static reference or an unregistered callback. Typical scenario: registering EventBus or LocationListener in onResume without calling unregister in onPause. VmPolicy will output a stack trace indicating the line where the reference was created.
// ❌ Leak — callback not cancelled
private var locationCallback: LocationCallback? = null
override fun onResume() {
super.onResume()
locationCallback = LocationCallback(this::onLocationUpdated)
locationManager.register(locationCallback) // StrictMode → LEAK!
}
override fun onPause() {
super.onPause()
// Forgot: locationManager.unregister(locationCallback)
}
Frequently Asked Questions
StrictMode does add a small overhead — every system call is checked against the policies. The performance impact is 1–3% in debug builds and is absent in release (where StrictMode is disabled). When enabling detectAll on older devices (Android 6–8), overhead can reach 5%, so it's recommended to configure only the necessary policies.
Yes, StrictMode is fully compatible with Jetpack Compose. The disk and network policies work at the framework level, independent of the UI framework. Moreover, in Compose the criticality of UI blocks is higher — Compose redraws frames at 120 FPS on high-refresh-rate devices, so an extra 5 ms on file reading becomes more noticeable.
Starting from Android 8.1 (API 27), SharedPreferences may use in-memory caching — if the file has already been read, re-reading won't trigger StrictMode. Make sure you're calling getSharedPreferences for the first time (cold read) and that the detectDiskReads policy is active. Also check that StrictMode hasn't been overridden in a parent-free fragment.
In JUnit tests, use StrictMode.allowThreadDiskReads() and StrictMode.allowThreadDiskWrites() in @Before, and restore settings in @After via StrictMode.enableDefaults(). For Instrumentation tests, use a custom TestRunner with temporary preservation of the original policy. In Espresso tests, it's convenient to wrap StrictMode-sensitive code in an IdlingResource.
StrictMode only works on the Android platform through the Android SDK. In Kotlin Multiplatform (KMP), commonMain code cannot use StrictMode, but for androidMain you can add it as usual. For the iOS part, use an analog — DispatchQueue.main.async assertion for the main thread.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also