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.
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()
iService.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 onastartForeground()
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 zJobService.onStartJob()
lubJobService.onStopJob()
w ciągu kilku sekund lub jeśli rozpocznie się zadanie inicjowane przez użytkownika, a aplikacja nie wywoła metodyJobService.setNotification()
w ciągu kilku sekund po wywołaniu funkcjiJobService.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 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
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.
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:
- Odbiornik nie zakończył wykonywania metody
onReceive()
w znacznym czasie. - Odbiornik wywołuje metodę
goAsync()
i nie wywołuje metodyfinish()
w obiekciePendingResult
.
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.
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.
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.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Zbyt częste wybudzenia