StrictMode — це вбудований в Android SDK інструмент розробника, який у реальному часі виявляє та повідомляє про випадкові операції введення-виведення та мережеві виклики на головному потоці застосунку. Він не виправляє помилки, а виступає в ролі детектора — викидає винятки або пише в LogCat при порушенні заданих політик. За даними Google, 2024, правильне налаштування StrictMode дозволяє виявити до 80% проблем продуктивності ще до релізу застосунку.
Головне
StrictMode — це API, що входить до Android SDK, починаючи з API Level 9 (Android 2.3 Gingerbread). Його завдання — у рантаймі виявляти випадкове виконання важких операцій на головному (UI) потоці, які можуть заблокувати відображення інтерфейсу. Головний потік відповідає за обробку введення користувача, обчислення макету та відображення — будь-яке блокування довше 16 мс призводить до пропуску кадру.
StrictMode дотримується принципу «fail fast» — виявити проблему якомога раніше, бажано в момент її першої появи. Замість того щоб чекати скарг користувачів на гальмування, розробник отримує сигнал (логи, діалог або crash) прямо на етапі розробки. Інструмент не вимагає додаткових бібліотек та конфігурації Gradle — достатньо кількох рядків коду в Application.onCreate, і він працює автоматично на всіх пристроях.
StrictMode призначений для всіх розробників Android, незалежно від досвіду. Початківцям він допомагає сформувати правильні звички (не робити мережеві запити в UI-потоці), досвідченим — автоматизувати контроль якості в CI/CD пайплайні. Великі проекти (Google, Uber, Spotify) включають StrictMode в debug-збірки з penaltyDeath, а в релізних збірках відключають через перевірку BuildConfig.DEBUG.
StrictMode перехоплює системні виклики, які можуть блокувати потік, і порівнює їх з набором активних політик. Якщо виклик відповідає політиці та виконується на головному потоці, StrictMode застосовує заданий penalty (покарання). Механізм перехоплення реалізований через внутрішньопроцесний хук — він не використовує рефлексію та працює з мінімальним оверхедом.
При активації політики StrictMode впроваджує свій обробник в точку входу системних викликів (FileInputStream, FileOutputStream, Socket, URLConnection). Коли застосунок викликає, наприклад, URLConnection.openStream на головному потоці, StrictMode перевіряє поточний потік — якщо це main thread, інструмент спрацьовує. В Android 6.0+ механізм посилений: мережеві виклики в main thread генерують NetworkOnMainThreadException навіть без StrictMode, але StrictMode дозволяє контролювати і disk I/O.
Кожна політика може мати свій тип penalty або комбінацію: penaltyLog — запис в LogCat зі стектрейсом, penaltyDialog — показ діалогу користувачеві (тільки в debug), penaltyDeath — викид винятку та краш застосунку, penaltyDropBox — збереження даних в DropBoxManager для подальшого аналізу. Для CI/CD пайплайну рекомендується penaltyDeath — це гарантує, що жоден merge з порушенням не пройде непоміченим.
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 для виявлення небуферизованого введення-виведення.
VmPolicy контролює витоки на рівні віртуальної машини ART: detectActivityLeaks (Activity, які не були знищені), detectLeakedClosableObjects (незакриті Cursor, Stream, Socket), detectLeakedRegistrationObjects (нескасовані BroadcastReceiver, ServiceConnection). Якщо VmPolicy виявляє, що Activity була створена, але не знищена після виклику onDestroy, він виводить full stack trace — це економить години налагодження витоків пам'яті.
| Політика | Рівень | Що детектить |
|---|---|---|
| detectDiskReads | Thread | Читання SharedPrefs, SQLite, файлів в UI-потоці |
| detectDiskWrites | Thread | Запис в SharedPrefs, SQLite, файли в UI-потоці |
| detectNetwork | Thread | Будь-які мережеві операції в UI-потоці |
| detectActivityLeaks | VM | Activity, що пережили onDestroy |
| detectLeakedClosableObjects | VM | Незакриті Cursor, Stream, Socket |
Через detectCustomSlowCalls можна позначити власні методи як «підозрілі» та отримувати попередження при перевищенні заданого порогу. Наприклад, якщо ваш метод loadUserProfile() зазвичай виконується 5 мс, але в деяких випадках займає 200 мс — оберніть його в StrictMode.noteSlowCall(«loadUserProfile»). Якщо тривалість перевищує поріг (за умовчанням 2000 мс), StrictMode згенерує penalty. Поріг налаштовується через setSlowCallDurationThreshold.
Базове налаштування StrictMode займає 10 рядків коду та виконується в методі onCreate кастомного Application-класу. Головне правило: StrictMode включається тільки в debug-збірках — в релізних збірках він сповільнює застосунок та може створити помилкові спрацьовування.
Створіть клас, що успадковує 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 мс, для disk_read та disk_write — без порогу (будь-яка операція тригерить). Через setSlowCallDurationThreshold та setSlowIoDurationThreshold можна задати свої значення в мілісекундах. Якщо ваш застосунок легітимно читає SharedPreferences на головному потоці (невеликий конфіг), збільште поріг до 10–20 мс — це відсіче швидкі читання, але залишить повільні.
StrictMode — потужний, але примхливий інструмент. Неправильне налаштування призводить до мільйону помилкових спрацьовувань, через що розробники перестають звертати на них увагу. Нижче — перевірені практики, зібрані з досвіду великих Android-команд.
Це залізне правило: StrictMode НІКОЛИ не повинен бути активним в release-збірках. Використовуйте прапорець BuildConfig.DEBUG або кастомний buildConfigField. В release-збірках багато сторонніх бібліотек легітимно виконують операції на головному потоці (ініціалізація SDK, запис кешу), і StrictMode створить помилкові спрацьовування. Більше того, penaltyDialog в release-збірці покаже діалог кінцевому користувачеві — що неприпустимо.
Для невеликих проектів (1–10 екранів) налаштуйте penaltyLog — достатньо логів для ручного аналізу. Для середніх проектів (10–50 екранів) додайте penaltyDeath на network та customSlowCalls. Для великих проектів (50+ екранів) увімкніть повний набір політик з penaltyDeath в CI/CD, а для локальної розробки — penaltyLog. Така градація дозволяє не перевантажувати розробника помилковими крашами, але жорстко контролювати якість в пайплайні.
Деякі бібліотеки (Firebase, Crashlytics, Adjust) легітимно виконують операції в фоні, які StrictMode може помилково детектувати. Рішення: додайте бібліотеку до whitelist через penaltyListener, оновіть бібліотеку до версії з явним перемиканням на background thread, або використовуйте StrictMode.vmPolicy. В Android 11+ з'явився StrictMode.OnVmViolationListener для програмної фільтрації порушень за stacktrace.
// Фільтрація false positives через 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 або кешувати в memory на старті. SharedPreferences синхронно читає XML-файл з диска — навіть при маленькому файлі (1–2 КБ) операція займає 1–5 мс, а на дешевих пристроях до 20 мс, що може призвести до пропуску кадру.
// ❌ Проблемний код — читання SharedPrefs в UI-потоці
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// StrictMode detectDiskReads → ПОРУШЕННЯ!
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 → ВИТІК!
}
override fun onPause() {
super.onPause()
// Забули: locationManager.unregister(locationCallback)
}
Часті запитання
StrictMode дійсно додає невеликий оверхед — кожен системний виклик перевіряється на відповідність політикам. Вплив на продуктивність становить 1–3% в debug-збірці та відсутній в release (де StrictMode відключається). При включенні detectAll на старих пристроях (Android 6–8) оверхед може досягати 5%, тому рекомендується налаштовувати тільки потрібні політики.
Так, StrictMode повністю сумісний з Jetpack Compose. Політики disk та network працюють на рівні фреймворку, незалежно від UI-фреймворку. Більше того, в Compose критичність UI-блокувань вища — Compose перемальовує кадри з частотою 120 FPS на пристроях з високою частотою оновлення, тому зайві 5 мс на читання файлу стають помітнішими.
Починаючи з 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також