Cómo identificar, diagnosticar y solucionar errores ANR

  • Análisis de las causas raíz que provocan la congelación de la interfaz de usuario y el bloqueo del hilo principal.
  • Metodologías de diagnóstico mediante herramientas oficiales como Android Vitals, Crashlytics y registros de Perfetto.
  • Estrategias de optimización de código para trasladar operaciones pesadas a hilos secundarios y evitar tiempos de espera.

Cómo identificar, diagnosticar y solucionar errores ANR

Seguro que te ha pasado: estás usando una app a tope y, de repente, todo se queda congelado. Tras unos segundos de tensión, aparece el temido cuadro de diálogo avisándote de que la aplicación no responde. Para el usuario es una pesadilla y un motivo más para desinstalar la app, pero para nosotros, los desarrolladores, es un reto técnico llamado error ANR que debemos dominar para que nuestra herramienta no acabe en la papelera de reciclaje.

Básicamente, un ANR ocurre cuando el hilo de la interfaz de usuario (UI thread) se queda colgado el tiempo suficiente para que el sistema operativo Android decida que algo va mal. No es un cierre repentino o ‘crash’ común, sino un bloqueo que deja al usuario en el aire. En este artículo vamos a desgranar a fondo cómo detectar estos fallos, dónde buscar la raíz del problema y, lo más importante, cómo darles carpetazo para que la experiencia de navegación sea fluida como la seda.

¿Qué ocurre exactamente cuando aparece un ANR?

El sistema Android es bastante estricto con la capacidad de respuesta. Si el hilo principal, que es el encargado de pintar la pantalla y gestionar los clics, se bloquea durante más de cinco segundos, se dispara el error. Esto sucede porque el hilo principal no puede procesar los eventos de entrada ni actualizar la vista, dejando la aplicación totalmente inerte.

Existen diversos escenarios donde se activa este mecanismo. El más común es el tiempo de espera de envío de entradas, que ocurre cuando el usuario toca la pantalla o pulsa una tecla y la app no reacciona. Pero hay otros casos más técnicos, como cuando un servicio en ejecución no termina de procesar sus métodos de inicio (onCreate o onStartCommand) en el tiempo previsto, o cuando un BroadcastReceiver se queda colgado procesando un mensaje.

Tipos de errores ANR y sus detonantes

Para solucionar el problema, primero hay que saber a qué nos enfrentamos. No todos los bloqueos son iguales:

  • Envío de entradas (Input Dispatching): Es el más visible. El hilo principal está ocupado y no responde a los gestos del usuario. Suele deberse a operaciones de bloqueo o cálculos demasiado pesados en la UI.
  • Receptores de emisión (BroadcastReceiver): Ocurren cuando el receptor no termina su trabajo a tiempo. Dependiendo de si es un intent de prioridad en primer plano o segundo plano, los tiempos de espera varían (desde 10 hasta 120 segundos en versiones recientes).
  • Servicios de ejecución: Sucede si el hilo principal no logra iniciar un servicio rápidamente. Es crítico optimizar los métodos de creación y enlace para evitar que la app se sienta lenta.
  • Proveedores de contenido (Content Providers): Se disparan cuando una consulta a un proveedor remoto tarda demasiado, a menudo por agotamiento de los hilos de Binder.
  • JobScheduler: Si la app no responde a los métodos de inicio o parada de un trabajo, o tarda en lanzar la notificación requerida, el sistema lanzará la alerta.
TDD Android
Artículo relacionado:
Guía Completa de Desarrollo Guiado por Pruebas (TDD) en Android

Herramientas de diagnóstico: ¿Dónde buscar la falla?

No podemos ir a ciegas. Para encontrar el culpable, necesitamos datos reales de dispositivos. La herramienta estrella es Android Vitals dentro de la Play Console, que nos permite monitorizar la tasa de ANR y ver si hemos superado los umbrales de comportamiento inadecuado (como que el 0,47% de los usuarios activos experimenten un bloqueo).

Si buscamos algo más detallado, el diagnóstico y rastreo de errores con Firebase Crashlytics es fundamental. Esta herramienta etiqueta los hilos para decirnos quién es el Triggered ANR (el que activó el error) y quién es el Root Blocking (el hilo que realmente estaba bloqueando al principal). También nos avisa si hay un interbloqueo (Deadlock), donde dos hilos se esperan mutuamente y ninguno avanza.

Para los que prefieren bajar al barro, existen los registros de Perfetto y Traceview. Con Traceview podemos ver el cronograma exacto de ejecución y detectar qué método está ocupando el hilo principal durante más de cinco segundos. Además, mediante ADB podemos extraer los archivos de trazas situados en /data/anr/ para analizarlos en frío.

Estrategias para solucionar y prevenir los bloqueos

Una vez identificado el cuello de botella, el objetivo es liberar el hilo de la interfaz de usuario. La regla de oro es: nunca realices operaciones pesadas en la UI. Esto incluye accesos a bases de datos, peticiones de red o lectura de archivos extensos.

Para corregir los problemas más habituales, podemos aplicar estas medidas:

  • Mover el trabajo a hilos secundarios: Utilizar Worker threads o el framework de concurrencia de Kotlin para que el cálculo complejo no detenga el renderizado de la pantalla.
  • Uso de StrictMode: Esta herramienta es genial durante el desarrollo para detectar operaciones de E/S accidentales que se estén colando en el hilo principal.
  • Optimización de BroadcastReceivers: Si el procesamiento es largo, no lo hagas en onReceive(). Delega la tarea a un IntentService o utiliza goAsync() llamando a finish() lo antes posible.
  • Reducir la contención de bloqueos: Evita que el hilo principal espere por un recurso que un hilo de fondo tiene retenido. Intenta mantener los bloqueos sincronizados durante el menor tiempo posible.

Si trabajas con SDKs, es vital usar el SDK Console. Aquí puedes ver si tu librería es la causa de los ANR en las aplicaciones de terceros y añadir notas aclaratorias para que los desarrolladores sepan cómo integrar tu código sin romper la estabilidad del sistema.

Mantener una tasa de ANR baja no es solo una cuestión técnica, sino una estrategia de negocio, ya que impacta directamente en la visibilidad de la app en la tienda de Google Play y en la retención de usuarios. Al trasladar las tareas costosas a hilos de trabajo, optimizar el inicio en frío y monitorizar constantemente las trazas de error, logramos que la aplicación sea robusta, fluida y, sobre todo, que el usuario nunca tenga que forzar el cierre de la sesión por un bloqueo inesperado.

Automatizar Pruebas de UI en Android con Espresso
Artículo relacionado:
Guía Completa para Automatizar Pruebas de UI en Android con Espresso

Añadir como fuente preferida en Google