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 jest na pierwszym planie, system wyświetla użytkownikowi okno dialogowe, jak pokazano na rysunku 1. Okno błędu ANR daje użytkownikowi możliwość wymuszenia zamknięcia aplikacji.

Okno ANR wyświetlane użytkownikowi.
Rysunek 1. Okno błędu ANR wyświetlane użytkownikowi

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 frustruje 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:

  • Przekazywanie danych wejściowych przekroczyło limit czasu: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 Service.onCreateService.onStartCommand/Service.onBind w ciągu kilku sekund.
  • Service.startForeground nie wywołano: jeśli aplikacja używa funkcji Context.startForegroundService do uruchomienia nowej usługi na pierwszym planie, ale usługa nie wywołuje funkcji startForeground w ciągu 5 sekund.
  • Nadawanie intencji: jeśli BroadcastReceiver nie zakończył wykonywania w ustalonym czasie. Jeśli aplikacja jest aktywna na pierwszym planie, ten czas oczekiwania wynosi 5 sekund.
  • JobScheduler interakcje: jeśli JobService nie wraca z JobService.onStartJob lub JobService.onStopJob w ciągu kilku sekund lub 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 i starszego błędy ANR są ciche i nie są zgłaszane do aplikacji. W przypadku aplikacji kierowanych na Androida 14 i nowszego błędy ANR są jawne i są zgłaszane do aplikacji.

Jeśli w aplikacji występują błędy ANR, możesz skorzystać z informacji 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 te typowe wzorce:

  • Aplikacja wykonuje powolne operacje 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 Binder do innego procesu, a ten proces długo zwraca wynik.
  • Wątek główny jest zablokowany i oczekuje na zsynchronizowany blok długotrwałej operacji, która jest wykonywana w innym wątku.
  • Wątek główny znajduje się w zakleszczeniu z innym wątkiem w Twoim procesie lub w wywołaniu bindera. Wątek główny nie tylko czeka na zakończenie długotrwałej operacji, ale znajduje się w sytuacji zakleszczenia.

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

HealthStats

HealthStats udostępnia dane o stanie aplikacji, rejestrując łączny czas użytkownika i systemu, czas procesora, statystyki sieci i radia, czas włączenia/wyłączenia ekranu oraz alarmy budzenia. Pomoże Ci to mierzyć ogólne wykorzystanie procesora i zużycie baterii.

Debugowanie

Debug pomaga sprawdzać aplikacje na Androida podczas ich tworzenia, w tym śledzić i liczyć alokacje, aby wykrywać w nich zacięcia i opóźnienia. Możesz też użyć Debug, aby uzyskać liczniki pamięci natywnej i czasu działania oraz dane dotyczące pamięci, które pomogą Ci określić wykorzystanie pamięci konkretnego procesu.

ApplicationExitInfo

ApplicationExitInfo jest dostępny na Androidzie 11 (poziom interfejsu API 30) lub nowszym i zawiera informacje o przyczynie zamknięcia aplikacji. Obejmuje to błędy ANR, niski poziom 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.

Tryb ścisły

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

Włączanie okien ANR w tle

Android wyświetla okna ANR w przypadku aplikacji, które zbyt długo przetwarzają komunikat transmisji, tylko wtedy, gdy w opcjach programisty na urządzeniu włączona jest opcja Pokaż wszystkie błędy ANR. Z tego powodu okna dialogowe 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 Androida Studionarzędzia Layout Inspector, aby śledzić wąskie gardła rekompozycji. Więcej informacji znajdziesz w artykule Jetpack Compose Performance (w języku angielskim).

Pobieranie pliku śladów

Gdy w systemie Android wystąpi błąd ANR, zapisuje on informacje o śledzeniu. W starszych wersjach systemu operacyjnego na urządzeniu znajduje się jeden plik /data/anr/traces.txt. W nowszych wersjach systemu operacyjnego występuje kilka plików /data/anr/anr_*. Do śladów ANR na urządzeniu lub emulatorze możesz uzyskać dostęp za pomocą 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 utworzyć na urządzeniu fizycznym, korzystając z opcji programisty Zgłoś błąd na urządzeniu lub z 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 wątku głównym

Zidentyfikuj miejsca w kodzie, w których główny wątek aplikacji jest zajęty przez ponad 5 sekund. Wyszukaj w aplikacji podejrzane przypadki 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ą powolnych operacji w wątku głównym, które mogą powodować błędy ANR. W Compose deweloperzy często przypadkowo wywołują odczyty z dysku (np. SharedPreferences lub wywołania bazy danych) podczas próby uzyskania stanu początkowego.

Wykonywanie długotrwałych operacji wejścia/wyjścia poza warstwą interfejsu. Użyj withContext(Dispatchers.IO)ViewModel lub, co jeszcze lepsze, użyj Repositorywarstwie danych.

Zakleszczenia

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

Zakleszczenia są dobrze zbadanym zjawiskiem w informatyce. Istnieją algorytmy zapobiegające zakleszczeniom, których możesz używać, aby ich uniknąć.

Więcej informacji znajdziesz w artykułach ZakleszczenieAlgorytmy zapobiegające zakleszczeniom w Wikipedii.

Jeśli używasz języka Kotlin i biblioteki Compose, możesz zastąpić blokady pierwotne nieblokującymi wątków muteksami (Mutex.withLock) współprogramów, aby zapobiec blokowaniu wątków przez zawieszanie kontekstu wykonania zamiast zamrażania wątku UI. 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ą odpowiadać na wiadomości rozgłoszeniowe, np. włączanie lub wyłączanie trybu samolotowego czy zmianę stanu połączenia, za pomocą odbiorników rozgłoszeniowych. Błąd ANR występuje, gdy aplikacja zbyt długo przetwarza komunikat.

Błąd ANR występuje w tych przypadkach:

  • Odbiornik transmisji nie zakończył wykonywania metody onReceive w rozsądnym czasie.
  • Odbiornik transmisji wywołuje metodę goAsync i nie może wywołać metody finish na 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 rozgłoszeniowego, odłóż to zadanie na później, korzystając z ViewModel (wykorzystując moc współprogramów, zakresów i dispatcherów w Kotlinie), jeśli zadanie ma trwać co najwyżej kilka sekund, dowolnego rodzaju zmiennej stanu lub WorkManager w przypadku zadań, które mają trwać 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 C lub C++. Jeśli zastąpisz istniejącą działanie natywne biblioteką GameActivity, możesz zmniejszyć blokowanie wątku UI i zapobiec występowaniu niektórych błędów ANR.

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

Dodatkowe materiały

Wyświetla treści