Crash — nouzové ukončení mobilní aplikace kvůli neošetřené výjimce nebo fatální systémové chybě. Podle údajů Firebase Crashlytics se přibližně 2 % uživatelů denně setkává s pády a každý pád snižuje retenci o 10–20 %. Pochopení příčin a metod prevence pádů je povinnou dovedností pro mobilního vývojáře.
Hlavní body
Crash — je nouzové ukončení aplikace způsobené neošetřenou výjimkou nebo fatálním systémovým signálem, který nebyl zpracován v kódu aplikace. Když systém nebo virtuální stroj (JVM, ART) detekuje fatální stav — NullPointerException, IndexOutOfBoundsException, OutOfMemoryError — okamžitě zastaví proces a uvolní jej z paměti. Uživatel vidí náhlé zavření aplikace bez jakéhokoli systémového oznámení o chybě. Podle Google ztrácejí aplikace s crash-free mírou nižší než 99 % až 20 % aktivních uživatelů měsíčně.
Na Androidu se mechanismus zpracování pádů liší od desktopových systémů. Místo ladicího dialogu se stack trace Android jednoduše zabije proces a neukládá podrobné informace. Sběr informací o pádu je úkolem knihoven třetích stran (Crashlytics, Sentry, Bugsnag), které zachycují výjimky pomocí Thread.setDefaultUncaughtExceptionHandler předtím, než je proces ukončen.
iOS používá podobný mechanismus s NSException a Mach exceptions pro zpracování fatálních chyb. Při neošetřené výjimce systém ukončí aplikaci a zpráva se uloží jako soubor .crash. Sběr pádů na iOS vyžaduje integraci s Crashlytics nebo vestavěnou zprávou přes Xcode Organizer.
Pět kategorií pádů pokrývá 90 % všech pádů v mobilních aplikacích. Pochopení každého typu pomáhá rychleji diagnostikovat a opravovat problémy v produkčním prostředí.
NullPointerException (NPE) — nejrozšířenější typ pádu ve všech aplikacích Java/Kotlin. Vzniká při pokusu o volání metody nebo přístup k poli objektu, který je null. Typické scénáře: neinicializované pole Activity při otáčení obrazovky, null odpověď ze serveru při deserializaci JSON, neopatrná navigace adaptérem RecyclerView.
Kotlin řeší problém NPE na úrovni jazyka pomocí null-bezpečných typů: String? nelze použít bez explicitní kontroly. Kompatibilita s Javou a Reflection však stále vytváří rizika. Používejte anotace @NonNull a @Nullable a zapněte strictNullChecks v nástrojích statické analýzy.
fun safeLength(text: String?): Int {
return text?.length ?: 0 // bezpečné zpracování null
}
IndexOutOfBoundsException vzniká při přístupu k neexistujícímu indexu seznamu nebo pole. Častý scénář: odstranění prvku z RecyclerView bez synchronizace s adaptérem, vícevláknová modifikace ArrayList bez zámku, nesprávný výpočet pozice v ViewPager. ConcurrentModificationException — blízký příbuzný při současné iteraci a modifikaci kolekcí.
Pro vícevláknový přístup použijte CopyOnWriteArrayList nebo Lock-free kolekce z java.util.concurrent. Pro synchronizaci s UI použijte DiffUtil, který bezpečně a efektivně vypočítává rozdíl mezi starým a novým seznamem.
ClassCastException vzniká při převodu objektu na nekompatibilní typ. V Androidu typické příčiny: nesprávný typ ViewHolder v RecyclerView (různé typy buněk bez správného getItemViewType), nesprávný převod Fragmentu při navigaci, Serializable objekty s různými verzemi tříd.
Použijte bezpečný převod Kotlin pomocí operátoru as?, který vrací null při nekompatibilitě typů. V Javě — kontrola pomocí instanceof před převodem. U Parcelable objektů musíte v každé třídě deklarovat CREATOR.
IllegalStateException signalizuje volání metody v nevhodném stavu objektu. Typický příklad v Androidu — getSupportFragmentManager() po onSaveInstanceState, když commit() fragmentu není povolen. Další častý případ — volání dismiss() na již uzavřeném dialogu.
Před operacemi s FragmentManager zkontrolujte stav životního cyklu. Použijte commitAllowingStateLoss() pouze když jste si jisti, že ztráta stavu není kritická. V Kotlinu vytvářejte DSL-like stavitele, které vylučují nesprávné stavy na úrovni typů.
Native Crash vzniká v nativním kódu C/C++ při porušení paměti: přístup přes nulový ukazatel, double-free, přetečení zásobníkového bufferu. V Androidu k takovým pádům dochází v NDK knihovnách, herních enginech (Unity, Unreal) a systémových závislostech. Native Crash NENÍ zachycován Thread.setDefaultUncaughtExceptionHandler — okamžitě zabíjí proces.
Pro diagnostiku nativních pádů používejte minidump soubory (Breakpad) nebo Android tombstone. Firebase Crashlytics podporuje sběr nativních pádů přes NDK SDK. Na iOS se podobný problém řeší pomocí PLCrashReporter.
Tři nástroje dominují na trhu hlášení mobilních pádů. Každý poskytuje sběr stack trace, agregaci podle verzí aplikace a upozornění na nové pády.
Crashlytics — nejoblíbenější hlásič pádů pro mobilní aplikace, součást ekosystému Firebase. Automaticky sbírá stack trace, data o zařízení, verzi OS a uživatelské vlastní klíče. Integrace trvá 10 minut přes Firebase Console a Gradle Plugin. Crashlytics také podporuje skutečné logy (Logcat) a vlastní sledování.
FirebaseCrashlytics.getInstance()
.setCustomKey("current_screen", "ProfileFragment")
FirebaseCrashlytics.getInstance()
.log("User tapped login button")
Sentry — alternativa k Crashlytics s flexibilnějším systémem filtrování a podporou 90+ platforem. Na rozdíl od Firebase poskytuje Sentry vlastní server (self-hosted) pro společnosti s přísnými požadavky na data. Sentry podporuje distributivní sledování, breadcrumbs a integraci s CI/CD pipeline.
Bugsnag se vyznačuje podporou upozornění založených na závažnosti: rozděluje pády na kritické, chyby a varování. AppCenter od Microsoftu — bezplatný nástroj se základní funkcionalitou pro malé projekty. Oba podporují Android, iOS, React Native a Flutter.
Analýza pádu je proces rekonstrukce úplného obrazu události. Stack trace ukazuje pouze poslední bod selhání, ale neposkytuje kontext, který vedl k problému. Profesionální přístup zahrnuje čtyři fáze.
První fáze — čtení stack trace. Určete třídu, metodu a řádek kódu, kde došlo k výjimce. Sledujte řetězec volání od horního rámce dolů: poslední řádek v zásobníku je místo pádu a horní řádky jsou posloupnost volání. Deobfuskace (ProGuard/R8 mapping) je povinná pro produkční sestavení.
Druhá fáze — kontext zařízení. Crashlytics zobrazuje model zařízení, verzi OS, dostupnou paměť a verzi aplikace. Například pád pouze na Samsung Galaxy S10 s Androidem 11 ukazuje na problém s konkrétní verzí One UI, nikoli na obecnou chybu kódu.
Třetí fáze — reprodukce na testovacím zařízení. Pokud se pád stabilně nereprodukuje, zeptejte se uživatele na přesné kroky nebo použijte Remote Config pro logování před problematickou částí kódu. AB testování opravy na části publika pomáhá potvrdit řešení.
Čtvrtá fáze — monitorování po opravě. Po publikování opravy sledujte frekvenci pádu po dobu 3–5 dní. Pokud pád zcela zmizel — oprava fungovala. Pokud frekvence klesla, ale neklesla na nulu — existuje druhý scénář vyžadující samostatnou analýzu.
Systémový přístup k prevenci pádů zahrnuje nástroje statické analýzy, povinné testování okrajových případů a správné zpracování chyb na všech úrovních aplikace.
Detekt (Kotlin) a Lint (Android) nacházejí potenciální problémy ve fázi kompilace: nepoužívané proměnné, potenciální NPE, nesprávné použití API. Zapněte tyto nástroje v CI pipeline s prahem chyb. Například Detekt s konfigurací 30+ varování nebo jakékoli error-blocking nepropustí sestavení.
Pokrytí klíčových scénářů použití unit testy je základní ochranou proti regresním pádům. Testujte datové modely, ViewModel a UseCase vrstvy s okrajovými případy: null hodnoty, prázdné seznamy, neplatný JSON. UI testy přes Espresso nebo Compose Test pokrývají kritické toky: autentizace, platba, onboardig.
Navrhujte aplikaci tak, aby selhání v jednom modulu nezbořilo celou obrazovku. Používejte catch bloky na úrovni ViewModel s vrácením záložního stavu: zobrazení placeholderu místo seznamu, data z cache při absenci sítě, záložní obrázek při chybě načítání. To mění potenciální pád v řízený UX scénář.
Staged rollouts — standardní praxe Google Play a App Store: nová verze se distribuuje na 5 %, poté na 20 % a nakonec na 100 % publika s intervalem 1–3 dnů. V každé fázi se monitoruje frekvence pádů: pokud crash-free míra klesne pod 99,5 %, zavádění se automaticky zastaví. Firebase Remote Config umožňuje vypínat problematické funkce bez publikování nové verze.
Renovate nebo Dependabot v CI automaticky kontrolují knihovny na známé zranitelnosti a kritické chyby. Aktualizace jedné závislosti může odstranit celou třídu pádů. Aktualizace však testujte na staging prostředí před nasazením do produkce — nová verze knihovny může obsahovat nekompatibilní změny.
Často kladené otázky
Ne. Část pádů je způsobena faktory mimo kontrolu vývojáře: systémové chyby, hardwarové problémy, nekompatibilita firmwaru. Cílem je snížit frekvenci na 0,1 % a níže a zbývající pády minimalizovat z hlediska doby reakce.
Hlásič pádů sbírá stack trace, stav paměti a zařízení v okamžiku pádu. Analytika sbírá behaviorální data uživatele. Crashlytics kombinuje oba přístupy a poskytuje kontext pádu spolu s uživatelskými vlastními klíči.
ProGuard a R8 obfuskují kód pro ochranu duševního vlastnictví. Pro deobfuskaci nahrajte mapping soubor do Crashlytics při publikování. Bez mapping souboru stack trace zobrazí a.a(), b.b() místo skutečných názvů tříd a metod.
Pomocí Thread.setDefaultUncaughtExceptionHandler na Androidu: knihovna zaregistruje svůj vlastní handler, který jako první obdrží neošetřenou výjimku, uloží data a teprve poté ukončí proces. Na iOS se používá NSSetUncaughtExceptionHandler pro NSException a Mach exception handler pro signály.
Fatal — aplikace byla ukončena. Non-fatal (zachycená výjimka) — vývojář zachytil výjimku pomocí try-catch, ale to může naznačovat potenciální problém. Crashlytics tyto typy rozlišuje a umožňuje filtrovat non-fatal samostatně, aby nezanášel dashboard.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také