Błędy ANR

Gdy wątek interfejsu aplikacji na Androida jest zablokowany zbyt długo, pojawia się błąd „Aplikacja nie odpowiada” (ANR). Jeśli aplikacja działa na pierwszym planie, system wyświetla użytkownikowi okno, tak jak to pokazano na rysunku 1. Okno ANR umożliwia użytkownikowi wymuszenie zamknięcia aplikacji.

Rysunek 1. Okno ANR zostało wyświetlone użytkownikowi

Rysunek 1. Okno ANR zostało wyświetlone użytkownikowi

Błędy ANR to problem, ponieważ główny wątek aplikacji, który odpowiada za aktualizację UI, nie może przetwarzać zdarzeń wejściowych użytkownika ani rysować, co jest frustrujące użytkownika. Więcej informacji na temat głównego wątku aplikacji znajdziesz w sekcji Procesy i wątki.

Błąd ANR jest wywoływany w przypadku aplikacji, gdy wystąpi jeden z tych warunków:

  • Upłynął limit czasu wysyłania danych wejściowych: jeśli aplikacja nie odpowiedziała na zdarzenie wejściowe (takie jak naciśnięcie klawisza lub dotknięcie ekranu) w ciągu 5 sekund.
  • Wykonywanie usługi: jeśli usługa zadeklarowana przez aplikację nie może ukończyć wykonywania Service.onCreate() i Service.onStartCommand()/Service.onBind() w ciągu kilku sekund.
  • Nie wywołano funkcji Service.startForeground(): jeśli aplikacja używa metody Context.startForegroundService() do uruchomienia nowej usługi na pierwszym planie, ale nie wywołuje ona startForeground() w ciągu 5 sekund.
  • Ogłoszenie intencji: jeśli działanie BroadcastReceiver nie zakończy się w ustalonym czasie. Jeśli aplikacja jest wykonywana na pierwszym planie, ten czas oczekiwania wynosi 5 sekund.
  • Interakcje z JobScheduler: jeśli element JobService nie wróci z JobService.onStartJob() lub JobService.onStopJob() w ciągu kilku sekund lub jeśli rozpocznie się zadanie inicjowane przez użytkownika, a aplikacja nie wywoła metody JobService.setNotification() w ciągu kilku sekund po wywołaniu funkcji JobService.onStartJob(). W przypadku aplikacji kierowanych na Androida 13 i starsze błędy ANR są ciche i nie są zgłaszane aplikacji. W przypadku aplikacji kierowanych na Androida 14 lub nowszego błędy ANR są wyraźne i są zgłaszane do aplikacji.

Jeśli w aplikacji występują błędy ANR, skorzystaj ze wskazówek podanych w tym artykule, aby zdiagnozować i rozwiązać problem.

Wykryj problem

Jeśli masz już opublikowaną aplikację, możesz użyć Android Vitals, aby wyświetlić informacje o błędach ANR. Możesz używać innych narzędzi do wykrywania błędów ANR w terenie. Pamiętaj jednak, że w przeciwieństwie do Android Vitals narzędzia innych firm nie mogą zgłaszać błędów ANR w starszych wersjach Androida (Android 10 i starsze).

Android Vitals

Android Vitals pomaga monitorować i poprawiać częstotliwość błędów ANR w aplikacji. Android Vitals mierzy kilka częstotliwości błędów ANR:

  • Częstotliwość błędów ANR: odsetek aktywnych użytkowników dziennie, u których wystąpił dowolny typ błędu ANR.
  • Częstotliwość błędów ANR widocznych dla użytkowników: odsetek aktywnych użytkowników dziennie, u których wystąpił co najmniej 1 widoczny dla nich błąd ANR. Obecnie tylko błędy ANR typu Input dispatching timed out są uważane za widoczne dla użytkowników.
  • Częstotliwość wielokrotnych błędów ANR: odsetek aktywnych użytkowników dziennie, u których wystąpiły co najmniej 2 błędy ANR.

Aktywny użytkownik dziennie to unikalny użytkownik, który korzysta z Twojej aplikacji w ciągu 1 dnia na 1 urządzeniu, a potem w ramach wielu sesji. Jeśli użytkownik korzysta z aplikacji na więcej niż 1 urządzeniu w ciągu 1 dnia, każde z tych urządzeń będzie miało wpływ na liczbę aktywnych użytkowników w danym dniu. Jeśli wielu użytkowników korzysta z tego samego urządzenia w ciągu 1 dnia, jest to liczone jako 1 aktywny użytkownik.

Częstotliwość błędów ANR widocznych dla użytkowników jest podstawowym wskaźnikiem, co oznacza, że wpływa na możliwość odkrycia Twojej aplikacji w Google Play. To ważne, ponieważ zliczane błędy ANR występują zawsze, gdy użytkownik korzysta z aplikacji, co powoduje największe zakłócenia.

W przypadku tych danych Google Play ma 2 progi niewłaściwego działania:

  • Ogólny próg niewłaściwego działania: widoczny błąd ANR wystąpił u co najmniej 0, 47% aktywnych użytkowników dziennie na wszystkich modelach urządzeń.
  • Próg niewłaściwego działania na urządzenie: widoczny błąd ANR wystąpił u co najmniej 8% użytkowników dziennie na 1 modelu urządzenia.

Jeśli Twoja aplikacja przekracza ogólny próg niewłaściwego działania, prawdopodobnie będzie trudniejsza do odkrycia na wszystkich urządzeniach. Jeśli Twoja aplikacja przekracza próg niewłaściwego działania na niektórych urządzeniach, może być na nich trudniejsza do odkrycia, a na jej stronie w Sklepie może wyświetlać się ostrzeżenie.

Android Vitals może powiadamiać Cię w Konsoli Play o nadmiernych błędach ANR w aplikacji.

Informacje o tym, jak Google Play gromadzi dane Android Vitals, znajdziesz w dokumentacji Konsoli Play.

Diagnozuj błędy ANR

Podczas diagnozowania błędów ANR należy wziąć pod uwagę kilka typowych wzorców:

  • Aplikacja wykonuje powolne operacje, w tym wejścia/wyjścia w wątku głównym.
  • Aplikacja wykonuje długie obliczenia w wątku głównym.
  • Wątek główny wykonuje synchroniczne wywołanie powiązania z innym procesem, a ich realizacja zajmuje dużo czasu.
  • Wątek główny jest blokowany podczas oczekiwania na zsynchronizowany blok dla długiej operacji trwającej w innym wątku.
  • Wątek główny jest w zakleszczeniu z innym wątkiem w Twoim procesie lub przez wywołanie binder. Wątek główny nie tylko czeka na zakończenie długiej operacji, ale też jest w zakleszczeniu. Więcej informacji znajdziesz w artykule Zakleszczenie w Wikipedii.

Poniższe techniki mogą pomóc w określeniu przyczyny błędów ANR.

Statystyki zdrowotne

HealthStats udostępnia dane o stanie aplikacji, rejestrując łączny czas użytkownika i systemu, czas pracy procesora, sieć, statystyki radia, czas włączania i wyłączania ekranu oraz budziki. Pomaga to zmierzyć ogólne wykorzystanie procesora i wykorzystanie baterii.

Debuguj

Debug pomaga kontrolować aplikacje na Androida w trakcie tworzenia aplikacji, w tym śledzenie i liczbę przydziałów w celu wykrywania zacięć i opóźnień w ich działaniu. Możesz też użyć funkcji Debug, aby uzyskać liczniki środowiska wykonawczego i pamięci natywnej, a także wskaźniki pamięci, które pomogą Ci określić wykorzystanie pamięci przez dany proces.

ApplicationExitInfo

Narzędzie ApplicationExitInfo jest dostępne na urządzeniach z Androidem 11 (poziom interfejsu API 30) i nowszym i zawiera informacje o przyczynie zamknięcia aplikacji. Obejmuje to błędy ANR, małą ilość pamięci, awarie aplikacji, nadmierne użycie procesora, przerwy użytkowników, przerwy w systemie i zmiany uprawnień w czasie działania.

Tryb ścisły

Korzystanie z funkcji StrictMode pomaga znajdować przypadkowe operacje wejścia-wyjścia w wątku głównym podczas programowania aplikacji. StrictMode możesz używać na poziomie aplikacji lub działania.

Włącz okna ANR w tle

W przypadku aplikacji, których przetworzenie zajmuje zbyt dużo czasu, Android wyświetla okna błędów ANR tylko wtedy, gdy w Opcjach programisty na urządzeniu włączona jest opcja Pokaż wszystkie błędy ANR. Z tego powodu okna błędów ANR w tle nie zawsze są widoczne, ale aplikacja nadal może mieć problemy z wydajnością.

Widok logu czasu

Możesz użyć widoku śledzenia, aby prześledzić działanie uruchomionej aplikacji podczas analizy przypadków użycia i zidentyfikować miejsca, w których jest zajęty wątek główny. Informacje o korzystaniu z widoku śledzenia znajdziesz w artykule o profilowaniu z użyciem danych Traceview i dmtracedump.

Pobieranie pliku śledzenia

Gdy wystąpi błąd ANR, Android przechowuje informacje z śladu. W starszych wersjach systemu operacyjnego urządzenie zawiera 1 plik /data/anr/traces.txt. W nowszych wersjach systemu operacyjnego znajduje się wiele plików /data/anr/anr_*. Dostęp do śladów błędów ANR możesz uzyskać z urządzenia lub emulatora, używając narzędzia Android Debug Bridge (adb) jako root:

adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>

Raport o błędzie możesz przechwycić na urządzeniu fizycznym, korzystając z opcji tworzenia raportów o błędach dla programistów lub polecenia adb bugreport na komputerze. Więcej informacji znajdziesz w artykule o rejestrowaniu i odczytywaniu raportów o błędach.

Rozwiąż problemy

Po zidentyfikowaniu problemu możesz skorzystać ze wskazówek w tej sekcji, aby rozwiązać często występujące problemy.

Powolny kod w wątku głównym

Znajdź miejsca w kodzie, w których główny wątek aplikacji jest zajęty przez ponad 5 sekund. Poszukaj podejrzanych przypadków użycia w aplikacji i spróbuj odtworzyć błąd ANR.

Na przykład rys. 2 przedstawia oś czasu widoku śledzenia, na której wątek główny jest zajęty przez ponad 5 sekund.

Rysunek 2. Oś czasu w widoku logu pokazująca zajęty wątek główny

Rysunek 2. Oś czasu w widoku śledzenia pokazująca zajęty wątek główny

Rysunek 2 pokazuje, że większość nieprawidłowego kodu występuje w module obsługi onClick(View), jak widać w tym przykładowym kodzie:

Kotlin

override fun onClick(v: View) {
    // This task runs on the main thread.
    BubbleSort.sort(data)
}

Java

@Override
public void onClick(View view) {
    // This task runs on the main thread.
    BubbleSort.sort(data);
}

W tym przypadku przenieś pracę uruchamianą w wątku głównym do wątku roboczego. Android Framework zawiera klasy, które pomagają przenieść zadanie do wątku roboczego. Więcej informacji znajdziesz w sekcji Wątki instancji roboczych.

IO w wątku głównym

Wykonywanie operacji wejścia-wyjścia w wątku głównym jest częstą przyczyną powolnych operacji w wątku głównym, które mogą powodować błędy ANR. Zalecamy przeniesienie wszystkich operacji wejścia-wyjścia do wątku roboczego, jak pokazano w poprzedniej sekcji.

Przykładami operacji wejścia-wyjścia są operacje związane z siecią i pamięcią masową. Więcej informacji znajdziesz w artykułach Wykonywanie operacji sieciowych i Zapisywanie danych.

Rywalizacja o blokadę

W niektórych sytuacjach praca, która powoduje błąd ANR, nie jest wykonywana bezpośrednio w głównym wątku aplikacji. Jeśli wątek roboczy blokuje zasób, którego wymaga wątek główny do zakończenia działania, może wystąpić błąd ANR.

Na przykład rysunek 4 przedstawia oś czasu w widoku logu czasu, na której większość pracy jest wykonywana w wątku instancji roboczej.

Rysunek 4. Oś czasu w widoku śledzenia, która pokazuje pracę wykonywaną w wątku instancji roboczej

Rysunek 4. Oś czasu w widoku śledzenia, która pokazuje pracę wykonywaną w wątku instancji roboczej

Jeśli jednak u użytkowników nadal występują błędy ANR, sprawdź stan wątku głównego w narzędziu Android Device Monitor. Zwykle wątek główny ma stan RUNNABLE, jeśli jest gotowy do zaktualizowania interfejsu i zwykle reaguje.

Jeśli jednak wątek główny nie może wznowić wykonywania, jest w stanie BLOCKED i nie może odpowiadać na zdarzenia. W Monitorze urządzeń Android stan wyświetla się jako Monitoruj lub Zaczekaj, jak widać na ilustracji 5.

Rysunek 5. Wątek główny w stanie monitorowania

Rysunek 5. Wątek główny w stanie monitorowania

Poniższy log czasu pokazuje główny wątek aplikacji, który został zablokowany oczekiwanie na zasób:

...
AsyncTask #2" prio=5 tid=18 Runnable
  | group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
  | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
  | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
  | stack=0x94a7e000-0x94a80000 stackSize=1038KB
  | held mutexes= "mutator lock"(shared held)
  at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
  - locked <0x083105ee> (a java.lang.Boolean)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
  at android.os.AsyncTask$2.call(AsyncTask.java:305)
  at java.util.concurrent.FutureTask.run(FutureTask.java:237)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
  at java.lang.Thread.run(Thread.java:761)
...

Sprawdzenie logu czasu może pomóc Ci znaleźć kod, który blokuje wątek główny. Ten kod odpowiada za utrzymanie blokady, która blokuje wątek główny w poprzednim logu czasu:

Kotlin

override fun onClick(v: View) {
    // The worker thread holds a lock on lockedResource
    LockTask().execute(data)

    synchronized(lockedResource) {
        // The main thread requires lockedResource here
        // but it has to wait until LockTask finishes using it.
    }
}

class LockTask : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? =
            synchronized(lockedResource) {
                // This is a long-running operation, which makes
                // the lock last for a long time
                BubbleSort.sort(params[0])
            }
}

Java

@Override
public void onClick(View v) {
    // The worker thread holds a lock on lockedResource
   new LockTask().execute(data);

   synchronized (lockedResource) {
       // The main thread requires lockedResource here
       // but it has to wait until LockTask finishes using it.
   }
}

public class LockTask extends AsyncTask<Integer[], Integer, Long> {
   @Override
   protected Long doInBackground(Integer[]... params) {
       synchronized (lockedResource) {
           // This is a long-running operation, which makes
           // the lock last for a long time
           BubbleSort.sort(params[0]);
       }
   }
}

Innym przykładem jest główny wątek aplikacji, który czeka na wynik z wątku roboczego, jak pokazano w poniższym kodzie. Pamiętaj, że używanie wait() i notify() nie jest zalecanym wzorcem w usłudze Kotlin, która ma własne mechanizmy obsługi równoczesności. Gdy używasz Kotlin, używaj w miarę możliwości mechanizmów charakterystycznych dla tego języka.

Kotlin

fun onClick(v: View) {
    val lock = java.lang.Object()
    val waitTask = WaitTask(lock)
    synchronized(lock) {
        try {
            waitTask.execute(data)
            // Wait for this worker thread’s notification
            lock.wait()
        } catch (e: InterruptedException) {
        }
    }
}

internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? {
        synchronized(lock) {
            BubbleSort.sort(params[0])
            // Finished, notify the main thread
            lock.notify()
        }
    }
}

Java

public void onClick(View v) {
   WaitTask waitTask = new WaitTask();
   synchronized (waitTask) {
       try {
           waitTask.execute(data);
           // Wait for this worker thread’s notification
           waitTask.wait();
       } catch (InterruptedException e) {}
   }
}

class WaitTask extends AsyncTask<Integer[], Integer, Long> {
   @Override
   protected Long doInBackground(Integer[]... params) {
       synchronized (this) {
           BubbleSort.sort(params[0]);
           // Finished, notify the main thread
           notify();
       }
   }
}

Wątek główny może być zablokowany w innych sytuacjach, np. w przypadku wątków korzystających z Lock i Semaphore, a także puli zasobów (np. puli połączeń z bazą danych) lub innych mechanizmów wykluczania (muteks).

Warto sprawdzić blokady stosowane przez aplikację w odniesieniu do zasobów, ale jeśli chcesz uniknąć błędów ANR, sprawdź blokady nałożone na zasoby wymagane przez wątek główny.

Upewnij się, że blokady są utrzymywane przez krótszy czas, a nawet lepiej – zastanów się, czy w ogóle aplikacja ich nie wymaga. Jeśli używasz blokady do określania, kiedy zaktualizować interfejs na podstawie przetwarzania wątku roboczego, używaj mechanizmów takich jak onProgressUpdate() i onPostExecute() do komunikacji między wątkami roboczymi a wątkami głównymi.

Zakleszczenie

Zakleszczenie ma miejsce, gdy wątek przechodzi w stan oczekiwania, ponieważ wymagany zasób jest przechowywany przez inny wątek, który również oczekuje na zasób przechowywany przez pierwszy wątek. Jeśli w takiej sytuacji znajduje się główny wątek aplikacji, błędy ANR są bardzo prawdopodobne.

Zakleszczenie to zjawisko dobrze zbadane w informatyce. Istnieją algorytmy zapobiegające zakleszczeniu, których można użyć, aby uniknąć zakleszczeń.

Więcej informacji znajdziesz w Wikipedii: algorytmy zapobiegające zakleszczeniu i algorytmy zapobiegające zakleszczeniu.

Wolne odbiorniki

Aplikacje mogą reagować na komunikaty przy użyciu odbiorników, np. włączać lub wyłączać tryb samolotowy czy zmieniać stan połączenia. Błąd ANR występuje, gdy przetwarzanie komunikatu trwa zbyt długo.

Błędy ANR występują w tych przypadkach:

Aplikacja powinna wykonywać tylko krótkie operacje w metodzie onReceive() BroadcastReceiver. Jeśli jednak w efekcie przesyłanego komunikatu Twoja aplikacja wymaga bardziej złożonego przetwarzania, odłóż to zadanie do IntentService.

Możesz użyć narzędzi takich jak Traceview, aby określić, czy odbiornik wykonuje długotrwałe operacje w głównym wątku aplikacji. Na przykład rys. 6 przedstawia oś czasu odbiornika, który przetwarza wiadomość w wątku głównym przez około 100 sekund.

Rysunek 6. Oś czasu w widoku śledzenia pokazująca działanie funkcji „BroadcastReceivedr” w wątku głównym

Rysunek 6. Oś czasu w widoku śledzenia pokazująca zadania BroadcastReceiver w wątku głównym

Takie zachowanie może być spowodowane wykonywaniem długo trwających operacji na metodzie onReceive() obiektu BroadcastReceiver, jak pokazano w tym przykładzie:

Kotlin

override fun onReceive(context: Context, intent: Intent) {
    // This is a long-running operation
    BubbleSort.sort(data)
}

Java

@Override
public void onReceive(Context context, Intent intent) {
    // This is a long-running operation
    BubbleSort.sort(data);
}

W takich sytuacjach zalecamy przeniesienie długo trwającej operacji do IntentService, ponieważ do jej wykonywania służy wątek instancji roboczej. Ten kod pokazuje, jak użyć IntentService do przetworzenia długo trwającej operacji:

Kotlin

override fun onReceive(context: Context, intent: Intent) {
    Intent(context, MyIntentService::class.java).also { intentService ->
        // The task now runs on a worker thread.
        context.startService(intentService)
    }
}

class MyIntentService : IntentService("MyIntentService") {
    override fun onHandleIntent(intent: Intent?) {
        BubbleSort.sort(data)
    }
}

Java

@Override
public void onReceive(Context context, Intent intent) {
    // The task now runs on a worker thread.
    Intent intentService = new Intent(context, MyIntentService.class);
    context.startService(intentService);
}

public class MyIntentService extends IntentService {
   @Override
   protected void onHandleIntent(@Nullable Intent intent) {
       BubbleSort.sort(data);
   }
}

W wyniku użycia IntentService długo trwająca operacja jest wykonywana w wątku roboczym, a nie w wątku głównym. Rysunek 7 przedstawia pracę odroczoną do wątku roboczego na osi czasu widoku śledzenia.

Rysunek 7. Oś czasu w widoku śledzenia pokazująca wiadomość przetworzona
w wątku instancji roboczej

Rysunek 7. Oś czasu w widoku śledzenia pokazująca wiadomość przetworzona w wątku instancji roboczej

Odbiornik może użyć parametru goAsync(), aby zasygnalizować systemowi, że potrzebuje więcej czasu na przetworzenie wiadomości. Wywołuj jednak finish() w obiekcie PendingResult. Z przykładu poniżej dowiesz się, jak wywołać funkcję end(), aby umożliwić systemowi recykling odbiornika i uniknąć błędu ANR:

Kotlin

val pendingResult = goAsync()

object : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? {
        // This is a long-running operation
        BubbleSort.sort(params[0])
        pendingResult.finish()
        return 0L
    }
}.execute(data)

Java

final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
   @Override
   protected Long doInBackground(Integer[]... params) {
       // This is a long-running operation
       BubbleSort.sort(params[0]);
       pendingResult.finish();
   }
}.execute(data);

Jednak przeniesienie kodu z wolnego odbiornika do innego wątku i użycie interfejsu goAsync() nie naprawi błędu ANR, jeśli transmisja odbywa się w tle. Nadal obowiązuje limit czasu błędu ANR.

Aktywność w grach

W bibliotece GameActivity zmniejszyła się liczba błędów ANR w studiach przypadków gier i aplikacji napisanych w językach C lub C++. Zastąpienie dotychczasowej aktywności natywnej kodem GameActivity pozwala ograniczyć blokowanie wątków w interfejsie i zapobiec pojawianiu się niektórych błędów ANR.

Więcej informacji o błędach ANR znajdziesz w artykule Utrzymywanie elastyczności aplikacji. Więcej informacji o wątkach znajdziesz w artykule o wydajności wątków.