ANR

Lorsque le thread UI d'une application Android est bloqué trop longtemps, une erreur ANR (« L'application ne répond pas ») se déclenche. Si l'application est exécutée au premier plan, le système affiche une boîte de dialogue, comme illustré dans la figure 1. La boîte de dialogue ANR permet à l'utilisateur de forcer la fermeture de l'application.

Figure 1. Boîte de dialogue ANR affichée

Figure 1 : Boîte de dialogue ANR affichée

Les ANR posent problème, car le thread principal de l'application, responsable de la mise à jour de l'UI, ne peut pas traiter les événements d'entrée utilisateur ni dessiner, ce qui génère de la frustration pour l'utilisateur. Pour en savoir plus sur le thread principal de l'application, consultez Processus et threads.

Une erreur ANR se déclenche pour votre application lorsque l'une des conditions suivantes se produit :

  • Délai d'envoi des entrées dépassé : si votre application n'a pas répondu à une entrée (comme un appui sur une touche ou un événement tactile sur l'écran) dans les cinq secondes.
  • Exécution du service : si un service déclaré par votre application ne peut pas terminer l'exécution de Service.onCreate() et Service.onStartCommand()/Service.onBind() en quelques secondes.
  • Service.startForeground() non appelé : si votre application utilise Context.startForegroundService() pour démarrer un nouveau service au premier plan, mais que le service n'appelle pas startForeground() dans les cinq secondes.
  • Diffusion de l'intent : si l'exécution d'un BroadcastReceiver n'est pas terminée dans un délai donné. Si l'application présente une activité au premier plan, ce délai est de cinq secondes.
  • Interactions JobScheduler : si un JobService ne renvoie pas une valeur de JobService.onStartJob() ou JobService.onStopJob() dans les secondes qui suivent, ou si une tâche lancée par l'utilisateur démarre et que votre application n'appelle pas JobService.setNotification() dans les quelques secondes suivant l'appel de JobService.onStartJob(). Pour les applications ciblant Android 13 et les versions antérieures, les erreurs ANR sont silencieuses et ne sont pas signalées à l'application. Pour les applications ciblant Android 14 et versions ultérieures, les erreurs ANR sont explicites et sont signalées à l'application.

Si votre application rencontre des erreurs ANR, vous pouvez suivre les instructions de cet article pour les diagnostiquer et les résoudre.

Détecter le problème

Si vous avez déjà publié votre appli, vous pouvez utiliser Android Vitals pour consulter des informations sur les erreurs ANR. Vous pouvez utiliser d'autres outils pour détecter les erreurs ANR sur le terrain. Notez toutefois que les outils tiers ne peuvent pas signaler les ANR sur les anciennes versions d'Android (Android 10 et versions antérieures), contrairement à Android Vitals.

Android Vitals

Android Vitals vous permet de surveiller et d'améliorer le taux d'erreurs ANR de votre application. Android Vitals mesure plusieurs taux d'erreurs ANR :

  • Taux d'erreurs ANR : pourcentage d'utilisateurs actifs par jour ayant subi un type d'erreur ANR.
  • Taux d'erreurs ANR repérées par l'utilisateur : pourcentage d'utilisateurs actifs par jour ayant subi au moins une erreur ANR repérée par l'utilisateur. Actuellement, seules les erreurs ANR de type Input dispatching timed out sont considérées comme étant repérées par l'utilisateur.
  • Taux d'erreurs ANR multiples : pourcentage de vos utilisateurs actifs par jour ayant subi au moins deux erreurs ANR.

Un utilisateur actif par jour est un utilisateur unique qui se sert de votre application un seul jour sur un seul appareil, avec éventuellement plusieurs sessions. Si un utilisateur se sert de votre application sur plusieurs appareils au cours d'une même journée, chaque appareil est comptabilisé dans le nombre d'utilisateurs actifs pour ce jour-là. Si plusieurs personnes utilisent le même appareil au cours d'une même journée, un seul utilisateur actif est comptabilisé.

Le taux d'erreurs ANR repérées par l'utilisateur est une statistique principale Android Vitals. Il affecte donc la visibilité de votre application sur Google Play. Cette métrique est importante, car les erreurs ANR comptabilisées se produisent toujours lorsque l'utilisateur interagit avec l'appli, ce qui entraîne le plus de perturbations.

Play a défini deux seuils de comportement insatisfaisant pour cette métrique :

  • Seuil de comportement insatisfaisant en général : au moins 0,47 % des utilisateurs actifs par jour subissent une erreur ANR repérée par l'utilisateur sur tous les modèles d'appareils.
  • Seuil de comportement insatisfaisant par appareil : au moins 8 % des utilisateurs actifs par jour subissent une erreur ANR repérée par l'utilisateur sur un même modèle d'appareil.

Si votre application dépasse le seuil général de comportement insatisfaisant, elle risque d'être moins visible sur tous les appareils. Si elle dépasse le seuil de comportement insatisfaisant par appareil, elle risque d'être moins visible sur le type d'appareil en question et d'afficher un avertissement dans votre fiche Play Store.

Android Vitals peut vous envoyer des alertes via la Play Console lorsque votre application subit un nombre excessif d'erreurs ANR.

Pour savoir comment Google Play collecte les données Android Vitals, consultez la documentation de la Play Console.

Diagnostiquer les erreurs ANR

Voici quelques tendances courantes à prendre en compte lors du diagnostic des ANR :

  • L'application effectue des opérations lentes impliquant des E/S sur le thread principal.
  • L'application effectue un calcul long sur le thread principal.
  • Le thread principal effectue un appel de liaison synchrone vers un autre processus, et le retour de cet autre processus prend beaucoup de temps.
  • Le thread principal est bloqué en attendant un blocage synchronisé pour une longue opération qui se produit sur un autre thread.
  • Le thread principal se trouve dans un interblocage avec un autre thread, soit au cours de votre processus, soit via un appel de liaison. Le thread principal n'attend pas que l'opération longue se termine. Il se trouve dans une situation d'interblocage. Pour en savoir plus, consultez l'article Interblocage sur Wikipédia.

Les techniques suivantes peuvent vous aider à déterminer la cause de vos erreurs ANR.

HealthStats

HealthStats fournit des métriques sur l'état d'une application en capturant le temps système et utilisateur total, le temps CPU, le réseau, les statistiques radio, le temps d'activation/de désactivation de l'écran et les alarmes de réveil. Cela peut vous aider à mesurer l'utilisation globale du processeur et la décharge de la batterie.

Debug

Debug permet d'inspecter les applications Android pendant le développement, y compris le traçage et le nombre d'allocations pour identifier les à-coups et la latence dans les applis. Vous pouvez également utiliser Debug pour obtenir des compteurs d'exécution et de mémoire native, ainsi que des métriques de mémoire qui peuvent aider à identifier l'espace mémoire utilisé d'un processus particulier.

ApplicationExitInfo

ApplicationExitInfo est disponible sur Android 11 (niveau d'API 30) ou version ultérieure et fournit des informations sur le motif de fermeture d'une application. Cela inclut les erreurs ANR, une mémoire insuffisante, les plantages d'application, l'utilisation excessive du processeur, les interruptions utilisateur, les interruptions système ou les modifications des autorisations d'exécution.

Mode strict

StrictMode vous aide à détecter les opérations d'E/S accidentelles sur le thread principal pendant le développement de votre application. Vous pouvez utiliser StrictMode au niveau de l'application ou de l'activité.

Activer les boîtes de dialogue ANR en arrière-plan

Android affiche des boîtes de dialogue ANR pour les applications qui mettent trop de temps à traiter le message de diffusion uniquement si l'option Afficher toutes les erreurs ANR est activée dans les Options pour les développeurs de l'appareil. Pour cette raison, les boîtes de dialogue ANR en arrière-plan ne sont pas toujours présentées à l'utilisateur, mais l'application peut toujours rencontrer des problèmes de performances.

Traceview

Vous pouvez utiliser Traceview pour obtenir une trace de votre application en cours d'exécution tout en parcourant les cas d'utilisation et identifier les emplacements où le thread principal est occupé. Pour en savoir plus sur l'utilisation de Traceview, consultez Profiler avec Traceview et dmtracedump.

Récupérer un fichier de traces

Android stocke les informations de trace en cas d'erreur ANR. Pour les versions d'OS plus anciennes, un seul fichier /data/anr/traces.txt est installé sur l'appareil. Dans les versions d'OS plus récentes, il existe plusieurs fichiers /data/anr/anr_*. Vous pouvez accéder aux traces ANR d'un appareil ou d'un émulateur en utilisant Android Debug Bridge (adb) en tant que racine :

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

Vous pouvez générer un rapport de bug à partir d'un appareil physique à l'aide de l'option « Créer un rapport de bug » du développeur sur l'appareil ou de la commande « adb bugreport » sur votre ordinateur de développement. Pour en savoir plus, consultez Capturer et lire les rapports de bug.

Résoudre les problèmes

Une fois que vous avez identifié le problème, vous pouvez utiliser les conseils de cette section pour le résoudre.

Lenteur du code dans le thread principal

Identifiez les emplacements de votre code où le thread principal de l'application est occupé pendant plus de cinq secondes. Recherchez les cas d'utilisation suspects dans votre application et essayez de reproduire l'erreur ANR.

Par exemple, la figure 2 illustre une chronologie Traceview dans laquelle le thread principal est occupé pendant plus de cinq secondes.

Figure 2. Chronologie de Traceview montrant un thread principal occupé

Figure 2. Chronologie de Traceview montrant un thread principal occupé

La figure 2 montre que la majeure partie du code incriminé se produit dans le gestionnaire onClick(View), comme indiqué dans l'exemple de code suivant :

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

Dans ce cas, vous devez déplacer la tâche qui s'exécute dans le thread principal vers un thread de nœud de calcul. Le framework Android inclut des classes permettant de déplacer la tâche vers un thread de nœud de calcul. Pour en savoir plus, consultez Threads de calcul.

E/S sur le thread principal

L'exécution d'opérations d'E/S sur le thread principal est une cause fréquente de lenteur, ce qui peut entraîner des erreurs ANR. Il est recommandé de déplacer toutes les opérations d'E/S vers un thread de nœud de calcul, comme indiqué dans la section précédente.

Les opérations réseau et de stockage sont des exemples d'E/S. Pour en savoir plus, consultez Effectuer des opérations réseau et Enregistrer des données.

Conflits de verrouillage

Dans certains cas, la tâche à l'origine de l'erreur ANR n'est pas directement exécutée sur le thread principal de l'application. Si un thread de calcul applique un verrouillage sur une ressource dont le thread principal a besoin pour effectuer sa tâche, une erreur ANR peut se produire.

Par exemple, la figure 4 illustre une chronologie Traceview dans laquelle la plupart des tâches sont effectuées sur un thread de calcul.

Figure 4. Chronologie Traceview affichant la tâche en cours d&#39;exécution sur un thread de nœud de calcul

Figure 4. Chronologie Traceview affichant la tâche en cours d'exécution sur un thread de nœud de calcul

Toutefois, si vos utilisateurs rencontrent toujours des erreurs ANR, vous devez consulter l'état du thread principal dans Android Device Monitor. En règle générale, le thread principal est à l'état RUNNABLE s'il est prêt à mettre à jour l'UI et s'il est généralement responsif.

Toutefois, si le thread principal ne peut pas reprendre l'exécution, cela signifie qu'il est à l'état BLOCKED et qu'il ne peut pas répondre aux événements. L'état indique Monitor (Surveiller) ou Wait (Patienter) dans Android Device Monitor, comme illustré dans la figure 5.

Figure 5. Thread principal à l&#39;état de surveillance

Figure 5. Thread principal à l'état de surveillance

La trace suivante montre le thread principal d'une application qui est bloqué en attente d'une ressource :

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

L'examen de la trace peut vous aider à localiser le code qui bloque le thread principal. Le code suivant est responsable du verrouillage qui bloque le thread principal dans la trace précédente :

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

Le thread principal d'une application qui attend le résultat d'un thread de nœud de calcul constitue un autre exemple, comme illustré dans le code suivant. Notez que l'utilisation de wait() et notify() n'est pas un modèle recommandé dans Kotlin, qui possède ses propres mécanismes de gestion de la simultanéité. Si vous utilisez Kotlin, vous devez si possible utiliser des mécanismes spécifiques à Kotlin.

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

D'autres situations peuvent bloquer le thread principal, y compris les threads qui utilisent Lock et Semaphore, ainsi qu'un pool de ressources (tel qu'un pool de connexions de base de données) ou d'autres mécanismes d'exclusion mutuelle (mutex).

Vous devez évaluer les verrous que votre application détient sur les ressources en général, mais si vous souhaitez éviter les erreurs ANR, vous devez examiner le verrouillage des ressources requises par le thread principal.

Assurez-vous que les verrous sont conservés pendant le moins de temps possible ou, mieux encore, évaluez si l'application requiert véritablement un blocage. Si vous utilisez le verrouillage pour déterminer quand mettre à jour l'UI en fonction du traitement d'un thread de travail, utilisez des mécanismes tels que onProgressUpdate() et onPostExecute() pour communiquer entre le thread de travail et le thread principal.

Interblocages

Un interblocage se produit lorsqu'un thread passe à l'état d'attente, car une ressource requise est détenue par un autre thread, qui attend également une ressource détenue par le premier thread. Si le thread principal de l'application se trouve dans cette situation, il est probable que des erreurs ANR se produisent.

Les interblocages sont un phénomène bien étudié en informatique. Il existe des algorithmes de prévention des interblocages que vous pouvez utiliser pour éviter ce problème.

Pour en savoir plus, consultez les articles sur l'interblocage et la prévention des interblocages sur Wikipédia.

Broadcast receivers lents

Les applications peuvent répondre aux messages de diffusion, par exemple en activant ou en désactivant le mode Avion, ou en cas de changement d'état de la connectivité, à l'aide de broadcast receivers. Une erreur ANR se produit lorsqu'une application prend trop de temps pour traiter le message de diffusion.

Une erreur ANR se produit dans les cas suivants :

Votre application ne doit effectuer des opérations courtes que dans la méthode onReceive() d'un BroadcastReceiver. Toutefois, si votre application nécessite un traitement plus complexe à la suite d'un message de diffusion, vous devez reporter la tâche vers un IntentService.

Vous pouvez utiliser des outils comme Traceview pour déterminer si votre broadcast receiver exécute des opérations de longue durée sur le thread principal de l'application. Par exemple, la figure 6 illustre la chronologie d'un broadcast receiver qui traite un message sur le thread principal pendant environ 100 secondes.

Figure 6. Chronologie Traceview affichant la tâche &quot;BroadcastReceiver&quot; sur le thread principal

Figure 6. Chronologie Traceview affichant la tâche BroadcastReceiver sur le thread principal

Ce comportement peut être dû à l'exécution d'opérations de longue durée sur la méthode onReceive() du BroadcastReceiver, comme indiqué dans l'exemple suivant :

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

Dans de telles situations, il est recommandé de déplacer l'opération de longue durée vers un IntentService, puisqu'un thread de travail est utilisé pour exécuter sa tâche. Le code suivant montre comment traiter une opération de longue durée à l'aide d'un IntentService :

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

L'utilisation de l'IntentService permet d'exécuter l'opération de longue durée sur un thread de travail plutôt que sur le thread principal. La figure 7 présente la tâche différée dans le thread de travail dans la chronologie Traceview.

Figure 7. Chronologie Traceview affichant le message de diffusion traité sur un thread de travail

Figure 7. Chronologie Traceview affichant le message de diffusion traité sur un thread de travail

Votre broadcast receiver peut utiliser goAsync() pour signaler au système qu'il a besoin de plus de temps pour traiter le message. Toutefois, vous devez appeler finish() sur l'objet PendingResult. L'exemple suivant montre comment appeler finish() pour permettre au système de recycler le broadcast receiver et d'éviter une erreur 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);

Toutefois, le déplacement du code d'un broadcast receiver lent vers un autre thread et l'utilisation de goAsync() ne permettent pas de résoudre les erreurs ANR si la diffusion s'exécute en arrière-plan. Le délai avant expiration de l'ANR s'applique toujours.

GameActivity

La bibliothèque GameActivity a réduit le nombre d'erreurs ANR dans les études de cas de jeux et d'applications écrits en C ou C++. Si vous remplacez votre activité native existante par GameActivity, vous pouvez réduire le blocage des threads UI et empêcher certaines erreurs ANR.

Pour en savoir plus sur les erreurs ANR, consultez Garder une application responsive. Pour en savoir plus sur les threads, consultez Performances des threads.