StrictMode to wbudowane w Android SDK narzędzie deweloperskie, które w czasie rzeczywistym wykrywa i informuje o przypadkowych operacjach wejścia-wyjścia oraz wywołaniach sieciowych w głównym wątku aplikacji. Nie naprawia błędów, a działa jako detektor — wyrzuca wyjątki lub zapisuje do LogCat przy naruszeniu zadanych polityk. Według Google, 2024, prawidłowa konfiguracja StrictMode pozwala wykryć do 80% problemów wydajnościowych jeszcze przed premierą aplikacji.
Najważniejsze
StrictMode to API wchodzące w skład Android SDK od API Level 9 (Android 2.3 Gingerbread). Jego zadaniem jest wykrywanie w czasie rzeczywistym przypadkowego wykonywania ciężkich operacji w głównym (UI) wątku, które mogą zablokować renderowanie interfejsu. Główny wątek odpowiada za przetwarzanie danych wejściowych użytkownika, obliczanie układu i renderowanie — każde zablokowanie dłuższe niż 16 ms prowadzi do pominięcia klatki.
StrictMode kieruje się zasadą „fail fast” — wykryj problem jak najwcześniej, najlepiej w momencie jego pierwszego pojawienia się. Zamiast czekać na skargi użytkowników dotyczące spowolnień, deweloper otrzymuje sygnał (logi, dialog lub crash) już na etapie rozwoju. Narzędzie nie wymaga dodatkowych bibliotek ani konfiguracji Gradle — wystarczy kilka linijek kodu w Application.onCreate, a działa ono automatycznie na wszystkich urządzeniach.
StrictMode jest przeznaczony dla wszystkich deweloperów Androida, niezależnie od doświadczenia. Początkującym pomaga wyrobić prawidłowe nawyki (nie wykonywać zapytań sieciowych w wątku UI), doświadczonym — automatyzować kontrolę jakości w CI/CD pipeline. Duże projekty (Google, Uber, Spotify) włączają StrictMode w debug builds z penaltyDeath, a w wydaniowych buildach wyłączają przez sprawdzenie BuildConfig.DEBUG.
StrictMode przechwytuje wywołania systemowe, które mogą blokować wątek, i porównuje je z zestawem aktywnych polityk. Jeśli wywołanie odpowiada polityce i jest wykonywane w głównym wątku, StrictMode stosuje zadany penalty (karę). Mechanizm przechwytywania jest zaimplementowany przez wewnątrzprocesowy hook — nie używa refleksji i działa z minimalnym narzutem.
Przy aktywacji polityki StrictMode wstawia swój handler w punkt wejścia wywołań systemowych (FileInputStream, FileOutputStream, Socket, URLConnection). Gdy aplikacja wywołuje, na przykład, URLConnection.openStream w głównym wątku, StrictMode sprawdza bieżący wątek — jeśli jest to main thread, narzędzie się uruchamia. W Android 6.0+ mechanizm został wzmocniony: wywołania sieciowe w main thread generują NetworkOnMainThreadException nawet bez StrictMode, ale StrictMode pozwala kontrolować także dyskowe I/O.
Każda polityka może mieć swój typ penalty lub kombinację: penaltyLog — zapis do LogCat ze stacktrace, penaltyDialog — pokazanie dialogu użytkownikowi (tylko w debug), penaltyDeath — wyrzucenie wyjątku i crash aplikacji, penaltyDropBox — zapisanie danych w DropBoxManager do późniejszej analizy. Dla CI/CD pipeline zaleca się penaltyDeath — gwarantuje to, że żaden merge z naruszeniem nie przejdzie niezauważony.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
StrictMode dzieli polityki na dwa poziomy: ThreadPolicy (wątkowe — czego nie robić w głównym wątku) i VmPolicy (maszyny wirtualnej — wycieki pamięci i zasobów). Oba poziomy konfiguruje się niezależnie i działają równolegle.
Na poziomie wątku StrictMode kontroluje cztery typy naruszeń: odczyt z dysku (detectDiskReads), zapis na dysk (detectDiskWrites), operacje sieciowe (detectNetwork) i niestandardowe wolne wywołania (detectCustomSlowCalls). disk_read uruchamia się przy każdym odczycie SharedPreferences, SQLite, plików w głównym wątku. network — przy żądaniach HTTP, WebSocket, połączeniach Socket. W Android 11+ dodano detectUnbufferedIO do wykrywania niebuforowanego wejścia-wyjścia.
VmPolicy kontroluje wycieki na poziomie maszyny wirtualnej ART: detectActivityLeaks (Activities, które nie zostały zniszczone), detectLeakedClosableObjects (niezamknięte Cursor, Stream, Socket), detectLeakedRegistrationObjects (nieodwołane BroadcastReceiver, ServiceConnection). Jeśli VmPolicy wykryje, że Activity została utworzona, ale nie zniszczona po wywołaniu onDestroy, wypisuje pełny stack trace — to oszczędza godziny debugowania wycieków pamięci.
| Polityka | Poziom | Co wykrywa |
|---|---|---|
| detectDiskReads | Thread | Odczyt SharedPrefs, SQLite, plików w wątku UI |
| detectDiskWrites | Thread | Zapis do SharedPrefs, SQLite, plików w wątku UI |
| detectNetwork | Thread | Wszelkie operacje sieciowe w wątku UI |
| detectActivityLeaks | VM | Activities, które przetrwały onDestroy |
| detectLeakedClosableObjects | VM | Niezamknięte Cursor, Stream, Socket |
Przez detectCustomSlowCalls można oznaczyć własne metody jako „podejrzane” i otrzymywać ostrzeżenie przy przekroczeniu zadanego progu. Na przykład, jeśli twoja metoda loadUserProfile() zwykle wykonuje się 5 ms, ale w niektórych przypadkach zajmuje 200 ms — opakuj ją w StrictMode.noteSlowCall("loadUserProfile"). Jeśli czas trwania przekroczy próg (domyślnie 2000 ms), StrictMode wygeneruje penalty. Próg konfiguruje się przez setSlowCallDurationThreshold.
Podstawowa konfiguracja StrictMode zajmuje 10 linijek kodu i wykonuje się w metodzie onCreate niestandardowej klasy Application. Główna zasada: StrictMode włącza się tylko w debug buildach — w release buildach spowalnia aplikację i może generować fałszywe alarmy.
Utwórz klasę dziedziczącą po Application, zarejestruj ją w AndroidManifest.xml przez atrybut android:name i dodaj konfigurację StrictMode. ThreadPolicy.Builder włącza wszystkie detektory i wszystkie rodzaje penalty (oprócz dialog — działa tylko przy podłączonym debuggerze). VmPolicy.Builder dodaje detektory wycieków Activity i Closable. Dla dużych projektów (100+ ekranów) zaleca się skonfigurowanie VmPolicy z penaltyDeath na detektor Activity Leaks — to surowe, ale skuteczne.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
Do automatycznej kontroli w CI/CD używaj penaltyDeath — jeśli jakiś test naruszy politykę, aplikacja padnie z wyjątkiem. Łącz z Android Test Orchestrator, aby każdy test uruchamiał się w czystym procesie. Dla testów UI (Espresso, Compose Test) napisz niestandardowy TestRule, który przechwytuje naruszenia StrictMode i zamienia je w assertion failure. Przykład: w @Before włącz StrictMode, a w @After sprawdź, czy nie było violations.
Domyślnie próg dla customSlowCall wynosi 2000 ms, dla disk_read i disk_write — bez progu (każda operacja go wyzwala). Przez setSlowCallDurationThreshold i setSlowIoDurationThreshold można ustawić własne wartości w milisekundach. Jeśli twoja aplikacja legalnie odczytuje SharedPreferences w głównym wątku (mała konfiguracja), zwiększ próg do 10–20 ms — to odetnie szybkie odczyty, ale pozostawi wolne.
StrictMode to potężne, ale kapryśne narzędzie. Nieprawidłowa konfiguracja prowadzi do miliona fałszywych alarmów, przez co deweloperzy przestają na nie zwracać uwagę. Poniżej — sprawdzone praktyki zebrane z doświadczenia dużych zespołów Android.
To żelazna zasada: StrictMode NIGDY nie powinien być aktywny w release buildach. Używaj flagi BuildConfig.DEBUG lub niestandardowej buildConfigField. W release buildach wiele bibliotek zewnętrznych legalnie wykonuje operacje w głównym wątku (inicjalizacja SDK, zapis cache), a StrictMode wygeneruje fałszywe alarmy. Co więcej, penaltyDialog w release buildzie pokaże dialog użytkownikowi końcowemu — co jest niedopuszczalne.
Dla małych projektów (1–10 ekranów) skonfiguruj penaltyLog — wystarczy logów do ręcznej analizy. Dla średnich projektów (10–50 ekranów) dodaj penaltyDeath dla network i customSlowCalls. Dla dużych projektów (50+ ekranów) włącz pełny zestaw polityk z penaltyDeath w CI/CD, a do lokalnego rozwoju — penaltyLog. Taka gradacja pozwala nie przeciążać dewelopera fałszywymi crashami, ale surowo kontrolować jakość w pipeline.
Niektóre biblioteki (Firebase, Crashlytics, Adjust) legalnie wykonują operacje w tle, które StrictMode może błędnie wykryć. Rozwiązania: dodaj bibliotekę do whitelist przez penaltyListener, zaktualizuj bibliotekę do wersji z jawnym przełączaniem na background thread lub użyj StrictMode.vmPolicy. W Android 11+ pojawił się StrictMode.OnVmViolationListener do programowego filtrowania naruszeń według stacktrace.
// Filtracja fałszywych pozytywów przez penaltyListener
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyListener { violation ->
val stack = violation.stackTraceToString()
if ("com.google.firebase" !in stack) {
logViolation(violation)
}
}
.build()
)
StrictMode nie jest jedynym narzędziem kontroli jakości w ekosystemie Android. Aby zrozumieć jego miejsce, porównajmy go z Android Lint, Android Profiler i Perfetto według kluczowych kryteriów: czas sprawdzania, głębokość analizy i automatyzacja.
| Kryterium | StrictMode | Android Lint | Profiler / Perfetto |
|---|---|---|---|
| Czas sprawdzania | Runtime (podczas działania aplikacji) | Compile time (przed uruchomieniem) | Runtime (post-mortem) |
| Co sprawdza | Dysk, sieć, wycieki | XML, kod, zasoby | CPU, pamięć, sieć, energia |
| Automatyzacja | CI/CD przez penaltyDeath | Gradle task + lint-baseline | Wymaga ręcznej analizy |
| Głębokość | Tylko UI-wątek i wycieki | Statyczna analiza kodu | Pełny obraz wydajności |
| Fałszywe alarmy | Średnie (zależą od bibliotek) | Niskie (skonfigurowane reguły) | Brak (rzeczywiste pomiary) |
Najlepsza strategia to łączenie wszystkich trzech podejść: Android Lint łapie oczywiste błędy na etapie kompilacji (na przykład zapomniany IdleHandler), StrictMode wykrywa problemy w czasie rzeczywistym, a Android Profiler / Perfetto jest używany do głębokiej analizy, gdy pierwsze dwa narzędzia nie dają odpowiedzi. W rzeczywistych projektach (Google Maps, Instagram) StrictMode wdraża się w drugim tygodniu rozwoju — zaraz po skonfigurowaniu podstawowej architektury.
Rozpatrzmy dwa rzeczywiste scenariusze, w których StrictMode pomaga wykryć i rozwiązać problemy wydajnościowe: odczyt SharedPreferences w głównym wątku i wyciek Activity przez niezarejestrowany callback.
Przy starcie aplikacji StrictMode z polityką detectDiskReads wykryje odczyt SharedPreferences w głównym wątku. Rozwiązanie: ładować konfigurację asynchronicznie przez CoroutineScope lub buforować w pamięci przy starcie. SharedPreferences synchronicznie odczytuje plik XML z dysku — nawet przy małym pliku (1–2 KB) operacja zajmuje 1–5 ms, a na tanich urządzeniach do 20 ms, co może prowadzić do pominięcia klatki.
// ❌ Problemowy kod — odczyt SharedPrefs w wątku UI
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// StrictMode detectDiskReads → VIOLATION!
prefs = getSharedPreferences("config", MODE_PRIVATE)
}
}
// ✅ Poprawiony kod — odczyt przez Coroutine
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfigAsync()
}
}
StrictMode z VmPolicy.detectActivityLeaks wykryje Activity, która wyszła ze stosu (finish wywołany), ale obiekt Activity nadal wisi w pamięci z powodu statycznego odniesienia lub niezarejestrowanego callbacka. Typowy scenariusz: rejestracja EventBus lub LocationListener w onResume bez wywołania unregister w onPause. VmPolicy wypisze stacktrace ze wskazaniem linii, w której utworzono odniesienie.
// ❌ Wyciek — callback nie został anulowany
private var locationCallback: LocationCallback? = null
override fun onResume() {
super.onResume()
locationCallback = LocationCallback(this::onLocationUpdated)
locationManager.register(locationCallback) // StrictMode → LEAK!
}
override fun onPause() {
super.onPause()
// Zapomniano: locationManager.unregister(locationCallback)
}
Często zadawane pytania
StrictMode rzeczywiście dodaje niewielki narzut — każde wywołanie systemowe jest sprawdzane pod kątem zgodności z politykami. Wpływ na wydajność wynosi 1–3% w debug buildzie i nie występuje w release (gdzie StrictMode jest wyłączony). Przy włączeniu detectAll na starych urządzeniach (Android 6–8) narzut może sięgać 5%, dlatego zaleca się konfigurować tylko potrzebne polityki.
Tak, StrictMode jest w pełni kompatybilny z Jetpack Compose. Polityki disk i network działają na poziomie frameworka, niezależnie od frameworka UI. Co więcej, w Compose krytyczność blokad UI jest wyższa — Compose przerysowuje klatki z częstotliwością 120 FPS na urządzeniach z wysoką częstotliwością odświeżania, dlatego dodatkowe 5 ms na odczyt pliku stają się bardziej zauważalne.
Począwszy od Android 8.1 (API 27), SharedPreferences może używać buforowania w pamięci — jeśli plik został już odczytany, ponowny odczyt nie wyzwala StrictMode. Sprawdź, czy wywołujesz getSharedPreferences po raz pierwszy (zimny odczyt) i czy polityka detectDiskReads jest aktywna. Sprawdź także, czy StrictMode nie jest nadpisywany w fragmencie parent-free.
W testach JUnit używaj StrictMode.allowThreadDiskReads() i StrictMode.allowThreadDiskWrites() w @Before, a w @After przywróć ustawienia przez StrictMode.enableDefaults(). Dla testów Instrumentation używaj niestandardowego TestRunnera z tymczasowym zapisaniem oryginalnej polityki. W testach Espresso wygodnie jest opakować kod podatny na StrictMode w IdlingResource.
StrictMode działa tylko na platformie Android przez Android SDK. W Kotlin Multiplatform (KMP) kod commonMain nie może używać StrictMode, ale dla androidMain możesz dodać go jak zwykle. Dla części iOS użyj odpowiednika — DispatchQueue.main.async assertion dla głównego wątku.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również