Błędy ANR

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.

Użytkownikowi wyświetliło się okno z błędem ANR.
Rysunek 1. Wyświetlenie użytkownikowi okna z błędem ANR

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 zareagował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 metod Service.onCreate i Service.onStartCommand/Service.onBind w ciągu kilku sekund.
  • Nie wywołano Service.startForeground: jeśli aplikacja używa Context.startForegroundService do uruchomienia nowej usługi na pierwszym planie, ale usługa nie wywołuje startForeground w ciągu 5 sekund.
  • Rozgłaszanie intencji: jeśli BroadcastReceiver nie zakończy się w określonym czasie. Jeśli aplikacja jest aktywna na pierwszym planie, limit czasu wynosi 10 sekund.
  • Interakcje z JobScheduler: jeśli JobService nie zwróci odpowiedzi z JobService.onStartJob lub JobService.onStopJob w ciągu kilku sekund albo jeśli rozpocznie się zadanie zainicjowane przez użytkownika, a aplikacja nie wywoła JobService.setNotification w ciągu kilku sekund po wywołaniu JobService.onStartJob. W przypadku aplikacji kierowanych na Androida 13 lub starszego błędy ANR są ciche i nie są zgłaszane aplikacji. W przypadku aplikacji kierowanych na Androida 14 lub nowszego błędy ANR są jawne i są zgłaszane aplikacji.

Jeśli w aplikacji występują błędy ANR, możesz skorzystać z informacji zawartych w tym dokumencie, aby zdiagnozować i rozwiązać problem.

Diagnozowanie błędów ANR

Podczas diagnozowania błędów ANR warto zwrócić uwagę na kilka typowych wzorców:

  • Aplikacja wykonuje powolne operacje wejścia-wyjścia w wątku głównym.
  • Aplikacja wykonuje długie obliczenia w głównym wątku.
  • Wątek główny wykonuje synchroniczne wywołanie Binder do innego procesu, a ten proces długo nie zwraca odpowiedzi.
  • Wątek główny jest zablokowany i czeka na zsynchronizowany blok długiej operacji, która odbywa się w innym wątku.
  • Wątek główny jest zablokowany przez inny wątek w Twoim procesie lub przez wywołanie binder. Wątek główny nie tylko czeka na zakończenie długiej operacji, ale jest w sytuacji zakleszczenia.

Poniższe techniki mogą pomóc Ci określić przyczynę błędów ANR.

HealthStats

HealthStats dostarcza dane o stanie aplikacji, rejestrując łączny czas użytkownika i systemu, czas pracy procesora, sieć, statystyki radiowe, czas włączenia/wyłączenia ekranu i alarmy wybudzenia. Może to pomóc w pomiarze ogólnego wykorzystania procesora i zużycia baterii.

Debugowanie

Debug pomaga sprawdzać aplikacje na Androida podczas ich tworzenia, m.in. śledzić i liczyć alokacje, aby wykrywać zacinanie się i opóźnienia w aplikacjach. Możesz też użyć Debug, aby uzyskać liczniki pamięci środowiska wykonawczego i pamięci natywnej oraz wskaźniki pamięci, które pomogą Ci określić wykorzystanie pamięci przez dany proces.

ApplicationExitInfo

Interfejs ApplicationExitInfo jest dostępny na Androidzie 11 (poziom API 30) lub nowszym i zawiera informacje o przyczynie zamknięcia aplikacji. Obejmuje to błędy ANR, małą ilość pamięci, awarie aplikacji, nadmierne wykorzystanie procesora, przerwy w działaniu aplikacji spowodowane przez użytkownika, przerwy w działaniu aplikacji spowodowane przez system i zmiany uprawnień w czasie działania aplikacji.

Tryb ścisły

Korzystanie z StrictMode pomaga znaleźć przypadkowe operacje wejścia/wyjścia w głównym wątku podczas tworzenia aplikacji. Możesz używać StrictMode na poziomie aplikacji lub aktywności.

Włącz okna błędów ANR w tle

Android wyświetla okna ANR w przypadku aplikacji, które zbyt długo przetwarzają komunikat, 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ą wyświetlane użytkownikowi, nawet jeśli aplikacja ma problemy z wydajnością.

Wąskie gardła rekompozycji

Użyj Profilera w Android Studio i Layout Inspectora, aby znaleźć wąskie gardła rekompozycji. Więcej informacji znajdziesz w artykule o wydajności Jetpack Compose.

Pobieranie pliku logów czasu

Android przechowuje informacje o śladach, gdy wystąpi błąd ANR. W starszych wersjach systemu operacyjnego na urządzeniu znajduje się jeden plik /data/anr/traces.txt. W nowszych wersjach systemu operacyjnego jest kilka plików /data/anr/anr_*. Ślady błędów ANR możesz uzyskać z urządzenia lub emulatora, używając Android Debug Bridge (adb) jako użytkownik root:

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

Raport o błędzie możesz wygenerować na urządzeniu fizycznym, korzystając z opcji programisty Zgłoś błąd na urządzeniu lub polecenia adb bugreport na komputerze używanym do programowania. Więcej informacji znajdziesz w artykule Przechwytywanie i odczytywanie raportów o błędach.

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.

Częstym problemem jest długotrwałe zadanie bezpośrednio w funkcji kompozycyjnej:

@Composable
fun BadList(rawStrings: List<String>) {
    // Math or sorting inside the composable runs on EVERY recomposition pass!
    val heavilyProcessedList = rawStrings
        .filter { it.isNotBlank() }
        .map { it.uppercase().reversed() }
        .map { it.computationallyHeavyFunction() }
.sortedBy { it.length }
    LazyColumn { items(sortedList) { Text(it) } }
}

// Modern Compose-first fix
@Composable
fun GoodList(viewModel: MyViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    // UI simply renders state; no heavy processing allowed here
    LazyColumn { items(uiState.sortedData) { Text(it) } }
}

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żyj withContext(Dispatchers.IO) w ViewModel lub, jeszcze lepiej, użyj Repository w warstwie danych.

Zakleszczenia

Zakleszczenie występuje, gdy wątek przechodzi w stan oczekiwania, ponieważ wymagany zasób jest zajęty przez inny wątek, który również oczekuje na zasób zajęty przez pierwszy wątek. Jeśli główny wątek aplikacji znajduje się w takiej sytuacji, prawdopodobnie wystąpią błędy ANR.

Zakleszczenia to dobrze znane zjawisko w informatyce. Istnieją algorytmy zapobiegania zakleszczeniom, których można używać, aby ich unikać.

Więcej informacji znajdziesz w artykułach Zakleszczenie i Algorytmy zapobiegania zakleszczeniom w Wikipedii.

Jeśli używasz Kotlina i Compose, możesz zastąpić blokady podstawowe nieblokującymi muteksami współprogramów (Mutex.withLock), aby zapobiec blokowaniu wątków przez zawieszenie kontekstu wykonywania zamiast zamrażania wątku interfejsu. Na przykład:

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

// Modern non-blocking concurrency state architecture
class SecureDataRepository {
    private val mutex = Mutex()

    suspend fun safeUIAccess() {
        // If locked, the main thread suspends seamlessly, preventing an ANR
        mutex.withLock {
            performSafeOperation()
        }
    }
}

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 onReceive w odpowiednim czasie.
  • Odbiornik wywołuje metodę goAsync, ale nie wywołuje metody finish w obiekcie PendingResult.

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.

GameActivity

Biblioteka GameActivity zmniejszyła liczbę błędów ANR w studiach przypadków gier i aplikacji napisanych w języku C lub C++. Jeśli zastąpisz dotychczasowe działanie natywne biblioteką GameActivity, możesz ograniczyć blokowanie wątku UI i zapobiec niektórym błędom ANR.

Więcej informacji o błędach ANR znajdziesz w artykule Zapewnianie szybkiego działania aplikacji. Więcej informacji o wątkach znajdziesz w artykule Lepsza wydajność dzięki wątkom.

Dodatkowe materiały

Wyświetlanie treści