StrictMode је уграђен у Android SDK алат за програмере који у реалном времену открива и пријављује случајне I/O операције и мрежне позиве на главном ниту апликације. Он не исправља грешке, већ делује као детектор — баца изузетке или пише у LogCat при кршењу задатих политика. Према Google, 2024, правилно подешавање StrictMode омогућава откривање до 80% проблема перформанси пре издавања апликације.
Главно
StrictMode је API који је део Android SDK-а од API Level 9 (Android 2.3 Gingerbread). Ъегов задатак је да у реалном времену открије случајно извршавање тешких операција на главном (UI) ниту које могу блокирати рендеровање интерфејса. Главни нит је одговоран за обраду корисничког уноса, рачунање изгледа и рендеровање — свако блокирање дуже од 16 ms доводи до прескацања оквира.
StrictMode следи принцип „fail fast“ — откриј проблем што раније могуће, поћељно у тренутку његовог првог појављивања. Уместо да чека жалбе корисника на успорена, програмер добија сигнал (логове, дијалог или crash) већ у фази развоја. Алат не захтева додатне библиотеке или Gradle конфигурацију — довољно је неколико линија кода у Application.onCreate и алат ради аутоматски на свим уређајима.
StrictMode је намењен свим Android програмерима, без обзира на искуство. Почетницима помаже да формирају правилне навике (да не шаљу мрежне захтеве у UI ниту), искуснима — да аутоматизују контролу квалитета у CI/CD пајплајну. Велики пројекти (Google, Uber, Spotify) укључују StrictMode у debug builds са penaltyDeath, а у release builds га искључују провером BuildConfig.DEBUG.
StrictMode пресреће системске позиве које могу блокирати нит и упоређује их са скупом активних политика. Ако позив одговара политици и извршава се на главном ниту, StrictMode примењује задати penalty. Механизам пресретања је имплементиран кроз унутарпроцесни хук — не користи reflection и ради са минималним оптерећењем.
При активирању политике, StrictMode поставља свој хендлер у улазну тачку системских позива (FileInputStream, FileOutputStream, Socket, URLConnection). Када апликација позове, на пример, URLConnection.openStream на главном ниту, StrictMode проверава тренутни нит — ако је main thread, алат се активира. У Android 6.0+ механизам је појачан: мрежни позиви у main thread-у генеришу NetworkOnMainThreadException чак и без StrictMode-а, али StrictMode омогућава контролу и диск I/O-а.
Свака политика може имати сопствени тип penalty или комбинацију: penaltyLog — упис у LogCat са stacktrace, penaltyDialog — приказ дијалога кориснику (само у debug), penaltyDeath — бацање изузетке и crash апликације, penaltyDropBox — чување података у DropBoxManager-у за каснију анализу. За CI/CD пајплајн се препоручује penaltyDeath — гарантује да ће мергеви са кршењем неће проћи непримећени.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
StrictMode дели политике на два нивоа: ThreadPolicy (нитске — шта не радити на главном ниту) и VmPolicy (виртуелне машине — цурења меморије и ресурса). Оба нивоа се подешавају независно и раде паралелно.
На нивоу нита, StrictMode контролише четири типа кршења: читање са диска (detectDiskReads), писање на диск (detectDiskWrites), мрежне операције (detectNetwork) и прилагођене споре позиве (detectCustomSlowCalls). disk_read се активира при сваком читању SharedPreferences, SQLite, датотека на главном ниту. network — при HTTP захтевима, WebSocket, Socket везама. У Android 11+ додат је detectUnbufferedIO за откривање небуферисаног I/O-а.
VmPolicy контролише цурења на нивоу ART виртуелне машине: detectActivityLeaks (Активности које нису уништене), detectLeakedClosableObjects (незатворени Cursor, Stream, Socket), detectLeakedRegistrationObjects (отказани BroadcastReceiver, ServiceConnection). Ако VmPolicy открије да је Активност креирана али није уништена након onDestroy, исписује пун стацк трејс — штеди сате дебагирања цурења меморије.
| Политика | Ниво | Шта открива |
|---|---|---|
| detectDiskReads | Thread | Читање SharedPrefs, SQLite, датотека у UI ниту |
| detectDiskWrites | Thread | Писање у SharedPrefs, SQLite, датотеке у UI ниту |
| detectNetwork | Thread | Све мрежне операције у UI ниту |
| detectActivityLeaks | VM | Активности које су преживеле onDestroy |
| detectLeakedClosableObjects | VM | Незатворени Cursor, Stream, Socket |
Кроз detectCustomSlowCalls можете означити сопствене методе као „сумњиве“ и добијати упозорење при прекорачу задатог прага. На пример, ако се ваш метод loadUserProfile() обично извршава за 5 ms, али у неким случајевима траје 200 ms — овојте га у StrictMode.noteSlowCall("loadUserProfile"). Ако трајање премаши праг (подразумевано 2000 ms), StrictMode ће генерисати penalty. Праг се подешава кроз setSlowCallDurationThreshold.
Основно подешавање StrictMode заузима 10 линија кода и врши се у onCreate методи прилагођене Application класе. Главно правило: StrictMode се укључује само у debug builds — у release builds успорава апликацију и може да створи лажне аларме.
Креирајте класу која наслеђује Application, региструјте је у AndroidManifest.xml кроз атрибут android:name и додајте конфигурацију StrictMode. ThreadPolicy.Builder укључује све детекторе и све врсте penalty (осим dialog — ради само са повезаним дебагером). VmPolicy.Builder додаје детекторе цурења Activity и Closable. За велике пројекте (100+ екрана) препоручује се подешавање VmPolicy са penaltyDeath на детектор Activity Leaks — строго је, али ефикасно.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
За аутоматску контролу у CI/CD користите penaltyDeath — ако неки тест прекрши политику, апликација ће пасти са изузетком. Комбинујте са Android Test Orchestrator-ом да сваки тест ради у чистом процесу. За UI тестове (Espresso, Compose Test) напишите прилагођени TestRule који пресреће StrictMode кршења и претвара их у assertion failure. Пример: у @Before укључите StrictMode, а у @After проверите да није било violations.
Подразумевано, праг за customSlowCall је 2000 ms, за disk_read и disk_write — без прага (свака операција га покреће). Кроз setSlowCallDurationThreshold и setSlowIoDurationThreshold можете поставити сопствене вредности у милисекундама. Ако ваша апликација легитимно чита SharedPreferences на главном ниту (мала конфигурација), повећајте праг на 10–20 ms — пресећи ће брзе операције, али ће задржати споре.
StrictMode је моћан, али хирови алат. Неправилно подешавање доводи до милион лажних аларма, због чега програмери престају да их примећују. Испод су проверене праксе прикупљене из искуства великих Android тимова.
Ово је гвоздено правило: StrictMode НИКАДА не сме да буде активан у release builds. Користите заставицу BuildConfig.DEBUG или прилагођену buildConfigField. У release builds, многе библиотеке трећих страна легитимно извршавају операције на главном ниту (иницијализација SDK, писање кеша), и StrictMode ће створити лажне аларме. Штавише, penaltyDialog у release build-у ће приказати дијалог крањем кориснику — што је неприхватљиво.
За мале пројекте (1–10 екрана) подесите penaltyLog — логови су довољни за речну анализу. За средње пројекте (10–50 екрана) додајте penaltyDeath за network и customSlowCalls. За велике пројекте (50+ екрана) укључите пун скуп политика са penaltyDeath у CI/CD, а за локални развој — penaltyLog. Ова градација омогућава да не оптерећујете програмера лажним crash-евима, али да строго контролишете квалитет у пајплајну.
Неке библиотеке (Firebase, Crashlytics, Adjust) легитимно извршавају операције у позадини које StrictMode може погрешно да детектује. Решења: додајте библиотеку у whitelist кроз penaltyListener, ажурирајте библиотеку на верзију са јасним преласком на background thread или користите StrictMode.vmPolicy. У Android 11+ појавио се StrictMode.OnVmViolationListener за програмско филтрирање кршења по stacktrace-у.
// Филтрирање лажних позитива кроз penaltyListener
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyListener { violation ->
val stack = violation.stackTraceToString()
if ("com.google.firebase" !in stack) {
logViolation(violation)
}
}
.build()
)
StrictMode није једини алат за контролу квалитета у Android екосистему. Да бисмо разумели његово место, упоредимо га са Android Lint, Android Profiler и Perfetto по кључним критеријума: време провере, дубина анализе и аутоматизација.
| Критеријум | StrictMode | Android Lint | Profiler / Perfetto |
|---|---|---|---|
| Време провере | Runtime (током рада) | Compile time (пре покретања) | Runtime (post-mortem) |
| Шта проверава | Диск, мрежа, цурења | XML, код, ресурси | CPU, меморија, мрежа, енергија |
| Аутоматизација | CI/CD кроз penaltyDeath | Gradle task + lint-baseline | Захтева речну анализу |
| Дубина | Само UI нит и цурења | Статичка анализа кода | Пуна слика перформанси |
| Лажни аларми | Средњи (зависе од библиотека) | Ниски (подешена правила) | Нема (стварна мерења) |
Најбоља стратегија је комбинација сва три приступа: Android Lint хвата очигледне грешке у фази компилације (на пример, заборављени IdleHandler), StrictMode открива проблеме у радном окружењу, а Android Profiler / Perfetto се користи за дубинску анализу када прва два алата не дају одговор. У реалним пројектима (Google Maps, Instagram) StrictMode се примењује у другој недељи развоја — одмах након подешавања основне архитектуре.
Размотримо два реална сценарија у којима StrictMode помаже да се открију и отклоне проблеми перформанси: читање SharedPreferences на главном ниту и цурење Activity кроз нерегистровани callback.
При покретању апликације, StrictMode са политиком detectDiskReads ће открити читање SharedPreferences на главном ниту. Решење: учитајте конфигурацију асинхроно кроз CoroutineScope или кеширајте у меморији при покретању. SharedPreferences синхроно чита XML датотеку са диска — чак и код мале датотеке (1–2 KB), операција траје 1–5 ms, а на јефтиним уређајима до 20 ms, што може довести до прескацања оквира.
// ❌ Проблематичан код — читање SharedPrefs у UI ниту
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// StrictMode detectDiskReads → VIOLATION!
prefs = getSharedPreferences("config", MODE_PRIVATE)
}
}
// ✅ Исправљен код — читање кроз Coroutine
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfigAsync()
}
}
StrictMode са VmPolicy.detectActivityLeaks ће открити Activity која је изашла из стека (finish је позван), али објекат Activity и даље виси у меморији због статичке референце или нерегистрованог callback-а. Типичан сценариј: регистрација EventBus или LocationListener у onResume без позива unregister у onPause. VmPolicy ће исписати стацк трејс са назнаком линије где је креирана референца.
// ❌ Цурење — callback није отказан
private var locationCallback: LocationCallback? = null
override fun onResume() {
super.onResume()
locationCallback = LocationCallback(this::onLocationUpdated)
locationManager.register(locationCallback) // StrictMode → LEAK!
}
override fun onPause() {
super.onPause()
// Заборављено: locationManager.unregister(locationCallback)
}
Често постављана питања
StrictMode заиста додаје мали оптерећење — сваки системски позив се проверава на усклађеност са политикама. Утицај на перформансу је 1–3% у debug build-у и не постоји у release-у (где је StrictMode искључен). При укључивању detectAll на старијим уређајима (Android 6–8) оптерећење може досећи 5%, па се препоручује подешавање само потребних политика.
Да, StrictMode је потпуно компатибилан са Jetpack Compose. Политике диска и мреже раде на нивоу оквира, независно од UI оквира. Штавише, у Compose-у критичност UI блокада је већа — Compose прецртава оквире на 120 FPS на уређајима са високом фреквенцијом освежавања, па додатних 5 ms за читање датотеке постаје приметнији.
Од Android 8.1 (API 27), SharedPreferences може да користи кеширање у меморији — ако је датотека већ прочитана, поновно читање не покреће StrictMode. Проверите да ли први пут позивате getSharedPreferences (хладно читање) и да ли је политика detectDiskReads активна. Такође проверите да ли StrictMode није прегазан у parent-free фрагменту.
У JUnit тестовима користите StrictMode.allowThreadDiskReads() и StrictMode.allowThreadDiskWrites() у @Before, а у @After вратите поставке кроз StrictMode.enableDefaults(). За Instrumentation тестове користите прилагођени TestRunner са привременим чувањем оригиналне политике. У Espresso тестовима згодно је завити код осетљив на StrictMode у IdlingResource.
StrictMode ради само на Android платформи кроз Android SDK. У Kotlin Multiplatform (KMP) код commonMain не може да користи StrictMode, али за androidMain га можете додати као обично. За iOS део користите аналог — DispatchQueue.main.async assertion за главни нит.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође