Concepts et implémentation de Jetpack Compose
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 l'arrêt de l'application.
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 Présentation des processus et des 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
Service.onCreate()etService.onStartCommand()/Service.onBind()en quelques secondes. **Service.startForeground()non appelé**: si votre application utiliseContext.startForegroundService()pour démarrer un nouveau service au premier plan mais que le service n'appelle passtartForeground()dans les cinq secondes.- Diffusion de l'intent : si l'exécution d'un
BroadcastReceivern'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. **JobSchedulerinteractions**: si unJobServicene renvoie pas deJobService.onStartJob()ouJobService.onStopJob()en quelques secondes, ou si une tâche lancée par l'utilisateur démarre et que votre application n'appelle pasJobService.setNotification()quelques secondes après l'appel deJobService.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.
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é
La figure 2 montre que la majeure partie du code problématique se produit dans le
onClick(View) gestionnaire, comme illustré 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 travail. 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. Dans Compose, les développeurs déclenchent souvent accidentellement des lectures de disque (comme SharedPreferences ou des appels de base de données) lorsqu'ils tentent de dériver l'état initial.
Exécutez les opérations d'E/S de longue durée en dehors de la couche d'interface utilisateur. Utilisez
withContext(Dispatchers.IO) dans un ViewModel ou, mieux encore, utilisez un dépôt
sur la couche de données. 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.
Conflit 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 travail 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 3 illustre une chronologie Traceview dans laquelle la plupart des tâches sont effectuées sur un thread de travail.

Figure 3 : 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 4 : 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 travail 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.
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 :
- Un broadcast receiver n'a pas fini d'exécuter sa
onReceiveméthode dans un délai considérable. - Un broadcast receiver appelle
goAsyncet ne parvient pas à appelerfinishsur l'objetPendingResult.
Votre application ne doit effectuer que des opérations courtes dans la onReceive méthode de
a 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
ViewModel (en exploitant la puissance des coroutines, des étendues et des dispatchers Kotlin)
si la tâche devrait prendre quelques secondes au maximum, tout type de détenteur d'état,
ou vers WorkManager pour les tâches qui devraient prendre plus de quelques secondes.
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 5 : 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
onReceive() méthode du BroadcastReceiver, comme illustré 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 utiliser un IntentService pour traiter une
opération de longue durée :
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 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 6 : 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'PendingResult objet. 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.