ANRs (Ansichten)

Konzepte und Jetpack Compose-Implementierung

Wenn der UI-Thread einer Android-App zu lange blockiert ist, wird ein ANR-Fehler (Application Not Responding) 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 die App beenden.

Das ANR-Dialogfeld, das dem Nutzer angezeigt wird.
Abbildung 1. ANR-Dialogfeld, das dem Nutzer angezeigt wird

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

  • Zeitüberschreitung bei der Eingabeübertragung: Wenn Ihre App nicht innerhalb von 5 Sekunden auf ein Eingabe ereignis (z. B. einen Tastendruck oder eine Bildschirmberührung) reagiert hat.
  • Ausführung des Dienstes: Wenn ein von Ihrer App deklarierter Dienst die Ausführung von Service.onCreate() und Service.onStartCommand()/Service.onBind() nicht innerhalb weniger Sekunden abschließen kann.
  • **Service.startForeground() nicht aufgerufen**: Wenn Ihre App Context.startForegroundService() verwendet, um einen neuen Dienst im Vordergrund zu starten der Dienst aber startForeground() nicht innerhalb von 5 Sekunden aufruft.
  • Übertragung von Intents: Wenn die Ausführung eines BroadcastReceiver nicht innerhalb eines bestimmten Zeitraums abgeschlossen wurde. Wenn die App eine Aktivität im Vordergrund hat, beträgt dieses Zeitlimit 5 Sekunden.
  • **JobScheduler Interaktionen**: Wenn JobService nicht innerhalb weniger Sekunden von JobService.onStartJob() oder JobService.onStopJob() zurückgegeben wird oder wenn ein vom Nutzer initiierter Job gestartet wird und Ihre App JobService.setNotification() nicht innerhalb weniger Sekunden nach dem Aufruf von JobService.onStartJob() aufruft. Bei Apps, die auf Android 13 und niedriger ausgerichtet sind, werden ANR-Fehler nicht gemeldet. Bei Apps, die auf Android 14 und höher ausgerichtet sind, werden ANR-Fehler explizit gemeldet.

Wenn in Ihrer App ANR-Fehler auftreten, können Sie das Problem mithilfe der Informationen in diesem Artikel diagnostizieren und 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 in Ihrem 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.

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

Abbildung 2. Traceview-Zeitachse mit einem ausgelasteten Hauptthread

Abbildung 2 Traceview-Zeitachse, die einen beschäftigten Hauptthread zeigt

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

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 Worker-Thread verschieben. Das Android-Framework enthält Klassen, mit denen Sie die Aufgabe in einen Arbeitsthread verschieben können. Weitere Informationen finden Sie unter Worker-Threads.

E/A im Hauptthread

Die Ausführung 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 Festplattenlesevorgänge aus (z. B. SharedPreferences oder Datenbankaufrufe), während sie versuchen, den Anfangszustand abzuleiten.

Führen Sie E/A-Vorgänge mit langer Ausführungszeit 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 Worker-Thread zu verschieben, wie im vorherigen Abschnitt beschrieben.

Beispiele für E/A-Vorgänge sind Netzwerk- und Speichervorgänge. Weitere Informationen finden Sie 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 Sperre für eine Ressource hält, 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 auf einem Worker-Thread ausgeführten Aufgaben zeigt

Abbildung 3 Traceview-Zeitachse, die die Arbeit zeigt, die in einem Worker-Thread ausgeführt wird

Wenn bei Ihren Nutzern weiterhin ANR-Fehler auftreten, sollten Sie den Status des Hauptthreads im Android Device Monitor prüfen. Normalerweise befindet sich der Hauptthread im RUNNABLE Status, wenn er bereit ist, die Benutzeroberfläche zu aktualisieren, und im Allgemeinen reagiert.

Wenn die Ausführung des Hauptthreads jedoch nicht fortgesetzt werden kann, befindet er sich im BLOCKED Status und kann nicht auf Ereignisse reagieren. Der Status wird im Android Device Monitor als Monitor oder Wait angezeigt, wie in Abbildung 5 dargestellt.

Abbildung 4: Hauptthread im Monitorstatus

Abbildung 4 Hauptthread im Status „Monitor“

Der folgende Trace zeigt den Hauptthread einer App, der blockiert ist und 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)
...

Wenn Sie den Trace überprüfen, 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 dargestellt. Die Verwendung von wait() und notify() ist in Kotlin nicht empfehlenswert, da Kotlin eigene Mechanismen für die Verarbeitung von Parallelität 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 einige andere Situationen, die den Hauptthread blockieren können, darunter Threads, die Lock, Semaphore sowie einen Ressourcenpool (z. B. einen Pool von Datenbankverbindungen) oder andere Mechanismen zur gegenseitigen Ausschließung (Mutex) verwenden.

Sie sollten die Sperren, die Ihre App für Ressourcen hält, im Allgemeinen prüfen. Wenn Sie jedoch ANR-Fehler vermeiden möchten, sollten Sie sich die Sperren für Ressourcen ansehen, die der Hauptthread benötigt.

Achten Sie darauf, dass die Sperren nur für die kürzestmögliche Zeit gehalten werden. Noch besser ist es, zu prüfen, ob die App die Sperre überhaupt benötigt. Wenn Sie die Sperre verwenden, um zu bestimmen, 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 Übertragungsnachrichten reagieren, z. B. auf das Aktivieren oder Deaktivieren des Flugmodus oder auf eine Änderung des Verbindungsstatus. Ein ANR-Fehler tritt auf, wenn die Verarbeitung der Nachricht an alle durch eine App zu lange dauert.

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

  • Ein Übertragungsempfänger hat die Ausführung seiner onReceive Methode nicht innerhalb eines angemessenen Zeitraums abgeschlossen.
  • Ein Übertragungsempfänger ruft goAsync auf und ruft finish nicht für das PendingResult-Objekt auf.

Ihre App sollte in der onReceive-Methode eines BroadcastReceiver nur kurze Vorgänge ausführen. Wenn Ihre App jedoch eine komplexere Verarbeitung als Folge einer Nachricht an alle erfordert, sollten Sie die Aufgabe an ein ViewModel (mit den Vorteilen von Kotlin-Koroutinen, -Scopes und -Dispatchern) delegieren, wenn die Aufgabe voraussichtlich höchstens einige Sekunden dauert, an einen beliebigen Typ von State Holder, oder an WorkManager für Aufgaben, die voraussichtlich länger als einige Sekunden dauern.

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

Abbildung 5: Traceview-Zeitachse mit der Arbeit von „BroadcastReceiver“ im Hauptthread

Abbildung 5 Traceview-Zeitachse, die die Arbeit des BroadcastReceiver im Hauptthread zeigt

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

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 Fällen wird empfohlen, den Vorgang mit langer Ausführungszeit in ein IntentService zu verschieben, da dieser einen Arbeitsthread verwendet, um seine Arbeit auszuführen. Der folgende Code zeigt, wie ein IntentService verwendet wird, 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 des IntentService wird der Vorgang mit langer Ausführungszeit in einem Arbeitsthread anstelle des Hauptthreads ausgeführt. Abbildung 7 zeigt die Arbeit, die in der Traceview-Zeitachse an den Arbeitsthread delegiert wurde.

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

Abbildung 6 Traceview-Zeitachse, die die Übertragungsnachricht zeigt, die in einem Worker-Thread verarbeitet wird

Ihr Übertragungsempfänger kann goAsync() verwenden, um dem System zu signalisieren, dass es 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 wiederverwenden 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 Sie den Code von einem langsamen Übertragungsempfänger in einen anderen Thread verschieben und verwenden goAsync(), wird der ANR-Fehler jedoch nicht behoben, wenn die Übertragung im Hintergrund erfolgt. Das ANR-Zeitlimit gilt weiterhin.