Crash — аварійне завершення мобільного додатку через необроблене виключення або фатальний системний збій. За даними Firebase Crashlytics, близько 2% користувачів стикаються з крашами щодня, а кожне падіння знижує retention на 10–20%. Розуміння причин та методів запобігання крашам — обов'язковий навик для мобільного розробника.
Головне
Crash — це аварійне завершення додатку, викликане необробленим виключенням або фатальним системним сигналом, яке не було оброблено в коді додатку. Коли система або віртуальна машина (JVM, ART) виявляє фатальний стан — NullPointerException, IndexOutOfBoundsException, OutOfMemoryError — вона негайно зупиняє процес та вивантажує його з пам'яті. Користувач бачить раптове закриття додатку без жодного системного повідомлення про помилку. За даними Google, додатки з crash-free rate нижче 99% втрачають до 20% активних користувачів на місяць.
На Android механізм обробки крашів відрізняється від десктопних систем. Замість налагоджувального діалогу з stack trace Android просто вбиває процес і не зберігає детальну інформацію. Збір інформації про краш — завдання сторонніх бібліотек (Crashlytics, Sentry, Bugsnag), які перехоплюють виключення через Thread.setDefaultUncaughtExceptionHandler до того, як процес буде завершено.
iOS використовує аналогічний механізм з NSException та Mach exceptions для обробки фатальних помилок. При необробленому виключенні система завершує додаток, а звіт зберігається у вигляді .crash-файлу. Збір крашів на iOS вимагає інтеграції з Crashlytics або вбудованого звіту через Xcode Organizer.
П'ять категорій крашів покривають 90% усіх падінь у мобільних додатках. Розуміння кожного типу допомагає швидше діагностувати та виправляти проблеми в продакшені.
NullPointerException (NPE) — найпоширеніший тип краша у всіх Java/Kotlin додатках. Виникає при спробі викликати метод або звернутися до поля об'єкта, який дорівнює null. Типові сценарії: неініціалізоване поле Activity при повороті екрану, null-відповідь від сервера при десеріалізації JSON, неакуратна навігація по адаптеру RecyclerView.
Kotlin вирішує проблему NPE на рівні мови через null-safe типи: String? не може бути використаний без явної перевірки. Однак Java-сумісність та Reflection все ще створюють ризики. Використовуйте анотації @NonNull та @Nullable та увімкніть strictNullChecks в інструментах статичного аналізу.
fun safeLength(text: String?): Int {
return text?.length ?: 0 // безпечна обробка null
}
IndexOutOfBoundsException виникає при зверненні до неіснуючого індексу списку або масиву. Частий сценарій: видалення елемента з RecyclerView без синхронізації з адаптером, багатопотокова модифікація ArrayList без блокування, неправильний розрахунок позиції в ViewPager. ConcurrentModificationException — близький родич при одночасній ітерації та модифікації колекцій.
Використовуйте CopyOnWriteArrayList для багатопотокового доступу або Lock-free колекції з java.util.concurrent. Для синхронізації з UI застосовуйте DiffUtil, який обчислює різницю між старим та новим списком безпечно та ефективно.
ClassCastException виникає при приведенні об'єкта до несумісного типу. В Android типові причини: неправильний тип ViewHolder в RecyclerView (різні типи комірок без коректного getItemViewType), невірне приведення Fragment при навігації, Serializable-об'єкти з різними версіями класів.
Використовуйте safe-каст Kotlin через оператор as?, який повертає null при несумісності типів. В Java — перевірка через instanceof перед приведенням. Для Parcelable-об'єктів обов'язково оголошуйте CREATOR у кожному класі.
IllegalStateException сигналізує про виклик методу в непідходящому стані об'єкта. Типовий приклад в Android — getSupportFragmentManager() після onSaveInstanceState, коли commit() фрагмента не допускається. Інший частий випадок — виклик dismiss() на вже закритому діалозі.
Перевіряйте стан життєвого циклу перед операціями з FragmentManager. Використовуйте commitAllowingStateLoss() тільки коли впевнені, що втрата стану не критична. В Kotlin створюйте DSL-подібні білдери, які виключають некоректні стани на рівні типів.
Native Crash виникає в нативному коді C/C++ при порушенні пам'яті: звернення за нульовим покажчиком, double-free, stack buffer overflow. В Android такі краші відбуваються в NDK-бібліотеках, ігрових движках (Unity, Unreal) та системних залежностях. Native Crash НЕ перехоплюється Thread.setDefaultUncaughtExceptionHandler — він вбиває процес миттєво.
Для діагностики нативних крашів використовуйте minidump-файли (Breakpad) або tombstones Android. Firebase Crashlytics підтримує збір нативних крашів через NDK SDK. На iOS аналогічна проблема вирішується через PLCrashReporter.
Три інструменти домінують на ринку мобільного краш-репортингу. Кожен надає збір stack trace, агрегацію за версіями додатку та сповіщення про нові падіння.
Crashlytics — найпопулярніший краш-репортер для мобільних додатків, що входить в екосистему Firebase. Він автоматично збирає stack trace, дані про пристрій, версію ОС та користувацькі custom keys. Інтеграція займає 10 хвилин через Firebase Console та Gradle Plugin. Crashlytics також підтримує реальні логи (Logcat) та користувацькі треки.
FirebaseCrashlytics.getInstance()
.setCustomKey("current_screen", "ProfileFragment")
FirebaseCrashlytics.getInstance()
.log("User tapped login button")
Sentry — альтернатива Crashlytics з більш гнучкою системою фільтрації та підтримкою 90+ платформ. На відміну від Firebase, Sentry надає самописний сервер (self-hosted) для компаній з жорсткими вимогами до даних. Sentry підтримує дистрибутивний трекінг, breadcrumbs та інтеграцію з CI/CD пайплайнами.
Bugsnag виділяється підтримкою Severity-based алертів: розділяє краші на critical, error та warning. AppCenter від Microsoft — безкоштовний інструмент з базовим функціоналом для невеликих проєктів. Обидва підтримують Android, iOS, React Native та Flutter.
Аналіз краша — процес відновлення повної картини події. Stack trace показує тільки останню точку відмови, але не дає контекст, який призвів до проблеми. Професійний підхід включає чотири етапи.
Перший етап — читання stack trace. Визначте клас, метод та рядок коду, де відбулося виключення. Прослідкуйте ланцюжок викликів від верхнього фрейму до нижнього: останній рядок у стеку — місце падіння, а верхні рядки — послідовність викликів. Deobfuscation (ProGuard/R8 mapping) обов'язковий для продакшен-збірок.
Другий етап — контекст пристрою. Crashlytics показує модель пристрою, версію ОС, доступну пам'ять та версію додатку. Наприклад, краш тільки на Samsung Galaxy S10 з Android 11 вказує на проблему з конкретною версією One UI, а не на загальну помилку коду.
Третій етап — відтворення на тестовому пристрої. Якщо краш не відтворюється стабільно, запитайте у користувача точні кроки або використовуйте Remote Config для логування перед проблемною ділянкою коду. AB-тестування фіксу на частині аудиторії допомагає підтвердити рішення.
Четвертий етап — моніторинг після фіксу. Після публікації виправлення спостерігайте за частотою краша протягом 3–5 днів. Якщо падіння повністю зникло — фікс спрацював. Якщо частота знизилася, але не пішла в нуль — існує другий сценарій, що потребує окремого аналізу.
Системний підхід до запобігання крашам включає інструменти статичного аналізу, обов'язкове тестування крайових випадків та грамотну обробку помилок на всіх рівнях додатку.
Detekt (Kotlin) та Lint (Android) знаходять потенційні проблеми на етапі компіляції: невикористані змінні, потенційні NPE, неправильне використання API. Включіть ці інструменти в CI-пайплайн з порогом помилок. Наприклад, Detekt з конфігурацією на 30+ warning або будь-яке error-blocking не пропускає збірку.
Покриття ключових сценаріїв використання unit-тестами — базовий захист від регресійних крашів. Тестуйте моделі даних, ViewModel та UseCase-шари з граничними випадками: null-значення, порожні списки, некоректні JSON. UI-тести через Espresso або Compose Test покривають критичні flows: авторизація, платіж, onboarding.
Проектуйте додаток так, щоб збій в одному модулі не валив весь екран. Використовуйте catch-блоки на рівні ViewModel з поверненням fallback-стану: показ заглушки замість списку, кешовані дані при відсутності мережі, запасне зображення при помилці завантаження. Це перетворює потенційний краш у контрольований UX-сценарій.
Staged rollouts — стандартна практика Google Play та App Store: нова версія поширюється на 5%, потім на 20% та на 100% аудиторії з інтервалом 1–3 дні. На кожному етапі моніториться частота крашів: якщо crash-free rate падає нижче 99.5%, викатка зупиняється автоматично. Firebase Remote Config дозволяє відключати проблемні функції без публікації нової версії.
Renovate або Dependabot в CI автоматично перевіряють бібліотеки на відомі вразливості та критичні баги. Оновлення однієї залежності може усунути цілий клас крашів. Однак тестуйте оновлення на staging-оточенні перед викатом у продакшен — нова версія бібліотеки може містити несумісні зміни.
Часті запитання
Ні. Частина крашів викликається факторами поза контролем розробника: системні помилки, апаратні проблеми, несумісність прошивки. Мета — знизити частоту до 0.1% і нижче, а решту крашів мінімізувати за часом реакції.
Краш-репортер збирає stack trace, стан пам'яті та пристрій в момент падіння. Аналітика збирає поведінкові дані користувача. Crashlytics об'єднує обидва підходи, надаючи контекст краша разом з користувацькими custom keys.
ProGuard та R8 обфускують код для захисту інтелектуальної власності. Для деобфускації завантажте mapping-файл в Crashlytics при публікації. Без mapping-файлу stack trace покаже a.a(), b.b() замість реальних імен класів та методів.
Через Thread.setDefaultUncaughtExceptionHandler на Android: бібліотека реєструє свій обробник, який першим отримує необроблене виключення, зберігає дані і тільки потім завершує процес. На iOS використовується NSSetUncaughtExceptionHandler для NSException та Mach exception handler для сигналів.
Fatal — додаток завершився. Non-fatal (caught exception) — розробник зловив виключення через try-catch, але це може вказувати на потенційну проблему. Crashlytics розрізняє ці типи та дозволяє фільтрувати non-fatal окремо, щоб не засмічувати дашборд.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також