Мій досвід з Android StrictMode. Здивування, розчарування, рішення
Вітаю, шановні читачі! Я Анатолій Берчанов, Android Tech Lead у N-iX. Сьогодні розповім вам про Android StrictMode. А саме про — свій шлях ознайомлення з цим інструментом, який складався з трьох етапів: здивування, розчарування, рішення.
🤔 Здивування
Розберімось, що таке StrictMode. Це інструмент для динамічного аналізу Android-застосунків на предмет виявлення помилок. Тобто він аналізує роботу застосунку під час його виконання.
StrictMode є частиною Android SDK та зʼявився ще у далекому API 9. З часом поповнювався додатковими можливостями для перевірок та реагування на них. Він глибоко інтегрований в Android runtime та системні бібліотеки, що дає змогу моніторити роботу застосунку та взаємодію із системою.
Які помилки може виявити StrictMode? Вони поділяються на два типи:
- ThreadPolicy — помилки, повʼязані із роботою потоків.
- VmPolicy — помилки, повʼязані із роботою віртуальної машини.
ThreadPolicy дозволяє визначити, які саме помилки потрібно відстежувати для конкретного потоку. Деякі з найкорисніших ThreadPolicy-перевірок:
- detectCustomSlowCalls — виявлення повільних викликів.
- detectNetwork — виявлення мережевих запитів.
- detectDiskWrites — виявлення запису на диск.
- detectDiskReads — виявлення зчитування з диску.
- detectUnbufferedIo — виявлення відсутності буферизації у I/O операціях.
Видається логічним застосувати такі перевірки для main потоку, оскільки в ньому заборонено виконувати довготривалі задачі. На системний краш, який цьому запобігає, розраховувати не варто. Адже він відбувається не завжди.
StrictMode дає змогу налаштувати різні ThreadPolicy для різних потоків. Це може бути корисним для задач, коли для певного фонового потоку дозволено виконувати довготривалі операції, однак необхідно впевнитись, що робота виконується оптимально. Тоді можна використати detectUnbufferedIo, якщо є I/O операції, або detectCustomSlowCalls.
На відміну від ThreadPolicy, перевірки VmPolicy розповсюджуються на всю роботу застосунку, а не на конкретний потік. Деякі з цікавих перевірок VmPolicy:
- detectLeakedRegistrationObjects — виявлення відсутності закриття реєстрації лісенерів або бродкастів.
- detectLeakedClosableObjects — виявлення незакритих ресурсів, що реалізовують Closeable-інтерфейс.
- detectIncorrectContextUse — виявлення неправильного використання контексту в певних сценаріях. Наприклад, використання контексту, який уже було знищено.
Для налаштування обробки порушень можна використати такі методи для обох — ThreadPolicy та VmPolicy:
- penaltyLog — логування порушень у logcat.
- penaltyDeath — краш застосунку.
- penaltyListener — самостійно обробити порушення.
Однак чомусь у ThreadPolicy доступні додаткові методи оброки (penaltyDialog, penaltyFlashScreen), яких немає у VmPolicy. З якоїсь причини розробники StrictMode вирішили порушити тут консистентність.
Налаштування та активація StrictMode виглядає дуже просто:
StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .build() ) StrictMode.setVmPolicy( StrictMode.VmPolicy.Builder() .detectAll() .penaltyLog() .build() )
Відстежувати повністю усі порушення можна, використовуючи функцію detectAll.
Отож в результаті:
- StrictMode допомагає виявляти різноманітні помилки, які могли бути допущені при розробці.
- Налаштувати його дуже просто.
- Це системний інструмент, що не потребує підключення сторонніх залежностей.
Однак у своєму досвіді я не бачив жодного проєкту, де був би налаштований StrictMode. І попри те, що це технологія далеко не нова, більшість Android-розробників не знає про неї. Дивно 🤔
😔 Розчарування
Налаштувавши StrictMode на проєкті та почитавши більше статей, я зміг виявити головний недолік цього інструменту. Річ у тому, що StrictMode може викидувати багато помилкових спрацювань.
Це можуть бути порушення, зроблені навмисно (коли розв’язання проблеми приносить більше незручностей, ніж сама проблема). Або порушення всередині сторонніх бібліотек. Виправити їх не можна, однак вони впливають на роботу застосунку і StrictMode бачить їх. Ба більше, StrictMode може інколи сваритись на роботу безпосередньо Android SDK. Тут ви вже точно нічого не вдієте.
Існує рішення — змінювати налаштування StrictMode для локалізованої ділянки коду:
fun permitDiskReads(func: () -> Any?) : Any? { if (BuildConfig.DEBUG) { val oldThreadPolicy = StrictMode.getThreadPolicy() StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder(oldThreadPolicy) .permitDiskReads().build()) val anyValue = func() StrictMode.setThreadPolicy(oldThreadPolicy) return anyValue } else { return func() } }
Однак воно не зможе допомогти у ситуаціях, коли проблема у сторонніх бібліотеках чи Android SDK.
Що відбувається, коли присутні помилкові спрацювання? Їх «ховають під килим». Бо для правильної обробки таких спрацювань необхідна ретельна перевірка розробником, а часу завжди мало і люди ліниві. А ще помилкові спрацювання не повинні заважати роботі: крашити застосунок, спамити ворнінгами.
Тобто у кращому випадку на StrictMode очікує перспектива писати логи, на які ніхто не буде дивитись. Тобто на перший погляд чудовий інструмент перетворюється на баласт.
Можливо, це і є причиною, чому StrictMode не став must have.
🚀 Рішення
Отже, StrictMode не вистачає гнучкості — можливості ігнорувати помилки чи обробляти їх іншим чином.
Однак заждіть. У нас є можливість встановити penaltyListener і обробляти помилки самостійно.
Тоді можна використовувати тільки цей обробник і у середині нього, залежно від конкретної помилки, робити відповідні дії — краш, відображення діалогу, логування, або взагалі ігнорувати помилку.
Тож було ухвалене рішення реалізувати такий обробник. Однак із часом за об’ємом своєї логіки він ставав схожим на окрему бібліотеку. Оскільки проблема помилкових спрацьовувань StrictMode досить системна, така бібліотека може бути корисною багатьом. Тому представляю вашій увазі StrictPro. Як StrictMode, тільки краще :)
Переваги StrictPro:
- Можливість ігнорувати конкретні випадки порушень.
- Можливість налаштувати окремі реакції на окремі порушення.
- Консистентний SDK (легка міграція, зворотна сумісність, не потрібні SDK-перевірки).
- StrictProUI.
Використання StrictPro виглядає таким чином:
StrictPro.setVmPolicy( StrictPro.VmPolicy.Builder() .detectAll() .penaltyLog() .penaltyDialog() .penaltyFlashScreen() .setWhiteList { contains("some substring in stack", null) base64("base64 encoded stack trace", ViolationPenalty.Ignore) base64("another base64 encoded stack trace", ViolationPenalty.Dialog) condition { violation -> // some custom logic ViolationPenalty.FlashScreen } } .build(), )
Зверніть увагу на блок setWhiteList. Використовуючи DSL всередині нього, ви можете встановлювати різні ViolationPenalty залежно від обраної умови.
Через те, що деякі можливості StrictMode розширювались у подальших версіях Android API, код використання StrictMode може виглядати так:
StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() .let { builder -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder.detectResourceMismatches() } else { builder } } .let { builder -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { builder.detectExplicitGc() } else { builder } } .penaltyLog() .build() )
Ви можете бачити багато надмірного коду, повʼязаного із перевірками на Build.VERSION.
У порівнянні з цим ви можете написати аналогічно значно лаконічніше, використовуючи StrictPro:
StrictPro.setThreadPolicy( StrictPro.ThreadPolicy.Builder() .detectResourceMismatches() .detectExplicitGc() .penaltyLog() .build() )
Тепер щодо StrictProUI. Це окреме розширення бібліотеки StrictPro, яке дозволяє бачити помилки у графічному інтерфейсі. Вони зберігаються на диску, тож можна запускати застосунок багато разів із різними сценаріями. Та потім переглядати, чи траплялись помилки. Жодне порушення від вас не втече 🙂
Для швидкої перевірки, як працює StrictMode та StrictPro, можете подивитись на тестовий застосунок у модулі app
у репозиторії.
Більше інформації про StrictPro та інструкцію з налаштування можна знайти у репозиторії за посиланням. Якщо у вас виникло бажання долучитись до проєкту, буду радий. Завжди є що покращити.
Чи доводилось вам використовувати StrictMode? Чи були випадки, коли він допоміг відловити проблеми? Буду радий дізнатись про ваш досвід у коментарях 😊
4 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів