android.os.NetworkOnMainThreadException significa que una aplicación Android intentó ejecutar una operación de red en el hilo principal de la interfaz de usuario. Android genera esta excepción en aplicaciones orientadas al SDK de Honeycomb, nivel de API 11, o posterior. La primera medida más segura no es suprimir la excepción: inspecciona la ruta de llamadas que falla, identifica la primera operación de red bloqueante que pertenezca a la aplicación y determina si la API cliente implicada es síncrona o ya se puede invocar de forma segura desde el hilo principal.

Qué significa android.os.NetworkOnMainThreadException

La excepción exacta es android.os.NetworkOnMainThreadException. Según la referencia de NetworkOnMainThreadException de Android, se genera cuando una aplicación intenta realizar operaciones de red en su hilo principal. Se incorporó en el nivel de API 11 y se genera en aplicaciones orientadas al SDK de Honeycomb o posterior.

La operación desencadenante puede ser una conexión URL bloqueante, URL.openStream(), acceso a sockets, resolución de nombres o una llamada síncrona de un cliente. El código con corrutinas también puede provocar la excepción si invoca operaciones de E/S bloqueantes sin cambiar a un dispatcher de segundo plano adecuado. Marcar una función como suspend no traslada por sí solo ese trabajo fuera del hilo principal.

Bloquear el hilo principal impide que procese eventos de la interfaz mientras se ejecuta la operación, lo que puede reducir la capacidad de respuesta y provocar que la aplicación no responda. Las aplicaciones orientadas a niveles de SDK anteriores no estaban sujetas a esta excepción de la misma manera, pero eso no hace que las operaciones de red en el hilo principal sean seguras ni recomendables.

Localiza la llamada de red bloqueante en la ruta que falla

Utiliza el seguimiento de pila del fallo reproducible para localizar la operación síncrona antes de cambiar el modelo de ejecución. Los nombres y el orden exactos de los marcos varían según la aplicación y la biblioteca de red, por lo que este paso requiere confirmación específica en el proyecto.

  1. Reproduce la operación que genera android.os.NetworkOnMainThreadException y conserva el seguimiento de pila completo.
  2. Lee desde la excepción hacia los marcos de la aplicación. Busca el primer marco perteneciente a tu proyecto que conduzca a una conexión URL, URL.openStream(), acceso a sockets, resolución de nombres o una llamada síncrona de un cliente.
  3. Consulta la documentación de la biblioteca implicada para determinar si ese método es bloqueante, utiliza callbacks o es una API suspend documentada como segura para el hilo principal.
  4. Rastrea el contexto de ejecución de quien realiza la llamada. Las rutas iniciadas desde una actividad, un fragmento, un callback de un composable o viewModelScope suelen comenzar en el hilo principal, pero debes comprobar la ruta real del proyecto.
  5. Selecciona una de las soluciones correspondientes que aparecen a continuación, en lugar de envolver todas las API de red en otro mecanismo de ejecución.

Un seguimiento de pila puede mostrar StrictMode$AndroidBlockGuardPolicy.onNetwork por encima de un marco perteneciente a la aplicación. Este es un patrón de diagnóstico habitual, no una secuencia universal de marcos. El marco útil es la llamada del proyecto que inició la operación bloqueante.

Utiliza StrictMode solo cuando necesites un diagnóstico adicional durante la depuración

Cuándo se aplica: Utiliza StrictMode durante las pruebas locales cuando el fallo existente no muestre con claridad el acceso accidental a la red desde el hilo principal.

Requisitos previos: Utiliza una compilación de depuración o un entorno de pruebas local y prevé eliminar o relajar la política de diagnóstico después de la verificación.

  1. Activa StrictMode en la compilación de depuración.
  2. Ejecuta el flujo que falla.
  3. Inspecciona la infracción notificada e identifica la primera llamada de red bloqueante perteneciente a la aplicación.
  4. Corrige el hilo responsable de la llamada en lugar de suprimir la infracción.

Resultado esperado: El diagnóstico identifica una ruta perteneciente a la aplicación que puede asignarse a la API asíncrona, al dispatcher o a la solución con Executor correspondiente.

Riesgo y reversión: El riesgo es bajo y no se espera pérdida de datos. Desactiva la política exclusiva de depuración después de verificar la corrección. No utilices permitAll() ni permitNetwork() como solución en producción.

Elige la solución correspondiente a la API de red

Utiliza la opción aplicable que implique menos cambios. Primero determina si el cliente ya ejecuta las operaciones de forma asíncrona. Añade un dispatcher o un Executor únicamente cuando la operación subyacente sea bloqueante.

Solución 1: Utiliza directamente una API cliente que ya sea asíncrona o segura para el hilo principal

Cuándo se aplica: Utiliza esta opción cuando la documentación de la biblioteca indique que su API con callback o suspend ya gestiona la ejecución en segundo plano y se puede invocar de forma segura desde código que se ejecuta en el hilo principal.

Requisitos previos: Confirma este comportamiento en la documentación de la biblioteca cliente. No des por hecho que todos los métodos marcados como suspend son seguros para el hilo principal.

  1. Da preferencia a la API asíncrona con callback o a la API suspend documentada como segura para el hilo principal.
  2. Invoca esa API desde código seguro para el hilo principal.
  3. No añadas un contenedor redundante con withContext(Dispatchers.IO) alrededor de una API que ya esté documentada como segura para el hilo principal.
  4. Reanuda el trabajo de la interfaz en el hilo principal cuando termine la operación.

Resultado esperado: La biblioteca realiza su operación de red sin bloquear el hilo principal de Android, mientras que quien realizó la llamada procesa el resultado en el contexto correcto de la interfaz.

Riesgo y reversión: El riesgo es bajo y no se espera pérdida de datos. Si la documentación o las pruebas muestran que la API no es realmente segura para el hilo principal, deja de utilizar esta opción y traslada la capa bloqueante fuera del hilo principal. La documentación sobre corrutinas de Kotlin en Android distingue entre el código bloqueante que necesita Dispatchers.IO y las API de red que ya ofrecen un comportamiento suspend seguro para el hilo principal.

Solución 2: Traslada el trabajo de red bloqueante de Kotlin a withContext(Dispatchers.IO)

Cuándo se aplica: Utiliza esta opción para una función suspend de Kotlin que invoque directamente código bloqueante de red o resolución de nombres.

Requisitos previos: Debes controlar la función suspend o la capa de repositorio, y la operación subyacente debe ser bloqueante en lugar de ser ya segura para el hilo principal.

  1. Mantén a quien realiza la llamada en el hilo principal cuando sea responsable del estado de la interfaz.
  2. Envuelve únicamente el bloque de red bloqueante en withContext(Dispatchers.IO) o utiliza el dispatcher de E/S inyectado en el proyecto.
  3. Devuelve únicamente el resultado de la operación a quien realizó la llamada.
  4. Actualiza el estado de la interfaz después de que finalice la llamada suspend y la ejecución regrese al hilo principal.

Resultado esperado: La E/S bloqueante se ejecuta en el dispatcher de E/S, mientras que el estado de la interfaz sigue siendo responsabilidad del hilo principal.

Riesgo y reversión: El riesgo es bajo y no se espera pérdida de datos. Elimina el cambio de dispatcher únicamente si se confirma que la biblioteca proporciona una API suspend segura para el hilo principal. Las prácticas recomendadas para corrutinas en Android establecen que las funciones suspend que puedan invocarse desde el hilo principal deben ser seguras para dicho hilo y señalan que viewModelScope normalmente comienza en Dispatchers.Main.

Solución 3: Utiliza un Executor de Java y devuelve el resultado al hilo principal

Cuándo se aplica: Utiliza esta opción para código Java que realice directamente operaciones de E/S de red bloqueantes.

Requisitos previos: La aplicación debe disponer de un Executor o un grupo de hilos en segundo plano, además de una ruta de callback en el hilo principal para actualizar la interfaz.

  1. Envía la tarea de red bloqueante al Executor.
  2. Realiza la llamada de red dentro de esa tarea en segundo plano, no desde el hilo principal.
  3. Devuelve el resultado mediante la ruta de callbacks del hilo principal de la aplicación.
  4. Actualiza las vistas únicamente desde ese callback del hilo principal.

Resultado esperado: La operación bloqueante se ejecuta mediante el Executor y solo el trabajo resultante de la interfaz vuelve al hilo principal.

Riesgo y reversión: El riesgo es bajo y no se espera pérdida de datos. Cancela la tarea o deja de enviar trabajo nuevo cuando la interfaz que la inició ya no exista. Trasladar la operación de red fuera del hilo principal no permite actualizar las vistas de forma segura desde la tarea en segundo plano.

Gestiona el ciclo de vida y el trabajo persistente sin crear otro error

Asignar correctamente el hilo no hace que una solicitud sea automáticamente segura respecto al ciclo de vida. Una solicitud puede sobrevivir a su actividad o fragmento, devolver datos a una interfaz obsoleta o duplicar trabajo si se repite la acción que la inició. La cancelación, el tratamiento de la destrucción y la política sobre solicitudes duplicadas dependen de la arquitectura de la aplicación y deben comprobarse en el proyecto de destino.

  • Comprueba qué ocurre si la actividad o el fragmento se destruyen mientras el trabajo sigue activo.
  • Asegúrate de que el trabajo en segundo plano no actualice vistas después de que desaparezca la interfaz relacionada.
  • Comprueba si las acciones repetidas pueden enviar solicitudes duplicadas.
  • Cancela el trabajo o deja de aceptar su resultado cuando el ciclo de vida responsable ya no lo necesite.

Utiliza WorkManager solo para trabajo persistente o aplazable

Cuándo se aplica: Utiliza WorkManager cuando la tarea de red sea aplazable, de larga duración o deba sobrevivir a los reinicios de la aplicación. No es el sustituto predeterminado para todas las solicitudes inmediatas iniciadas desde la interfaz.

Requisitos previos: La operación debe poder expresarse como trabajo en segundo plano, en lugar de ser una solicitud inmediata vinculada a la pantalla actual.

  1. Crea una WorkRequest o un Worker para la operación en segundo plano.
  2. Añade restricciones de red si la tarea las necesita.
  3. Deja que WorkManager ejecute la operación fuera del hilo de la interfaz.
  4. Observa su finalización y actualiza la interfaz por separado.

Resultado esperado: El trabajo persistente o aplazable se gestiona de forma independiente de la pantalla que lo inició y no bloquea el hilo principal.

Riesgo y reversión: El riesgo es bajo y no se espera pérdida de datos. Cancela la WorkRequest cuando la tarea deje de ser necesaria. Si la solicitud necesita una respuesta inmediata para la interfaz actual, utiliza la API asíncrona, la corrutina o la opción con Executor que corresponda.

Comprueba que la excepción esté solucionada

  1. Reproduce el flujo que fallaba originalmente después de aplicar una de las soluciones correspondientes.
  2. Confirma que la operación bloqueante de red o resolución de nombres ya no se ejecute en el hilo principal.
  3. Confirma que las vistas y el estado de la interfaz solo se actualicen después de que el resultado vuelva al hilo principal.
  4. Comprueba qué sucede cuando la actividad o el fragmento que iniciaron la operación se destruyen antes de que termine.
  5. Prueba entradas repetidas para determinar si se crean solicitudes duplicadas o se controlan correctamente.
  6. En una compilación de depuración, utiliza StrictMode para comprobar si quedan accesos accidentales a la red desde el hilo principal y, después, restaura la política de depuración prevista.
  7. Revisa el seguimiento de pila resultante si aparece otra excepción. Diagnostica la nueva firma por separado en lugar de tratarla como otra instancia de NetworkOnMainThreadException.

La corrección de los hilos queda verificada cuando el flujo original deja de realizar trabajo de red bloqueante en el hilo principal y los cambios relacionados con la interfaz siguen ejecutándose en ese hilo. Una respuesta correcta del endpoint no demuestra por sí sola que el hilo sea el adecuado, y eliminar esta excepción no garantiza que la solicitud de red vaya a completarse correctamente.

Errores y alternativas que no solucionan esta excepción

Mensaje o método Por qué es diferente
UnknownHostException Es un fallo diferente relacionado con la resolución o la accesibilidad del host; no demuestra que el trabajo de red se haya ejecutado en el hilo principal.
SSLHandshakeException Es un fallo de TLS independiente y requiere su propio diagnóstico.
Falta el permiso INTERNET Es un problema de permisos o configuración, no la causa que define NetworkOnMainThreadException.
Fallo de la política de tráfico sin cifrar Es una restricción de la política de seguridad de red sobre el tráfico sin cifrar, no una infracción por ejecutar trabajo en el hilo principal.
CalledFromWrongThreadException Se refiere al acceso a vistas desde un hilo incorrecto después de cambiar el contexto de ejecución; no es la misma excepción.
permitAll() o permitNetwork() Estas opciones relajan la restricción de diagnóstico en lugar de corregir el hilo responsable de la operación bloqueante.
Instrucciones obsoletas sobre AsyncTask AsyncTask está obsoleto y no es la solución moderna recomendada.

No comiences diagnosticando TLS, accesibilidad DNS, permisos o tráfico sin cifrar cuando el fallo real sea android.os.NetworkOnMainThreadException. Corrige primero el hilo responsable. Si trasladar una solicitud HTTP fuera del hilo principal revela después el fallo Cleartext HTTP traffic not permitted, diagnostica ese nuevo error por separado.