Pojęcia i implementacja Jetpack Compose
Gdy wątek UI 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, jak na rysunku 1. Okno ANR umożliwia użytkownikowi wymuszenie zamknięcia aplikacji.
Błędy ANR są problemem, ponieważ główny wątek aplikacji, który odpowiada za aktualizowanie interfejsu, nie może przetwarzać zdarzeń wejściowych użytkownika ani rysować, co powoduje frustrację użytkownika. Więcej informacji o głównym wątku aplikacji znajdziesz w omówieniu procesów i wątków.
Błąd ANR jest wywoływany w aplikacji, gdy wystąpi jeden z tych warunków:
- Przekroczenie limitu czasu wysyłania danych wejściowych: jeśli aplikacja nie odpowie na zdarzenie wejściowe (np. 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 zakończyć wykonywania metod
Service.onCreate()iService.onStartCommand()/Service.onBind()w ciągu kilku sekund. **Service.startForeground()nie wywołano**: jeśli aplikacja używaContext.startForegroundService()do uruchomienia nowej usługi na pierwszym planie, ale usługa nie wywołujestartForeground()w ciągu 5 sekund.- Rozgłaszanie intencji: jeśli
BroadcastReceivernie zakończy się w ustawionym czasie. Jeśli aplikacja jest aktywna na pierwszym planie, limit czasu wynosi 10 sekund. - **Interakcje z
**JobScheduler**: jeśliJobServicenie powróci zJobService.onStartJob()lubJobService.onStopJob()w ciągu kilku sekund albo jeśli rozpocznie się zadanie zainicjowane przez użytkownika, a aplikacja nie wywołaJobService.setNotification()w ciągu kilku sekund od wywołaniaJobService.onStartJob(). W przypadku aplikacji kierowanych na Androida 13 i starsze wersje błędy ANR są ciche i nie są zgłaszane aplikacji. W przypadku aplikacji kierowanych na Androida 14 i nowsze wersje błędy ANR są jawne i są zgłaszane aplikacji.
Jeśli w aplikacji występują błędy ANR, możesz skorzystać z informacji w tym artykule, aby zdiagnozować i rozwiązać problem.
Rozwiązywanie problemów
Po zidentyfikowaniu problemu możesz skorzystać ze wskazówek w tej sekcji, aby rozwiązać najczęstsze problemy.
Powolny kod w głównym wątku
Zidentyfikuj miejsca w kodzie, w których główny wątek aplikacji jest zajęty przez ponad 5 sekund. Poszukaj w aplikacji podejrzanych przypadków użycia i spróbuj odtworzyć błąd ANR.
Na przykład na rysunku 2 widać oś czasu Traceview, na której wątek główny jest zajęty przez ponad 5 sekund.

Rysunek 2. Oś czasu Traceview pokazująca zajęty wątek główny
Rysunek 2 pokazuje, że większość nieprawidłowego kodu występuje w procedurze obsługi onClick(View), jak pokazano w tym przykładzie kodu:
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 takim przypadku należy przenieść zadania wykonywane w wątku głównym do wątku instancji roboczej. Platforma Androida zawiera klasy, które mogą pomóc w przeniesieniu zadania do wątku instancji roboczej. Więcej informacji znajdziesz w sekcji Wątki instancji roboczej.
Wejście-wyjście w wątku głównym
Wykonywanie operacji wejścia-wyjścia w wątku głównym jest częstą przyczyną powolnego działania
wątku głównego, co może powodować błędy ANR. W Compose deweloperzy często przypadkowo wywołują odczyty dysku (np. SharedPreferences lub wywołania bazy danych) podczas próby uzyskania stanu początkowego.
Długo trwające operacje wejścia-wyjścia wykonuj poza warstwą interfejsu. Używaj withContext(Dispatchers.IO) w ViewModel lub, jeszcze lepiej, używaj repozytorium w warstwie danych. Zalecamy przeniesienie wszystkich operacji wejścia/wyjścia do wątku instancji roboczej, jak pokazano w poprzedniej sekcji.
Przykłady operacji wejścia-wyjścia to operacje sieciowe i operacje na pamięci masowej. Więcej informacji znajdziesz w artykułach Wykonywanie operacji sieciowych i Zapisywanie danych.
Rywalizacja o blokadę
W niektórych przypadkach zadanie, które powoduje błąd ANR, nie jest wykonywane bezpośrednio w głównym wątku aplikacji. Jeśli wątek instancji roboczej blokuje zasób, którego wątek główny potrzebuje do wykonania zadania, może wystąpić błąd ANR.
Na przykład na rysunku 3 widać oś czasu Traceview, na której większość pracy jest wykonywana w wątku instancji roboczej.

Rysunek 3. Oś czasu Traceview, która pokazuje pracę wykonywaną w wątku instancji roboczej
Jeśli jednak użytkownicy nadal napotykają błędy ANR, sprawdź stan głównego wątku w Android Device Monitor. Zwykle wątek główny jest w stanie RUNNABLE, jeśli jest gotowy do aktualizacji interfejsu i ogólnie reaguje na działania użytkownika.
Jeśli jednak wątek główny nie może wznowić wykonywania, jest w stanie BLOCKED i nie może odpowiadać na zdarzenia. Stan jest wyświetlany w Android Device Monitor jako Monitor (Monitoruj) lub Wait (Czekaj), jak pokazano na rysunku 5.

Rysunek 4. Wątek główny w stanie monitorowania
Poniższy log czasu pokazuje, że wątek główny aplikacji jest zablokowany i czeka 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)
...
Przejrzenie śladu może pomóc w zlokalizowaniu kodu, 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 wątek główny aplikacji, który czeka na wynik z wątku instancji roboczej, jak pokazano w poniższym kodzie. Pamiętaj, że używanie wait() i notify() nie jest zalecanym wzorcem w języku Kotlin, który ma własne mechanizmy obsługi równoczesności. Jeśli używasz Kotlina, w miarę możliwości korzystaj z mechanizmów specyficznych 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();
}
}
}
Istnieją inne sytuacje, które mogą blokować wątek główny, w tym wątki korzystające z Lock, Semaphore, a także puli zasobów (takiej jak pula połączeń z bazą danych) lub innych mechanizmów wzajemnego wykluczania (mutex).
Ogólnie rzecz biorąc, należy ocenić blokady zasobów, które są utrzymywane przez aplikację, ale jeśli chcesz uniknąć błędów ANR, sprawdź blokady zasobów wymaganych przez wątek główny.
Upewnij się, że blokady są utrzymywane przez jak najkrótszy czas, a najlepiej sprawdź, czy aplikacja w ogóle ich potrzebuje. Jeśli używasz blokady, aby określić, kiedy zaktualizować interfejs na podstawie przetwarzania wątku instancji roboczej, użyj mechanizmów takich jak onProgressUpdate() i onPostExecute() do komunikacji między instancją roboczą a głównymi wątkami.
Powolne odbiorniki
Aplikacje mogą reagować na komunikaty, takie jak włączenie lub wyłączenie trybu samolotowego albo zmiana stanu połączenia, za pomocą odbiorników komunikatów. Błąd ANR występuje, gdy aplikacja zbyt długo przetwarza komunikat.
Błąd ANR występuje w tych przypadkach:
- Odbiornik komunikatów nie zakończył wykonywania metody
onReceivew odpowiednim czasie. - Odbiornik wywołuje metodę
goAsync, ale nie wywołuje metodyfinishobiektuPendingResult.
Aplikacja powinna wykonywać tylko krótkie operacje w metodzie onReceive klasy BroadcastReceiver. Jeśli jednak aplikacja wymaga bardziej złożonego przetwarzania w wyniku otrzymania komunikatu, należy odroczyć zadanie do ViewModel (wykorzystując możliwości współprogramów, zakresów i dyspozytorów w Kotlin), jeśli zadanie ma potrwać co najwyżej kilka sekund, dowolnego typu zmiennej stanu lub do WorkManager w przypadku zadań, które mają potrwać dłużej niż kilka sekund.
Możesz użyć narzędzi takich jak Traceview, aby sprawdzić, czy odbiornik komunikatów wykonuje długo trwające operacje w głównym wątku aplikacji. Na przykład rysunek 6 przedstawia oś czasu odbiorcy komunikatów, który przetwarza wiadomość w głównym wątku przez około 100 sekund.

Rysunek 5. Oś czasu Traceview pokazująca pracę BroadcastReceiver w wątku głównym
Może to być spowodowane wykonywaniem długo trwających operacji w metodzie onReceive() interfejsu BroadcastReceiver, jak 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 wykonywania pracy używa on wątku instancji roboczej.
Poniższy kod pokazuje, jak używać IntentService do przetwarzania 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);
}
}
Dzięki użyciu IntentService długo trwająca operacja jest wykonywana w wątku instancji roboczej, a nie w wątku głównym. Rysunek 7 przedstawia pracę odroczoną do wątku instancji roboczej na osi czasu Traceview.

Rysunek 6. Oś czasu Traceview pokazująca komunikat przetworzony w wątku instancji roboczej
Odbiornik może użyć metody goAsync(), aby zasygnalizować systemowi, że potrzebuje więcej czasu na przetworzenie wiadomości. Należy jednak wywołać metodę finish() na obiekcie PendingResult. Poniższy przykład pokazuje, jak wywołać metodę finish(), aby umożliwić systemowi ponowne wykorzystanie 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 odbiorcy komunikatów do innego wątku i użycie goAsync() nie rozwiąże problemu z błędem ANR, jeśli komunikat jest w tle.
Limit czasu błędu ANR nadal obowiązuje.