En esta sección, se muestra cómo depurar un error de Aplicación no responde (ANR) con ProfilingManager con un seguimiento de ejemplo.
Configura la app para recopilar ANR
Para comenzar, configura un activador de ANR en tu app:
public void addANRTrigger() { ProfilingManager profilingManager = getApplicationContext().getSystemService( ProfilingManager.class); List<ProfilingTrigger> triggers = new ArrayList<>(); ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_ANR); triggers.add(triggerBuilder.build()); Executor mainExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = profilingResult -> { // Handle uploading trace to your back-end }; profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback); profilingManager.addProfilingTriggers(triggers); }
Después de capturar y subir un registro de ANR, ábrelo en la IU de Perfetto.
Analiza el seguimiento
Debido a que el error de ANR activó el registro, sabes que este finalizó cuando el sistema detectó que el subproceso principal de tu app no respondía. En la figura 1, se muestra cómo navegar al subproceso principal de tu app, que se etiqueta según corresponda dentro de la IU.

El final del registro coincide con la marca de tiempo del error de ANR, como se muestra en la Figura 2.
El seguimiento también muestra las operaciones que la app estaba ejecutando cuando se produjo el error de ANR.
Específicamente, la app ejecutó código en la porción de seguimiento handleNetworkResponse. Esta porción estaba dentro de la porción MyApp:SubmitButton. Usó 1.48 segundos de tiempo de CPU (figura 3).
Si solo confías en los seguimientos de pila en el momento del error de ANR para la depuración, es posible que atribuyas erróneamente el error de ANR por completo al código que se ejecuta dentro del segmento de seguimiento handleNetworkResponse, que no había finalizado cuando el perfil terminó de grabar. Sin embargo, 1.48 segundos no es suficiente para activar un error de ANR por sí solo, aunque se trate de una operación costosa. Debes mirar más atrás en el tiempo para comprender qué bloqueó el subproceso principal antes de este método.
Para obtener un punto de partida que permita buscar la causa del error de ANR, comenzamos a buscar después del último fotograma que generó el subproceso de IU, que corresponde al segmento Choreographer#doFrame 551275, y no hay grandes fuentes de retraso antes de iniciar el segmento MyApp:SubmitButton que terminó en el error de ANR (figura 4).

Para comprender el bloqueo, aleja la imagen y, luego, inspecciona la porción completa de MyApp:SubmitButton. Notarás un detalle crítico en los estados de los subprocesos, como se muestra en la figura 4: el subproceso pasó el 75% del tiempo (6.7 segundos) en el estado Sleeping y solo el 24% del tiempo en el estado Running.

Esto indica que la causa principal del error de ANR fue la espera, no el procesamiento. Examina los eventos de sueño individuales para encontrar un patrón.

Los primeros tres intervalos de suspensión (figuras 6 a 8) son casi idénticos, de aproximadamente 2 segundos cada uno. Un cuarto estado de suspensión atípico (figura 9) es de 0.7 segundos. Una duración de exactamente 2 segundos rara vez es una coincidencia en un entorno de procesamiento. Esto sugiere con fuerza un tiempo de espera programado en lugar de una contención de recursos aleatoria. Es posible que la última suspensión se deba a que el subproceso finalizó su espera porque la operación que estaba esperando se realizó correctamente.
Esta hipótesis es que la app alcanzaba un tiempo de espera definido por el usuario de 2 segundos varias veces y, finalmente, se ejecutaba correctamente, lo que causaba un retraso suficiente para activar un error de ANR.

Para verificarlo, inspecciona el código asociado con la sección de seguimiento de MyApp:SubmitButton:
private static final int NETWORK_TIMEOUT_MILLISECS = 2000; public void setupButtonCallback() { findViewById(R.id.submit).setOnClickListener(submitButtonView -> { Trace.beginSection("MyApp:SubmitButton"); onClickSubmit(); Trace.endSection(); }); } public void onClickSubmit() { prepareNetworkRequest(); boolean networkRequestSuccess = false; int maxAttempts = 10; while (!networkRequestSuccess && maxAttempts > 0) { networkRequestSuccess = performNetworkRequest(NETWORK_TIMEOUT_MILLISECS); maxAttempts--; } if (networkRequestSuccess) { handleNetworkResponse(); } } boolean performNetworkRequest(int timeoutMiliseconds) { // ... } void prepareNetworkRequest() { // ... } public void handleNetworkResponse() { Trace.beginSection("handleNetworkResponse"); // ... Trace.endSection(); }
El código confirma esta hipótesis. El método onClickSubmit ejecuta una solicitud de red
en el subproceso de IU con un NETWORK_TIMEOUT_MILLISECS de 2,000 ms codificado de forma rígida.
Es fundamental que se ejecute dentro de un bucle while que reintente hasta 10 veces.
En este seguimiento específico, es probable que el usuario haya tenido una mala conectividad de red. Los primeros
tres intentos fallaron, lo que causó tres tiempos de espera de 2 segundos (un total de 6 segundos).
El cuarto intento se realizó correctamente después de 0.7 segundos, lo que permitió que el código continuara con
handleNetworkResponse. Sin embargo, el tiempo de espera acumulado ya activó el error de ANR.
Para evitar este tipo de ANR, coloca las operaciones relacionadas con la red que tienen latencias variables en un subproceso en segundo plano en lugar de ejecutarlas en el subproceso principal. Esto permite que la IU siga respondiendo incluso con una conectividad deficiente, lo que elimina por completo esta clase de ANR.