ANRs (Ansichten)

Konzepte und Jetpack Compose-Implementierung

Wenn der UI-Thread einer Android-App zu lange blockiert ist, wird ein ANR-Fehler (App antwortet nicht) ausgelöst. Wenn sich die App im Vordergrund befindet, wird dem Nutzer ein Dialogfeld angezeigt, wie in Abbildung 1 dargestellt. Im ANR-Dialogfeld kann der Nutzer das Beenden der App erzwingen.

Dem Nutzer wird ein ANR-Dialogfeld angezeigt.
Abbildung 1: Dem Nutzer wird ein ANR-Dialogfeld angezeigt

ANR-Fehler sind ein Problem, weil der Hauptthread der App, der für die Aktualisierung der Benutzeroberfläche verantwortlich ist, keine Nutzereingabeereignisse verarbeiten oder zeichnen kann, was zu Frustration beim Nutzer führt. Weitere Informationen zum Hauptthread der App finden Sie unter Prozesse und Threads – Übersicht.

Ein ANR-Fehler wird für Ihre App ausgelöst, wenn eine der folgenden Bedingungen erfüllt ist:

  • Zeitüberschreitung bei der Eingabeverteilung: Wenn Ihre App innerhalb von 5 Sekunden nicht auf ein Eingabeereignis (z. B. Tastendruck oder Berührung des Bildschirms) reagiert hat.
  • Dienst wird ausgeführt: Wenn ein von deiner App deklarierter Dienst Service.onCreate() und Service.onStartCommand()/Service.onBind() nicht innerhalb weniger Sekunden ausführen kann.
  • **Service.startForeground() nicht aufgerufen**: Wenn deine App Context.startForegroundService() verwendet, um einen neuen Dienst im Vordergrund zu starten, der Dienst aber nicht innerhalb von 5 Sekunden startForeground() aufruft.
  • Broadcast of Intent: Wenn ein BroadcastReceiver nicht innerhalb einer bestimmten Zeit ausgeführt wurde. Wenn die App im Vordergrund aktiv ist, beträgt dieses Zeitlimit 10 Sekunden.
  • **JobScheduler-Interaktionen**: Wenn ein JobService nicht innerhalb weniger Sekunden von JobService.onStartJob() oder JobService.onStopJob() zurückkehrt oder wenn ein vom Nutzer initiierter Job gestartet wird und Ihre App nicht innerhalb weniger Sekunden nach dem Aufruf von JobService.onStartJob() die Methode JobService.setNotification() aufruft. Bei Apps, die auf Android 13 und niedriger ausgerichtet sind, werden ANRs nicht gemeldet. Bei Apps, die auf Android 14 und höher ausgerichtet sind, werden ANRs explizit gemeldet.

Wenn in deiner App ANR-Fehler auftreten, kannst du die Anleitung in diesem Artikel verwenden, um das Problem zu diagnostizieren und zu beheben.

Probleme beheben

Nachdem Sie das Problem identifiziert haben, können Sie die Tipps in diesem Abschnitt verwenden, um häufig auftretende Probleme zu beheben.

Langsamer Code im Hauptthread

Ermitteln Sie die Stellen im Code, an denen der Hauptthread der App länger als 5 Sekunden beschäftigt ist. Suchen Sie in Ihrer App nach verdächtigen Anwendungsfällen und versuchen Sie, den ANR-Fehler zu reproduzieren.

In Abbildung 2 ist beispielsweise eine Traceview-Zeitachse zu sehen, auf der der Hauptthread länger als 5 Sekunden beschäftigt ist.

Abbildung 2. Traceview-Zeitachse mit einem ausgelasteten Hauptthread

Abbildung 2. Traceview-Zeitachse mit einem ausgelasteten Hauptthread

Abbildung 2 zeigt, dass der Großteil des fehlerhaften Codes im Handler onClick(View) auftritt, wie im folgenden Codebeispiel zu sehen ist:

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);
}

In diesem Fall sollten Sie die Arbeit, die im Hauptthread ausgeführt wird, in einen Arbeitsthread verschieben. Das Android-Framework enthält Klassen, mit denen die Aufgabe in einen Arbeitsthread verschoben werden kann. Weitere Informationen finden Sie unter Arbeitsthreads.

E/A im Hauptthread

Das Ausführen von E/A-Vorgängen im Hauptthread ist eine häufige Ursache für langsame Vorgänge im Hauptthread, die zu ANR-Fehlern führen können. In Compose lösen Entwickler oft versehentlich Lesevorgänge auf dem Laufwerk aus (z. B. SharedPreferences oder Datenbankaufrufe), wenn sie versuchen, den Anfangszustand abzuleiten.

Führen Sie lang andauernde E/A-Vorgänge außerhalb der UI-Ebene aus. Verwenden Sie withContext(Dispatchers.IO) in einem ViewModel oder, noch besser, ein Repository auf der Datenebene. Es wird empfohlen, alle E/A-Vorgänge in einen Arbeitsthread zu verschieben, wie im vorherigen Abschnitt gezeigt.

Beispiele für E/A-Vorgänge sind Netzwerk- und Speichervorgänge. Weitere Informationen findest du unter Netzwerkvorgänge ausführen und Daten speichern.

Sperrenkonflikt

In einigen Fällen wird die Arbeit, die den ANR-Fehler verursacht, nicht direkt im Hauptthread der App ausgeführt. Wenn ein Arbeitsthread eine Ressource sperrt, die der Hauptthread zum Abschließen seiner Arbeit benötigt, kann ein ANR-Fehler auftreten.

Abbildung 3 zeigt beispielsweise eine Traceview-Zeitachse, auf der der Großteil der Arbeit in einem Arbeitsthread ausgeführt wird.

Abbildung 3: Traceview-Zeitachse, die die Arbeit zeigt, die in einem Arbeitsthread ausgeführt wird

Abbildung 3. Traceview-Zeitachse, die die Arbeit zeigt, die in einem Arbeitsthread ausgeführt wird

Wenn bei deinen Nutzern jedoch weiterhin ANRs auftreten, solltest du dir den Status des Hauptthreads im Android Device Monitor ansehen. Normalerweise befindet sich der Hauptthread im Status RUNNABLE, wenn er bereit ist, die Benutzeroberfläche zu aktualisieren, und im Allgemeinen reagiert.

Wenn der Hauptthread die Ausführung jedoch nicht fortsetzen kann, befindet er sich im Status BLOCKED und kann nicht auf Ereignisse reagieren. Der Status wird im Android Device Monitor als Monitor oder Wait angezeigt, wie in Abbildung 5 zu sehen.

Abbildung 4: Hauptthread im Monitor-Status

Abbildung 4. Hauptthread im Status „Monitor“

Der folgende Trace zeigt den Hauptthread einer App, der blockiert ist, weil er auf eine Ressource wartet:

...
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)
...

Anhand des Trace können Sie den Code finden, der den Hauptthread blockiert. Der folgende Code ist für die Sperre verantwortlich, die den Hauptthread im vorherigen Trace blockiert:

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]);
      }
  }
}

Ein weiteres Beispiel ist der Hauptthread einer App, der auf ein Ergebnis von einem Arbeitsthread wartet, wie im folgenden Code gezeigt. Die Verwendung von wait() und notify() ist in Kotlin kein empfohlenes Muster, da Kotlin eigene Mechanismen für die Nebenläufigkeit hat. Wenn Sie Kotlin verwenden, sollten Sie nach Möglichkeit Kotlin-spezifische Mechanismen verwenden.

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();
      }
  }
}

Es gibt noch andere Situationen, in denen der Hauptthread blockiert werden kann, z. B. Threads, die Lock, Semaphore sowie einen Ressourcenpool (z. B. einen Pool von Datenbankverbindungen) oder andere Mechanismen zum gegenseitigen Ausschluss (Mutex) verwenden.

Du solltest die Sperren, die deine App für Ressourcen hält, im Allgemeinen prüfen. Wenn du jedoch ANRs vermeiden möchtest, solltest du dir die Sperren für Ressourcen ansehen, die vom Hauptthread benötigt werden.

Achten Sie darauf, dass die Sperren so kurz wie möglich gehalten werden, oder prüfen Sie, ob die App die Sperre überhaupt benötigt. Wenn Sie die Sperre verwenden, um festzulegen, wann die Benutzeroberfläche basierend auf der Verarbeitung eines Arbeitsthreads aktualisiert werden soll, verwenden Sie Mechanismen wie onProgressUpdate() und onPostExecute(), um zwischen dem Arbeitsthread und dem Hauptthread zu kommunizieren.

Langsame Übertragungsempfänger

Apps können mithilfe von Übertragungsempfängern auf Broadcast-Nachrichten reagieren, z. B. auf das Aktivieren oder Deaktivieren des Flugmodus oder auf eine Änderung des Verbindungsstatus. Ein ANR-Fehler tritt auf, wenn eine App zu lange braucht, um die Broadcast-Nachricht zu verarbeiten.

Ein ANR-Fehler tritt in den folgenden Fällen auf:

  • Ein Übertragungsempfänger hat die Methode onReceive nicht innerhalb eines angemessenen Zeitraums ausgeführt.
  • Ein Übertragungsempfänger ruft goAsync auf und kann finish nicht für das PendingResult-Objekt aufrufen.

In der Methode onReceive eines BroadcastReceiver sollten nur kurze Vorgänge ausgeführt werden. Wenn Ihre App jedoch aufgrund einer Nachricht an alle eine komplexere Verarbeitung erfordert, sollten Sie die Aufgabe an einen ViewModel (unter Nutzung der Leistungsfähigkeit von Kotlin-Koroutinen, -Bereichen und -Dispatchern) delegieren, wenn die Aufgabe voraussichtlich höchstens ein paar Sekunden dauert, an einen beliebigen Typ von State Holder oder an WorkManager für Aufgaben, die voraussichtlich länger als ein paar Sekunden dauern.

Mit Tools wie Traceview können Sie feststellen, ob Ihr Übertragungsempfänger Vorgänge mit langer Ausführungszeit im Hauptthread der App ausführt. In Abbildung 6 ist beispielsweise die Zeitachse eines Übertragungsempfängers zu sehen, der eine Nachricht im Hauptthread etwa 100 Sekunden lang verarbeitet.

Abbildung 5: Traceview-Zeitachse mit der `BroadcastReceiver`-Arbeit im Hauptthread

Abbildung 5. Traceview-Zeitachse mit der BroadcastReceiver-Arbeit im Hauptthread

Dieses Verhalten kann durch die Ausführung von Vorgängen mit langer Ausführungszeit für die Methode onReceive() des BroadcastReceiver verursacht werden, wie im folgenden Beispiel gezeigt:

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);
}

In solchen Situationen empfiehlt es sich, den Vorgang mit langer Ausführungszeit in einen IntentService zu verschieben, da dieser einen Arbeitsthread verwendet, um seine Arbeit auszuführen. Der folgende Code zeigt, wie Sie einen IntentService verwenden, um einen Vorgang mit langer Ausführungszeit zu verarbeiten:

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);
  }
}

Durch die Verwendung von IntentService wird der Vorgang mit langer Ausführungszeit in einem Arbeitsthread statt im Hauptthread ausgeführt. Abbildung 7 zeigt die Arbeit, die in der Traceview-Zeitachse an den Arbeitsthread zurückgestellt wurde.

Abbildung 6. Traceview-Zeitachse mit der Broadcast-Nachricht, die in einem Arbeitsthread verarbeitet wird

Abbildung 6. Traceview-Zeitachse mit der Broadcast-Nachricht, die in einem Arbeitsthread verarbeitet wird

Dein Übertragungsempfänger kann goAsync() verwenden, um dem System zu signalisieren, dass er mehr Zeit für die Verarbeitung der Nachricht benötigt. Sie sollten jedoch finish() für das PendingResult-Objekt aufrufen. Das folgende Beispiel zeigt, wie finish() aufgerufen wird, damit das System den Übertragungsempfänger recyceln und einen ANR-Fehler vermeiden kann:

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);

Wenn du den Code von einem langsamen Übertragungsempfänger in einen anderen Thread verschiebst und goAsync() verwendest, wird der ANR-Fehler jedoch nicht behoben, wenn die Übertragung im Hintergrund erfolgt. Das ANR-Zeitlimit gilt weiterhin.