Awarie

Aplikacja na Androida ulega awarii przy nieoczekiwanym zamknięciu spowodowanym przez nieobsługiwany wyjątek lub sygnał. Aplikacja napisana w języku Java lub Kotlin ulega awarii w przypadku zgłoszenia nieobsłużonego wyjątku, reprezentowanego przez Throwable. An napisanej przy użyciu kodu maszynowego lub awarii języka C++, jeśli istnieje nieobsługiwany taki jak SIGSEGV.

Gdy aplikacja ulega awarii, Android przerywa jej proces i wyświetla okno aby powiadomić użytkownika o zatrzymaniu aplikacji, tak jak na ilustracji 1.

Awaria aplikacji na urządzeniu z Androidem

Rysunek 1. Awaria aplikacji na urządzeniu z Androidem

Aplikacja nie musi być uruchomiona na pierwszym planie, aby uległa awarii. Dowolna aplikacja nawet komponentów takich jak odbiorniki czy dostawcy treści, działają w tle, może to spowodować awarię aplikacji. Te awarie są co jest często mylące dla użytkowników, ponieważ nie wchodzą w interakcję z aplikacją.

Jeśli w aplikacji występują awarie, możesz skorzystać ze wskazówek na tej stronie, aby: zdiagnozować i rozwiązać problem.

Wykryj problem

Nie zawsze wiesz, że awarie występują u użytkowników gdy używają Twojej aplikacji. Jeśli aplikacja została już opublikowana, możesz użyć Android Vitals pokazuje częstotliwość awarii w Twojej aplikacji.

Android Vitals

Android Vitals może pomóc w monitorowaniu i poprawianiu częstotliwości awarii aplikacji. Android Vitals mierzy kilka częstotliwości awarii:

  • Częstotliwość awarii: odsetek aktywnych użytkowników dziennie, którzy nie wystąpiła żadna awaria.
  • Częstotliwość awarii widocznych dla użytkowników: odsetek aktywnych użytkowników dziennie u których wystąpiła co najmniej 1 awaria podczas aktywnego korzystania z aplikacji (awaria widoczna dla użytkowników). Aplikacja jest uznawana za aktywną. jeśli wyświetla działanie lub wykonuje usługę działającą na pierwszym planie.

  • Częstotliwość wielokrotnych awarii: odsetek aktywnych użytkowników dziennie, którzy wystąpiły co najmniej 2 awarie.

Aktywni użytkownicy dziennie to unikalny użytkownik, który korzysta z Twojej aplikacji. w ciągu jednego dnia na 1 urządzeniu, potencjalnie w trakcie kilku sesji. Jeśli użytkownik korzysta z aplikacji na więcej niż 1 urządzeniu w ciągu 1 dnia, każde urządzenie zwiększy liczbę aktywnych użytkowników w danym dniu. Jeśli z tego samego urządzenia korzysta wielu użytkowników w ciągu 1 dnia, jest liczony jako 1 aktywny użytkownik.

Częstotliwość awarii widocznych dla użytkowników to podstawowe znaczenie, co oznacza, że wpływa na wykrywalność Twojej aplikacji w Google Play. Jest to ważne, ponieważ powoduje awarie mają miejsce zawsze wtedy, gdy użytkownik korzysta z aplikacji, nie jest zakłócona.

W Google Play obowiązują 2 progi niewłaściwego działania dotyczące tych danych:

  • Ogólny próg niewłaściwego działania: co najmniej 1,09% aktywnych użytkowników dziennie. może nastąpić awaria widoczna dla użytkowników na wszystkich modelach urządzeń.
  • Próg niewłaściwego działania na urządzenie: co najmniej 8% aktywnych użytkowników dziennie. wystąpiła awaria widoczna dla użytkowników na 1 modelu urządzenia.

Jeśli aplikacja przekracza ogólny próg niewłaściwego działania, prawdopodobnie trudniejsze do znalezienia na wszystkich urządzeniach. Jeśli aplikacja przekracza niewłaściwe działanie na konkretnych urządzeniach na pewnych urządzeniach może być trudniej znaleźć na tych urządzeniach, i na stronie z informacjami o aplikacji może pojawić się ostrzeżenie.

Android Vitals może Cię ostrzec za pomocą Konsola Play gdy występują liczne awarie aplikacji.

Informacje o tym, jak Google Play zbiera dane Android Vitals, znajdziesz w Konsola Play dokumentacji.

Zdiagnozuj awarie

Gdy ustalisz, że aplikacja zgłasza awarie, następnym krokiem jest diagnoza. Naprawianie awarii może być trudne. Jeśli jednak możesz ustalić główną przyczynę po awarii, prawdopodobnie znajdziesz rozwiązanie problemu.

Awarie aplikacji mogą wynikać z wielu sytuacji. Niektóre przyczyny: oczywiste, takie jak sprawdzanie wartości null lub pustego ciągu, ale inne są bardziej subtelne, np. przez przekazywanie nieprawidłowych argumentów do interfejsu API, czy nawet złożonych wielowątkowości interakcje.

Awarie na Androidzie powodują utworzenie zrzutu stosu, które jest zrzutem sekwencji zagnieżdżone funkcje wywoływane w programie do momentu awarii. Dostępne opcje wyświetl zrzuty stosu awarii w Android Vitals.

Jak odczytywać zrzut stosu

Pierwszym krokiem do naprawienia awarii jest wskazanie miejsca, w którym do niej doszło. Dostępne opcje jeśli korzystasz z Google Play, użyj zrzutu stosu dostępnego w szczegółach raportu. Konsola lub dane wyjściowe narzędzia logcat. Jeśli nie dostępny jest zrzut stosu, musisz odtworzyć lokalnie błąd, ręcznie testując aplikację lub kontaktując się z użytkownikami, których dotyczy problem, w celu jego odtworzenia za pomocą narzędzia logcat.

Poniższy ślad pokazuje przykład awarii w aplikacji napisanej w języku Java język programowania:

--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.developer.crashsample, PID: 3686
java.lang.NullPointerException: crash sample
at com.android.developer.crashsample.MainActivity$1.onClick(MainActivity.java:27)
at android.view.View.performClick(View.java:6134)
at android.view.View$PerformClick.run(View.java:23965)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6440)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:746)
--------- beginning of system

Zrzut stosu pokazuje 2 rodzaje informacji, które są kluczowe dla debugowania awaria:

  • Typ zgłoszonego wyjątku.
  • Sekcja kodu, w której zgłaszany jest wyjątek.

Rodzaj zgłoszenia wyjątku jest zwykle bardzo mocną wskazówką, co powinno się stać źle. Sprawdź, czy IOException, OutOfMemoryError, lub innego elementu, a potem znajdź dokumentację klasy wyjątku.

Klasa, metoda, plik i numer wiersza pliku źródłowego, których dotyczy wyjątek jest widoczny w drugim wierszu zrzutu stosu. Dla każdej funkcji, która , kolejny wiersz pokazuje poprzedni wiersz wywołania (nazywany ramką stosu). Przechodząc na wyższy poziom i przeglądając kod, możesz znaleźć miejsce, które przekazują nieprawidłową wartość. Jeśli Twojego kodu nie ma w zrzucie stosu, prawdopodobnie gdzieś został przekazany nieprawidłowy parametr do . Często można się dowiedzieć, co się stało, analizując każdy wiersz zrzut stosu, znajdowanie użytych klas interfejsów API i potwierdzenie, że przesłane parametry były prawidłowe i zostały wywołane z miejsca, jest dozwolona.

Zrzuty stosu aplikacji z kodami w C i C++ działają mniej więcej tak samo.

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/foo/bar:10/123.456/78910:user/release-keys'
ABI: 'arm64'
Timestamp: 2020-02-16 11:16:31+0100
pid: 8288, tid: 8288, name: com.example.testapp  >>> com.example.testapp <<<
uid: 1010332
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Cause: null pointer dereference
    x0  0000007da81396c0  x1  0000007fc91522d4  x2  0000000000000001  x3  000000000000206e
    x4  0000007da8087000  x5  0000007fc9152310  x6  0000007d209c6c68  x7  0000007da8087000
    x8  0000000000000000  x9  0000007cba01b660  x10 0000000000430000  x11 0000007d80000000
    x12 0000000000000060  x13 0000000023fafc10  x14 0000000000000006  x15 ffffffffffffffff
    x16 0000007cba01b618  x17 0000007da44c88c0  x18 0000007da943c000  x19 0000007da8087000
    x20 0000000000000000  x21 0000007da8087000  x22 0000007fc9152540  x23 0000007d17982d6b
    x24 0000000000000004  x25 0000007da823c020  x26 0000007da80870b0  x27 0000000000000001
    x28 0000007fc91522d0  x29 0000007fc91522a0
    sp  0000007fc9152290  lr  0000007d22d4e354  pc  0000007cba01b640

backtrace:
  #00  pc 0000000000042f89  /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::Crasher::crash() const)
  #01  pc 0000000000000640  /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::runCrashThread())
  #02  pc 0000000000065a3b  /system/lib/libc.so (__pthread_start(void*))
  #03  pc 000000000001e4fd  /system/lib/libc.so (__start_thread)

Jeśli w natywnych zrzutach stosu nie widzisz informacji na poziomie klasy i funkcji, może być konieczne wygeneruj plik symboli do debugowania kodu natywnego i prześlij ją do Konsoli Google Play. Więcej informacji: Usuwanie zaciemnienia kodu w zrzutach stosu awarii. Ogólne informacje o awariach natywnych znajdziesz w artykule Diagnozuj awarie reklam natywnych

Wskazówki dotyczące odtwarzania wypadku

Może się zdarzyć, że nie uda się w pełni odtworzyć problemu, uruchamiając za pomocą emulatora lub podłączania urządzenia do komputera. Środowiska programistyczne mają zwykle więcej zasobów, takich jak przepustowość, pamięć i miejsce na dane. Użyj typu wyjątku w celu określenia, jakiego rodzaju zasobów może brakować, określić związek między wersją Androida, typem urządzenia a wersji.

Błędy pamięci

Jeśli posiadasz OutOfMemoryError możesz utworzyć emulator o małej pojemności pamięci, na którym przeprowadzisz testy. Ilustracja 2 pokazuje ustawienia menedżera AVD, w których można kontrolować ilość pamięci urządzenia.

Ustawienie pamięci w menedżerze AVD

Rysunek 2. Ustawienie pamięci w menedżerze AVD

Wyjątki sieci

Użytkownicy często przemieszczają się poza zasięg sieci komórkowej lub Wi-Fi, wyjątki sieci aplikacji zwykle nie powinny być traktowane jako błędy, ale w zwykłych warunkach działania, które pojawiają się nieoczekiwanie.

Jeśli chcesz odtworzyć wyjątek sieci, taki jak UnknownHostException a następnie włącz tryb samolotowy, a aplikacja spróbuje użyć

Inną możliwością jest zmniejszenie jakości sieci w emulatorze przez wybór emulacji szybkości sieci lub opóźnienia sieci. Za pomocą Ustawienia Szybkość i Czas oczekiwania w menedżerze AVD lub możesz uruchomić emulator z flagami -netdelay i -netspeed, jak pokazano poniżej przykład wiersza poleceń:

emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm

W tym przykładzie ustawiane jest 20-sekundowe opóźnienie w przypadku wszystkich żądań sieciowych i przesyłania a szybkość pobierania 14,4 kb/s. Więcej informacji o opcjach wiersza poleceń emulatora, zobacz Uruchom emulator z wiersza poleceń.

Czytanie z logcat

Gdy już będziesz mieć sposoby na odtworzenie awarii, możesz skorzystać z narzędzia takiego jak logcat, aby uzyskać więcej informacji.

W danych wyjściowych logcat zobaczysz, jakie inne komunikaty dziennika zostały wydrukowane, oraz inne dane w systemie. Nie zapomnij wyłączyć dodatkowych funkcji Log – oświadczenia, zostały dodane, bo ich drukowanie zużywa procesor i baterię, gdy aplikacja jest w domu.

Zapobiegaj awariom spowodowanym przez wyjątki wskaźnika null

Wyjątki wskaźnika o wartości null (określane na podstawie typu błędu środowiska wykonawczego) NullPointerException) występują, gdy próbujesz uzyskać dostęp do obiektu, który null, zazwyczaj przez wywoływanie jego metod lub uzyskiwanie dostępu do elementów. Wskaźnik pusty to najczęstsza przyczyna awarii aplikacji w Google Play. Cel null oznacza brak obiektu – na przykład utworzone lub przypisane. Aby uniknąć wyjątków od wskaźnika null, musisz upewnić się, do których odwołuje się Twój obiekt, nie mają wartości null przed wywołaniem lub próby uzyskania do nich dostępu. Jeśli odwołanie do obiektu to null, należy traktować to dobrze (np. wyjście z metody przed wykonaniem wykonywać wszystkie operacje na odwołaniach do obiektu i zapisywać informacje w dzienniku debugowania).

Ponieważ nie chcesz sprawdzać wartości null dla każdego parametru i każdej metody możesz polegać na IDE lub typie obiektu dopuszczalna wartość null.

Język programowania Java

Sekcje poniżej dotyczą języka programowania Java.

Ostrzeżenia dotyczące czasu kompilacji

Dodaj adnotacje do metod i zwracają wartości z argumentem @Nullable i @NonNull, aby otrzymać czas kompilacji z IDE. Te ostrzeżenia informują, że należy spodziewać się obiektu z możliwością wartości null:

Puste ostrzeżenie o wyjątku wskaźnika

Te kontrole o wartości null dotyczą obiektów, o których wiesz, że mogą mieć wartość null. Wyjątek Obiekt @NonNull wskazuje na błąd w kodzie, który musi zostać oraz je wyeliminować.

Błędy podczas kompilacji

Ponieważ wartość null powinna być istotna, możesz umieścić ją w używanych przez Ciebie typach dzięki czemu istnieje kontrola czasu kompilacji na wartość null. Jeśli wiesz, że obiekt może być i że możliwość null powinna być obsługiwana, możesz ją ująć w obiekt Optional Należy zawsze preferować typy, które przekazują wartość null.

Kotlin

W Kotlin dopuszczalność wartości null jest częścią systemu typów. Na przykład zmienna musi być zadeklarowana z może mieć wartość null lub nie. Typy, które mogą być wartości null, są oznaczone za pomocą ?:

// non-null
var s: String = "Hello"

// null
var s: String? = "Hello"

Zmiennym niedopuszczającym wartości null nie można przypisać wartości null ani zmiennych dopuszczonych do wartości null trzeba sprawdzić pod kątem możliwości null, zanim będzie używany jako inny niż null.

Jeśli nie chcesz wprost sprawdzać wartości null, możesz użyć funkcji bezpiecznego wywołania ?. :

val length: Int? = string?.length  // length is a nullable int
                                   // if string is null, then length is null

Zalecaną metodą jest rozwiązanie problemu z wartością null dla obiektu z możliwością wartości null, lub może pojawić się nieoczekiwany stan. Jeśli aplikacja nie ulega awarii z usługą NullPointerException, nie dowiesz się o ich istnieniu.

Oto kilka sposobów na sprawdzenie tej wartości:

  • if kontroli

    val length = if(string != null) string.length else 0
    

    Dzięki funkcji smartcast i kontroli zerowej kompilator Kotlin wie, że ma wartość różną od null, więc pozwala na bezpośrednie użycie odwołania, bez konieczności korzystania z operatora bezpiecznego połączenia.

  • ?: Operator Elvis

    Ten operator umożliwia zadeklarowanie, że „jeśli obiekt nie ma wartości null, zwróć obiektu; w przeciwnym razie zwróć coś innego”.

    val length = string?.length ?: 0
    

Nadal możesz uzyskać NullPointerException w Kotlin. Oto najczęstsze z nich typowe sytuacje:

  • Kiedy wyraźnie rzucasz NullPointerException.
  • Jeśli używasz tagu operator asercji null !!. Ten operator konwertuje dowolną wartość na typ niepusty, powodując rzutowanie NullPointerException, jeśli wartość jest pusta.
  • Gdy uzyskujesz dostęp do pustego odwołania danego typu platformy.

Typy platform

Typy platform to deklaracje obiektów pochodzące z Javy. Trzeba nad nimi zapracować. Testy o wartości null nie są tak egzekwowane, więc gwarancja niepusta jest taka sama jak w Java. Gdy uzyskujesz dostęp do odwołania do typu platformy, Kotlin nie tworzy kompilacji ale mogą one powodować błędy podczas działania. Zobacz poniższe informacje przykład z dokumentacji Kotlin:

val list = ArrayList<String>() // non-null (constructor result) list.add("Item")
val size = list.size // non-null (primitive int) val item = list[0] // platform
type inferred (ordinary Java object) item.substring(1) // allowed, may throw an
                                                       // exception if item == null

Kotlin opiera się na wnioskowaniu typu, gdy wartość platformy jest przypisana do Kotlina możesz też określić oczekiwany typ. Najlepszy sposób, by zapewnić prawidłowy stan dopuszczalności null w pliku referencyjnym pochodzącym z Javy to użycie dopuszczalności null adnotacji (np. @Nullable) w kodzie Java. Kompilator Kotlin będą reprezentować te odwołania jako rzeczywiste typy z możliwością wartości null lub niedopuszczające wartości null, a nie jako typów platform.

Do interfejsów API Java Jetpack w razie potrzeby dodano adnotacje @Nullable lub @NonNull. Podobne podejście przyjęto Pakiet SDK do Androida 11. Typy pochodzące z tego pakietu SDK używane w kotlinie będą reprezentowane jako poprawnych lub niedopuszczających wartości null lub niedopuszczających wartości null.

Ze względu na system typów w kotlinie zauważyliśmy, że w przypadku aplikacji nastąpił znaczny spadek NullPointerException awarii. Na przykład aplikacja Google Home uzyskała 30% ograniczenie liczby awarii spowodowanych przez wyjątki od wskaźnika zerowego w roku, w którym przeprowadziliśmy migrację nowych funkcji do Kotlin.