Depurar en producción: ANRs, Play Console y reproducir lo irreproducible
En producción desaparecen el depurador, el perfilador y la capacidad de reproducir a voluntad, y queda un flujo de evidencia parcial recogida por el sistema. Esta lección enseña a leerla: qué dispara un ANR y con qué plazo, cómo se lee su traza de hilos siguiendo la cadena de bloqueo hasta el hilo culpable, qué significan de verdad los umbrales de comportamiento de Play Console y por qué la agrupación por firma engaña. Termina con el problema más difícil del nivel, que es fabricar en el escritorio las condiciones —dispositivo lento, memoria escasa, red hostil, muerte del proceso— bajo las cuales un fallo que nadie consigue reproducir ocurre siempre.
Todo lo anterior en este nivel comparte una comodidad que se da por descontada hasta que desaparece: el problema estaba delante, en un dispositivo al alcance de la mano, y podía provocarse tantas veces como hiciera falta. En producción nada de eso existe. No hay depurador que enganchar, no hay perfilador que abrir, no hay forma de pedirle a los usuarios que repitan el gesto exacto, y el fallo que arruina la valoración de la aplicación ocurre en un modelo que nadie del equipo posee, con una versión del sistema que ya nadie recuerda y bajo una combinación de memoria, red y latencia que jamás se dio en el escritorio. Lo único que queda es un flujo de evidencia parcial que el sistema recoge por su cuenta: trazas de hilos capturadas en el instante del bloqueo, informes agrupados por firma, tasas comparadas contra umbrales. Depurar en producción es la disciplina de extraer conclusiones firmes de esa evidencia incompleta y, sobre todo, de convertirla en una receta que reproduzca el fallo a voluntad.
- Enumerar los disparadores de un ANR con sus plazos y deducir del disparador qué parte del sistema estaba esperando.
- Leer una traza de hilos siguiendo la cadena de bloqueo desde el hilo principal hasta el hilo que retiene el candado.
- Interpretar los umbrales de comportamiento de Play Console y las trampas de la agrupación por firma.
- Fabricar en el escritorio las condiciones de campo que convierten un fallo esporádico en un fallo determinista.
El ANR: quién lo dispara y con qué plazo
Un ANR no es un bloqueo del programa: es un veredicto del sistema. Alguien —el despachador de eventos, el gestor de actividades, el gestor de trabajos— le pidió algo a tu proceso, midió el tiempo de respuesta y decidió que había excedido el plazo. Ese origen importa muchísimo, porque cada disparador tiene su propio plazo y su propia semántica, y el enunciado del informe ya reduce el espacio de búsqueda antes de mirar una sola pila.
Entrada sin atender
El usuario tocó y el hilo principal no procesó el evento dentro de unos pocos segundos. Es el ANR clásico y el que el usuario percibe como una aplicación congelada.
Receptor de difusión lento
El receptor no terminó su ejecución en plazo. Es el disparador que más sorprende porque el usuario no estaba interactuando con nada.
Servicio que no arranca
La creación o el arranque de un servicio excedió su ventana, muy corta en primer plano y más generosa en segundo.
Sin ventana con foco
No hay una ventana lista para recibir eventos. Suele delatar un arranque interminable o una transición de pantalla que nunca se completó.
El detalle contraintuitivo es que la mayoría de los ANR no los causa un cálculo largo. Los causa una espera: una transacción hacia un servicio del sistema que está saturado, una lectura de disco en el hilo principal, un candado retenido por un hilo trabajador que a su vez espera a la red, una llamada bloqueante que fuerza al hilo principal a aguardar a una corrutina, una escritura síncrona de preferencias. La firma común es un hilo principal que no consume tiempo de procesador y aun así no avanza.
Leer la traza de hilos sin engañarse
Cuando el sistema declara un ANR captura una fotografía de todos los hilos del proceso. Esa traza llega por el informe de errores del dispositivo, por Play Console y, lo más útil de todo, se puede recuperar desde el propio proceso en el arranque siguiente.
// En el arranque: recuperar por que murio el proceso la vez anterior.
val am = getSystemService(ActivityManager::class.java)
am.getHistoricalProcessExitReasons(packageName, 0, 5).forEach { salida ->
if (salida.reason == ApplicationExitInfo.REASON_ANR) {
val traza = salida.traceInputStream?.bufferedReader()?.readText()
val migas = salida.processStateSummary?.decodeToString()
registrar(traza, migas, salida.timestamp)
}
}
Ese mecanismo es la pieza más infravalorada del arsenal de producción. Devuelve el motivo real de la última muerte del proceso —ANR, fallo, falta de memoria, cierre por el usuario, cierre por el sistema— junto con la traza completa, y además permite adjuntar un bloque de estado propio que se guarda con el registro, de modo que el informe llega acompañado de las migas de pan que el equipo decidió dejar.
La lectura de la traza tiene un orden que conviene respetar. Primero el enunciado, que dice el disparador. Segundo el hilo principal, y ahí hay tres desenlaces posibles. Si aparece ejecutando código propio, el diagnóstico es directo y el culpable está en la pila. Si aparece bloqueado esperando un monitor, la traza indica qué candado espera y qué hilo lo retiene, y entonces la investigación se traslada a ese otro hilo, que es donde está el problema real. Si aparece inactivo en la espera de mensajes, el hilo principal ya se había recuperado cuando se tomó la fotografía y la traza no contiene la causa: hay que buscarla en el consumo de procesador que acompaña al informe, en otros procesos, o aceptar que el instante quedó fuera del encuadre.
# Recuperar el informe completo con las trazas de bloqueo del dispositivo.
adb bugreport informe.zip
# Provocar un ANR de manual para practicar la lectura antes de necesitarla.
adb shell am broadcast -a com.ejemplo.LENTO
La fotografía se toma cuando el sistema decide tomarla, no cuando el bloqueo empezó, y en un dispositivo cargado ese retraso puede ser de varios segundos. Para entonces la operación que causó el atasco quizá terminó y el hilo principal ya está esperando mensajes tranquilamente. La conclusión errónea —que el informe es un falso positivo— es tentadora y falsa: el usuario sí vio la aplicación congelada. En esos casos la evidencia útil no está en la pila sino alrededor: el reparto de procesador entre procesos que acompaña al informe, los hilos secundarios que sí muestran trabajo pesado, y sobre todo la instrumentación propia que el equipo haya dejado sembrada para saber en qué fase estaba la aplicación.
Umbrales, agrupación y la reproducción deliberada
Play Console presenta la fiabilidad como dos tasas medidas sobre sesiones percibidas por el usuario: la de fallos y la de bloqueos. Superar el umbral de mal comportamiento no produce un aviso amable, produce pérdida de visibilidad en la tienda, y superar el umbral excesivo en un modelo concreto puede llevar a que se advierta al usuario o se limite la distribución en ese modelo. El matiz que se pasa por alto es el adjetivo percibida: solo cuentan los sucesos que el usuario pudo notar, lo que descarta muchos bloqueos en segundo plano y explica por qué las cifras internas del equipo nunca coinciden con las de la consola.
La agrupación es la segunda fuente de malentendidos. Los informes se agrupan por una firma derivada de la pila, y eso produce dos errores simétricos: una única agrupación gigantesca puede contener varias causas distintas que casualmente comparten el marco superior, y una sola causa puede aparecer repartida en decenas de agrupaciones porque la pila varía ligeramente entre versiones o modelos. Por eso la primera acción sobre un informe nunca es leer la pila, sino leer la distribución: qué modelos, qué versiones del sistema, qué versión de la aplicación, cuánta memoria. Una tasa concentrada en un fabricante o en una gama de memoria es un diagnóstico casi hecho.
Todo esto sería inútil sin ofuscación invertida. La subida del archivo de correspondencia y de los símbolos de depuración nativos es lo que convierte una pila de nombres de una sola letra en algo legible, y omitirla equivale a renunciar a los informes de producción por completo.
Llegado el diagnóstico, queda lo difícil: reproducir. La estrategia es dejar de intentar repetir el gesto del usuario y empezar a recrear sus condiciones, que es donde de verdad estaba la diferencia.
Degrada la máquina
Un dispositivo de gama baja real, memoria escasa y almacenamiento lleno. La mayoría de los bloqueos de campo son problemas de latencia que solo se manifiestan cuando todo es más lento.
Hostiliza la red
Latencia alta, pérdida de paquetes y sobre todo la conexión que ni responde ni falla. Los tiempos de espera mal puestos solo aparecen ahí.
Mata el proceso
Provocar la muerte del proceso en segundo plano y volver reproduce toda la familia de fallos de restauración de estado, invisible en el escritorio.
Vigila desde el desarrollo
El modo estricto convierte en visible durante el desarrollo lo que en producción solo se manifiesta como bloqueo: disco y red en el hilo principal.
flowchart TD
A[Informe de bloqueo en produccion] --> B[Leer el disparador y su plazo]
B --> C{Estado del hilo principal}
C -->|Ejecutando codigo propio| D[La causa esta en la pila]
C -->|Bloqueado en un monitor| E[Ir al hilo que retiene el candado]
C -->|Inactivo esperando mensajes| F[La fotografia llego tarde]
F --> G[Usar distribucion por modelo memoria y version]
E --> G
D --> H[Recrear condiciones no gestos]
G --> H
H --> I[Fallo determinista y prueba de regresion]
style A fill:#f38ba8,color:#11111b
style H fill:#fab387,color:#11111b
style I fill:#a6e3a1,color:#11111bHay un desplazamiento del objeto de trabajo que marca el paso de la ingeniería individual a la ingeniería de un producto vivo, y es este: cuando un fallo ocurre en un dispositivo que no se puede tocar, lo que se puede mejorar ya no es el fallo sino la evidencia. Un informe de campo que no permite decidir nada no significa que el problema sea difícil, significa que la aplicación no fue construida para ser observada, y esa carencia es tan reparable como cualquier otro defecto de diseño, con la particularidad de que solo se puede reparar antes de necesitarlo. De ahí que la pregunta correcta al cerrar cada investigación no sea únicamente qué causó este bloqueo, sino qué habría hecho falta para que el informe lo hubiera dicho solo; y la respuesta a esa segunda pregunta —una fase registrada en el bloque de estado que acompaña a la salida del proceso, una sección de instrumentación en la frontera sospechosa, una miga de pan antes de la llamada bloqueante, un identificador de la operación en vuelo— es una modificación del código que se hace ahora y que rentabiliza el mes que viene. Esta inversión del orden habitual tiene además una consecuencia que suele sorprender, y es que hace converger las condiciones de campo con las del escritorio. Un fallo que solo ocurre en un modelo con poca memoria, con red inestable y con el proceso muriendo en segundo plano no es misterioso, es simplemente el resultado de que el entorno de desarrollo es un lugar irrealmente benigno: máquina rápida, memoria abundante, red perfecta, procesos que nunca mueren. Reproducir lo irreproducible casi nunca consiste en adivinar la secuencia mágica de toques del usuario; consiste en dejar de desarrollar en el paraíso y hacer del escritorio un sitio tan hostil como el mundo, porque en un entorno suficientemente degradado el fallo esporádico se vuelve constante y deja de ser un enigma para convertirse en una prueba. Ese es el arco completo del nivel: los profiladores enseñan a mirar, Perfetto enseña a mirar el sistema entero, la instrumentación propia enseña a hacerse visible, y la depuración en producción enseña que la observabilidad no es una herramienta que se abre cuando hay un problema, sino una propiedad que se construye en el código mucho antes de que el problema exista.
- Provoca los cuatro disparadores de bloqueo de la lección y compara los enunciados de sus informes. Explica qué reduce cada enunciado antes de mirar ninguna pila.
- Crea un bloqueo por candado retenido: un hilo trabajador que lo toma y hace una operación lenta mientras el hilo principal lo espera. Sigue la cadena en la traza hasta nombrar al hilo culpable.
- Recupera desde el arranque la razón de la última muerte del proceso y adjunta un bloque de estado propio con la fase en la que se encontraba la aplicación.
- Toma un informe agrupado de tu aplicación y analiza primero su distribución por modelo, memoria y versión del sistema. Formula una hipótesis solo con eso.
- Recrea las condiciones de campo —dispositivo lento, memoria escasa, red que no responde, muerte del proceso en segundo plano— hasta que el fallo esporádico ocurra siempre, y escribe con ello una prueba de regresión.