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 (наказание). Механизм перехвата реализован через внутрипроцессный хук — он не использует reflection и работает с минимальным оверхедом.
При активации политики 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 → 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-сборке и отсутствует в 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также