Koncepcje i implementacja w Jetpack Compose
Gdy wątek UI aplikacji na Androida jest zablokowany przez zbyt długi czas, wywoływany jest błąd „Aplikacja nie odpowiada” (ANR). Jeśli aplikacja jest na pierwszym planie, system wyświetla użytkownikowi okno, jak pokazano na ilustracji 1. Okno ANR daje użytkownikowi możliwość wymuszenia 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 artykule Omówienie 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 odpowiedziała 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
Service.onCreate()iService.onStartCommand()/Service.onBind()w ciągu kilku sekund. **Service.startForeground()nie jest wywoływana:** 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ł wykonywania w określonym czasie. Jeśli aplikacja ma aktywność na pierwszym planie, ten limit czasu wynosi 5 sekund. **JobSchedulerinterakcje**: jeśliJobServicenie zwróci wartoś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 po wywołaniuJobService.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 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ęściej spotykane problemy.
Powolny kod w wątku głównym
Zidentyfikuj 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 na ilustracji 2 widać oś czasu Traceview, na której główny wątek jest zajęty przez ponad 5 sekund.

Ilustracja 2. Oś czasu Traceview pokazująca zajęty wątek główny
Ilustracja 2 pokazuje, że większość problematycznego kodu znajduje się w obsłudze
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ść pracę wykonywaną w wątku głównym do wątku roboczego. Android Framework zawiera klasy, które mogą pomóc w przeniesieniu zadania do wątku instancji roboczej. Więcej informacji znajdziesz w artykule Wątki robocze.
Wejście-wyjście 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, co może powodować błędy ANR. W Compose programiści często przypadkowo wywołują odczyty z dysku (np. SharedPreferences lub wywołania bazy danych) podczas próby uzyskania stanu początkowego.
Długotrwałe operacje wejścia-wyjścia wykonuj poza warstwą interfejsu. Użyj
withContext(Dispatchers.IO) w ViewModel lub, co jeszcze lepsze, użyj repozytorium
w warstwie danych. Zalecamy przeniesienie wszystkich operacji wejścia-wyjścia do wątku roboczego, jak pokazano w poprzedniej sekcji.
Przykłady operacji wejścia-wyjścia to operacje sieciowe i operacje na pamięci. Więcej informacji znajdziesz w artykułach Wykonywanie operacji sieciowych i Zapisywanie danych.
Rywalizacja o blokadę
W niektórych przypadkach praca powodująca błąd ANR nie jest wykonywana bezpośrednio w głównym wątku aplikacji. Jeśli wątek roboczy ma blokadę zasobu, którego główny wątek potrzebuje do wykonania swojej pracy, może wystąpić błąd ANR.
Na przykład na ilustracji 3 widać oś czasu Traceview, na której większość pracy jest wykonywana w wątku instancji roboczej.

Ilustracja 3. Oś czasu Traceview pokazująca pracę wykonywaną w wątku roboczym
Jeśli jednak użytkownicy nadal napotykają błędy ANR, sprawdź stan głównego wątku w Android Device Monitor. Zwykle główny wątek jest w stanie
RUNNABLE, jeśli jest gotowy do aktualizacji interfejsu i ogólnie
reaguje.
Jeśli jednak główny wątek nie może wznowić wykonywania, jest w stanie BLOCKED
i nie może odpowiadać na zdarzenia. Stan ten jest wyświetlany w Android Device Monitor jako
Monitor lub Wait, jak pokazano na ilustracji 5.

Ilustracja 4. Główny wątek w stanie Monitor
Ten ślad pokazuje główny wątek aplikacji, który 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)
...
Przeanalizowanie śladu może pomóc w znalezieniu kodu, który blokuje główny wątek. Ten kod odpowiada za blokowanie głównego wątku w poprzednim śladzie:
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 instancji roboczej, jak pokazano w tym kodzie. Pamiętaj, że używanie wait() i notify() nie jest zalecanym wzorcem w Kotlinie, który ma własne mechanizmy obsługi współbieżności. Jeśli używasz Kotlina, w miarę możliwości stosuj mechanizmy specyficzne 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ą też inne sytuacje, które mogą blokować główny wątek, w tym
wątki, które używają Lock, Semaphore, a także puli zasobów
(np. puli połączeń z bazą danych) lub innych mechanizmów wzajemnego wykluczania (mutex)
.
Ogólnie rzecz biorąc, należy ocenić blokady, które aplikacja ma na zasobach, ale jeśli chcesz uniknąć błędów ANR, sprawdź blokady zasobów wymaganych przez główny wątek.
Upewnij się, że blokady są utrzymywane przez jak najkrótszy czas, a jeszcze lepiej – sprawdź, czy aplikacja w ogóle potrzebuje blokady. Jeśli używasz blokady do określenia, kiedy zaktualizować interfejs na podstawie przetwarzania wątku instancji roboczej, użyj mechanizmów takich jak onProgressUpdate() i onPostExecute() do komunikacji między wątkiem instancji roboczej a głównym.
Powolne odbiorniki
Aplikacje mogą odpowiadać na komunikaty rozgłaszane, np. włączanie lub wyłączanie trybu samolotowego albo zmiana stanu połączenia, za pomocą odbiorników. Błąd ANR występuje, gdy przetwarzanie komunikatu rozgłaszanego przez aplikację trwa zbyt długo.
Błąd ANR występuje w tych przypadkach:
- Odbiornik nie zakończył wykonywania metody
onReceivew rozsądnym czasie. - Odbiornik wywołuje
goAsynci nie wywołujefinishw obiekciePendingResult.
Aplikacja powinna wykonywać tylko krótkie operacje w metodzie onReceive odbiornika BroadcastReceiver. Jeśli jednak aplikacja wymaga bardziej złożonego przetwarzania w wyniku komunikatu rozgłaszanego, odłóż zadanie do ViewModel (wykorzystując możliwości współprogramów, zakresów i mechanizmów dispatcher Kotlina) jeśli zadanie ma trwać co najwyżej kilka sekund, do dowolnego typu zmiennej stanu, lub do WorkManager w przypadku zadań, które mają trwać dłużej niż kilka sekund.
Za pomocą narzędzi takich jak Traceview możesz sprawdzić, czy odbiornik wykonuje długotrwałe operacje w głównym wątku aplikacji. Na przykład na ilustracji 6 widać oś czasu odbiornika, który przetwarza komunikat w wątku głównym przez około 100 sekund.

Ilustracja 5. Oś czasu Traceview pokazująca pracę BroadcastReceiver w wątku głównym
Takie zachowanie może być spowodowane wykonywaniem długotrwałych operacji w metodzie
onReceive() odbiornika 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 an IntentService ponieważ używa ona wątku instancji roboczej do wykonywania swojej pracy.
Ten 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. Ilustracja 7 pokazuje pracę odłożoną do wątku instancji roboczej na osi czasu Traceview.

Ilustracja 6. Oś czasu Traceview pokazująca komunikat rozgłaszany przetwarzany w wątku instancji roboczej
Odbiornik może używać goAsync(), aby zasygnalizować systemowi, że potrzebuje więcej czasu na przetworzenie komunikatu. Należy jednak wywołać
finish() w obiekcie PendingResult. Ten przykład pokazuje, jak wywołać finish(), aby umożliwić systemowi ponowne użycie 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);
Jeśli jednak przeniesiesz kod z powolnego odbiornika do innego wątku i
użyjesz goAsync(), nie rozwiążesz problemu z błędem ANR, jeśli rozgłaszanie odbywa się w tle.
Limit czasu ANR nadal obowiązuje.