android.os.NetworkOnMainThreadException signifie qu’une application Android a tenté d’effectuer une opération réseau sur le thread principal de son interface utilisateur. Android lève cette exception pour les applications ciblant le SDK Honeycomb, soit le niveau d’API 11, ou une version ultérieure. La première mesure, et la plus sûre, ne consiste pas à neutraliser l’exception : examinez le chemin d’appel en échec, repérez la première opération réseau bloquante appartenant à l’application et déterminez si l’API cliente concernée est synchrone ou déjà compatible avec le thread principal.

Signification de android.os.NetworkOnMainThreadException

L’exception exacte est android.os.NetworkOnMainThreadException. D’après la documentation de référence Android sur NetworkOnMainThreadException, elle est levée lorsqu’une application tente d’effectuer une opération réseau sur son thread principal. Elle a été ajoutée au niveau d’API 11 et concerne les applications ciblant le SDK Honeycomb ou une version ultérieure.

L’opération à l’origine de l’exception peut être une connexion URL bloquante, URL.openStream(), un accès à un socket, une résolution de nom ou un appel synchrone à un client. Du code utilisant des coroutines peut également déclencher l’exception s’il lance des E/S bloquantes sans basculer vers un répartiteur d’arrière-plan approprié. Le seul fait qu’une fonction soit déclarée avec suspend ne déplace pas son exécution hors du thread principal.

Le blocage du thread principal l’empêche de traiter les événements de l’interface pendant l’opération, ce qui peut rendre l’application peu réactive ou provoquer une erreur « Application ne répond pas ». Les applications ciblant des niveaux de SDK antérieurs n’étaient pas soumises à cette exception de la même manière, mais l’exécution d’opérations réseau sur le thread principal n’en est pas pour autant sûre ni recommandée.

Repérer l’appel réseau bloquant dans le chemin en échec

Avant de modifier le modèle d’exécution, utilisez la trace de la pile obtenue lors d’un échec reproductible pour localiser l’opération synchrone. Le nom et l’ordre exacts des frames varient selon l’application et la bibliothèque réseau ; cette étape doit donc être confirmée dans le projet concerné.

  1. Reproduisez l’opération qui signale android.os.NetworkOnMainThreadException et conservez la trace complète de la pile.
  2. Parcourez la trace depuis l’exception jusqu’aux frames de l’application. Recherchez la première frame appartenant à votre projet qui mène à une connexion URL, à URL.openStream(), à un accès à un socket, à une résolution de nom ou à un appel synchrone à un client.
  3. Consultez la documentation de la bibliothèque concernée pour déterminer si la méthode est bloquante, utilise des fonctions de rappel ou constitue une API suspendue explicitement compatible avec le thread principal.
  4. Remontez le contexte d’exécution de l’appelant. Les chemins issus d’une activité, d’un fragment, d’un rappel de composable ou de viewModelScope commencent souvent sur le thread principal, mais le chemin réel du projet doit être vérifié.
  5. Choisissez une seule correction adaptée parmi celles ci-dessous, au lieu d’encapsuler chaque API réseau dans un mécanisme d’exécution supplémentaire.

Une trace de la pile peut afficher StrictMode$AndroidBlockGuardPolicy.onNetwork au-dessus d’une frame appartenant à l’application. Il s’agit d’un schéma de diagnostic courant, mais pas d’une séquence universelle. La frame utile est l’appel du projet qui a lancé l’opération bloquante.

Utiliser StrictMode uniquement si un diagnostic supplémentaire est nécessaire

Quand utiliser cette méthode : utilisez StrictMode pendant les tests locaux lorsque l’échec existant ne révèle pas clairement un accès réseau involontaire sur le thread principal.

Conditions préalables : utilisez une version de débogage ou un environnement de test local, et prévoyez de supprimer ou d’assouplir la règle de diagnostic après la vérification.

  1. Activez StrictMode dans la version de débogage.
  2. Déclenchez le scénario en échec.
  3. Examinez l’infraction signalée et repérez le premier appel réseau bloquant appartenant à l’application.
  4. Corrigez le thread sur lequel l’appel s’exécute au lieu de neutraliser l’infraction.

Résultat attendu : le diagnostic met en évidence un chemin appartenant à l’application, auquel peut être appliquée l’API asynchrone, le répartiteur ou l’Executor approprié.

Risques et retour en arrière : le risque est faible et aucune perte de données n’est attendue. Désactivez la règle réservée au débogage après avoir vérifié la correction. N’utilisez pas permitAll() ou permitNetwork() comme correction en production.

Choisir la correction adaptée à l’API réseau

Utilisez la solution applicable la moins invasive. Commencez par déterminer si le client effectue déjà l’opération de manière asynchrone. N’ajoutez un répartiteur ou un Executor que si l’opération sous-jacente est bloquante.

Correction 1 : utiliser directement une API cliente déjà asynchrone ou compatible avec le thread principal

Quand utiliser cette méthode : choisissez cette solution lorsque la documentation de la bibliothèque indique que son API à fonctions de rappel ou son API suspendue gère déjà l’exécution en arrière-plan et peut être appelée depuis du code exécuté sur le thread principal.

Conditions préalables : confirmez ce comportement dans la documentation de la bibliothèque cliente. Ne supposez pas que toutes les méthodes déclarées avec suspend sont compatibles avec le thread principal.

  1. Privilégiez l’API asynchrone à fonctions de rappel ou l’API suspendue explicitement compatible avec le thread principal proposée par la bibliothèque.
  2. Appelez cette API depuis du code pouvant s’exécuter sur le thread principal.
  3. N’ajoutez pas inutilement de bloc withContext(Dispatchers.IO) autour d’une API déjà documentée comme compatible avec le thread principal.
  4. Reprenez les opérations liées à l’interface sur le thread principal une fois l’opération terminée.

Résultat attendu : la bibliothèque effectue son opération réseau sans bloquer le thread principal d’Android, tandis que l’appelant traite le résultat dans le contexte d’interface approprié.

Risques et retour en arrière : le risque est faible et aucune perte de données n’est attendue. Si la documentation ou les tests montrent que l’API n’est pas réellement compatible avec le thread principal, cessez d’utiliser cette solution et déplacez plutôt la couche bloquante hors de Main. La documentation sur les coroutines Kotlin sur Android distingue le code bloquant, qui nécessite Dispatchers.IO, des API réseau suspendues déjà conçues pour être appelées depuis le thread principal.

Correction 2 : déplacer les opérations réseau Kotlin bloquantes dans withContext(Dispatchers.IO)

Quand utiliser cette méthode : choisissez cette solution pour une fonction suspendue Kotlin qui appelle directement du code bloquant de réseau ou de résolution de nom.

Conditions préalables : vous devez contrôler la fonction suspendue ou la couche de dépôt, et l’opération sous-jacente doit être bloquante plutôt que déjà compatible avec le thread principal.

  1. Laissez l’appelant sur Main lorsqu’il est responsable de l’état de l’interface.
  2. Encapsulez uniquement le bloc réseau bloquant dans withContext(Dispatchers.IO), ou utilisez le répartiteur d’E/S injecté dans le projet.
  3. Renvoyez uniquement le résultat de l’opération à l’appelant.
  4. Mettez à jour l’état de l’interface après la fin de l’appel suspendu, une fois l’exécution revenue sur Main.

Résultat attendu : les E/S bloquantes s’exécutent sur le répartiteur d’E/S, tandis que l’état de l’interface reste géré par le thread principal.

Risques et retour en arrière : le risque est faible et aucune perte de données n’est attendue. Ne supprimez le changement de répartiteur que si la bibliothèque fournit de manière confirmée une API suspendue compatible avec le thread principal. Les bonnes pratiques concernant les coroutines sur Android imposent que les fonctions suspendues susceptibles d’être appelées depuis Main puissent y être appelées sans le bloquer, et indiquent que viewModelScope commence normalement sur Dispatchers.Main.

Correction 3 : utiliser un Executor Java et renvoyer le résultat vers Main

Quand utiliser cette méthode : choisissez cette solution pour du code Java qui effectue directement des E/S réseau bloquantes.

Conditions préalables : l’application doit disposer d’un Executor ou d’un pool de threads d’arrière-plan, ainsi que d’un mécanisme de rappel sur le thread principal pour mettre à jour l’interface.

  1. Soumettez la tâche réseau bloquante à l’Executor.
  2. Effectuez l’appel réseau dans cette tâche d’arrière-plan plutôt que sur le thread principal.
  3. Renvoyez le résultat par le mécanisme de rappel de l’application exécuté sur le thread principal.
  4. Ne mettez les vues à jour que depuis ce rappel exécuté sur le thread principal.

Résultat attendu : l’opération bloquante s’exécute par l’intermédiaire de l’Executor, et seules les opérations d’interface qui utilisent le résultat reviennent sur Main.

Risques et retour en arrière : le risque est faible et aucune perte de données n’est attendue. Annulez la tâche ou cessez de soumettre de nouvelles opérations lorsque l’interface qui les a lancées n’existe plus. Le déplacement de l’opération réseau hors de Main n’autorise pas la mise à jour des vues depuis la tâche d’arrière-plan.

Gérer le cycle de vie et les tâches persistantes sans créer un second problème

Le choix du bon thread d’exécution ne garantit pas qu’une requête respecte le cycle de vie. Une requête peut continuer après la disparition de son activité ou de son fragment, renvoyer un résultat à une interface obsolète ou être exécutée plusieurs fois si l’action initiale est répétée. Les règles d’annulation, la gestion de la destruction et la prévention des requêtes en double dépendent de l’architecture de l’application et doivent être vérifiées dans le projet concerné.

  • Vérifiez ce qui se passe si l’activité ou le fragment est détruit pendant que l’opération reste active.
  • Assurez-vous qu’une tâche d’arrière-plan ne met pas à jour les vues après la disparition de l’interface correspondante.
  • Vérifiez si des actions répétées peuvent envoyer plusieurs requêtes identiques.
  • Annulez l’opération, ou cessez d’accepter son résultat, lorsque son cycle de vie n’en a plus besoin.

Utiliser WorkManager uniquement pour les tâches persistantes ou différables

Quand utiliser cette méthode : utilisez WorkManager lorsque la tâche réseau peut être différée, qu’elle est longue ou qu’elle doit survivre aux redémarrages de l’application. WorkManager ne remplace pas par défaut toutes les requêtes immédiates déclenchées depuis l’interface.

Conditions préalables : l’opération doit pouvoir être représentée comme une tâche d’arrière-plan, et non comme une requête immédiate liée à l’écran actuel.

  1. Créez une WorkRequest ou un Worker pour l’opération d’arrière-plan.
  2. Ajoutez des contraintes réseau si la tâche en a besoin.
  3. Laissez WorkManager exécuter l’opération en dehors du thread de l’interface.
  4. Observez la fin de l’opération et mettez l’interface à jour séparément.

Résultat attendu : la tâche persistante ou différable est gérée indépendamment de l’écran qui l’a déclenchée et ne bloque pas le thread principal.

Risques et retour en arrière : le risque est faible et aucune perte de données n’est attendue. Annulez la WorkRequest lorsque la tâche n’est plus nécessaire. Si la requête exige une réponse immédiate pour l’interface actuelle, utilisez plutôt l’API asynchrone, la coroutine ou l’Executor applicable.

Vérifier que l’exception est corrigée

  1. Reproduisez le scénario initialement en échec après avoir appliqué une seule correction appropriée.
  2. Confirmez que l’opération bloquante de réseau ou de résolution de nom ne s’exécute plus sur le thread principal.
  3. Confirmez que les vues et l’état de l’interface ne sont mis à jour qu’après le retour du résultat sur Main.
  4. Testez le comportement lorsque l’activité ou le fragment à l’origine de l’opération est détruit avant la fin de celle-ci.
  5. Testez des entrées répétées pour déterminer si elles créent des requêtes en double ou si celles-ci sont correctement contrôlées.
  6. Dans une version de débogage, utilisez StrictMode pour rechercher d’autres accès réseau involontaires sur le thread principal, puis rétablissez la règle de débogage prévue.
  7. Si une autre exception apparaît, examinez la nouvelle trace de la pile. Diagnostiquez cette nouvelle signature séparément au lieu de la traiter comme une autre occurrence de NetworkOnMainThreadException.

La correction du modèle d’exécution est vérifiée lorsque le scénario initial n’effectue plus d’opération réseau bloquante sur Main et que les modifications de l’interface associées ont toujours lieu sur Main. Une réponse correcte du serveur ne prouve pas à elle seule que les bons threads sont utilisés. La disparition de cette exception ne garantit pas non plus que la requête réseau aboutira.

Erreurs et contournements qui ne correspondent pas à cette correction

Message ou méthode Pourquoi le problème est différent
UnknownHostException Il s’agit d’un autre échec, lié à la résolution ou à l’accessibilité de l’hôte. Il ne prouve pas qu’une opération réseau s’est exécutée sur Main.
SSLHandshakeException Il s’agit d’un échec TLS distinct, qui nécessite son propre diagnostic.
Autorisation INTERNET manquante Il s’agit d’un problème d’autorisation ou de configuration, et non de la cause caractéristique de NetworkOnMainThreadException.
Échec lié à la règle sur le trafic en clair Il s’agit d’une restriction de la politique de sécurité réseau visant le trafic en clair, et non d’une infraction liée à une exécution sur le thread principal.
CalledFromWrongThreadException Cette exception concerne l’accès aux vues depuis le mauvais thread après un changement de contexte. Ce n’est pas la même exception.
permitAll() ou permitNetwork() Ces méthodes assouplissent la restriction de diagnostic au lieu de corriger le thread d’exécution de l’opération bloquante.
Recommandations obsolètes fondées sur AsyncTask AsyncTask est obsolète et ne constitue pas la correction moderne recommandée.

Ne commencez pas par diagnostiquer TLS, l’accessibilité DNS, les autorisations ou le trafic en clair lorsque l’échec réel est android.os.NetworkOnMainThreadException. Corrigez d’abord le thread d’exécution. Si le déplacement d’une requête HTTP hors de Main révèle ensuite une erreur distincte « Cleartext HTTP traffic not permitted », diagnostiquez ce nouveau problème séparément.