Android: ¿cómo investigo un ANR?

153

¿Hay alguna forma de averiguar dónde mi aplicación arrojó un ANR (la aplicación no responde)? Eché un vistazo al archivo traces.txt en / data y veo un rastro para mi aplicación. Esto es lo que veo en el rastro.

DALVIK THREADS:
"main" prio=5 tid=3 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 s=0 obj=0x400143a8
  | sysTid=691 nice=0 sched=0/0 handle=-1091117924
  at java.lang.Object.wait(Native Method)
  - waiting on <0x1cd570> (a android.os.MessageQueue)
  at java.lang.Object.wait(Object.java:195)
  at android.os.MessageQueue.next(MessageQueue.java:144)
  at android.os.Looper.loop(Looper.java:110)
  at android.app.ActivityThread.main(ActivityThread.java:3742)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
  at dalvik.system.NativeStart.main(Native Method)

"Binder Thread #3" prio=5 tid=15 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x434e7758
  | sysTid=734 nice=0 sched=0/0 handle=1733632
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #2" prio=5 tid=13 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433af808
  | sysTid=696 nice=0 sched=0/0 handle=1369840
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #1" prio=5 tid=11 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433aca10
  | sysTid=695 nice=0 sched=0/0 handle=1367448
  at dalvik.system.NativeStart.run(Native Method)

"JDWP" daemon prio=5 tid=9 VMWAIT
  | group="system" sCount=1 dsCount=0 s=0 obj=0x433ac2a0
  | sysTid=694 nice=0 sched=0/0 handle=1367136
  at dalvik.system.NativeStart.run(Native Method)

"Signal Catcher" daemon prio=5 tid=7 RUNNABLE
  | group="system" sCount=0 dsCount=0 s=0 obj=0x433ac1e8
  | sysTid=693 nice=0 sched=0/0 handle=1366712
  at dalvik.system.NativeStart.run(Native Method)

"HeapWorker" daemon prio=5 tid=5 VMWAIT
  | group="system" sCount=1 dsCount=0 s=0 obj=0x4253ef88
  | sysTid=692 nice=0 sched=0/0 handle=1366472
  at dalvik.system.NativeStart.run(Native Method)

----- end 691 -----

¿Cómo puedo averiguar dónde está el problema? Los métodos en la traza son todos métodos SDK.

Gracias.

perdido en el transito
fuente
2
Tengo un informe de este tipo, que también está sucediendo en android.os.MessageQueue.nativePollOnce(Native Method). ¿Puedo ignorarlo con seguridad?
rds

Respuestas:

124

Un ANR ocurre cuando se realiza una operación larga en el hilo "principal". Este es el hilo del bucle de eventos, y si está ocupado, Android no puede procesar más eventos de GUI en la aplicación y, por lo tanto, arroja un cuadro de diálogo ANR.

Ahora, en el seguimiento que publicaste, el hilo principal parece estar bien, no hay problema. Está inactivo en MessageQueue, esperando que llegue otro mensaje. En su caso, el ANR probablemente fue una operación más larga, en lugar de algo que bloqueó el hilo permanentemente, por lo que el hilo del evento se recuperó después de que la operación finalizó, y su rastreo pasó. despues de la ANR.

Detectar dónde ocurren los ANR es fácil si es un bloqueo permanente (por ejemplo, un punto muerto que adquiere algunos bloqueos), pero es más difícil si se trata solo de un retraso temporal. Primero, revise su código y busque puntos vunerables y operaciones de larga duración. Los ejemplos pueden incluir el uso de enchufes, bloqueos, bloqueos de hilos y otras operaciones de bloqueo desde el hilo del evento. Debe asegurarse de que todo esto suceda en hilos separados. Si nada parece ser el problema, use DDMS y habilite la vista de hilo. Esto muestra todos los hilos en su aplicación similares a la traza que tiene. Reproduzca el ANR y actualice el hilo principal al mismo tiempo. Eso debería mostrarle exactamente lo que estaba sucediendo en el momento de la ANR

pronto
fuente
66
el único problema es "reproducir el ANR" :-). ¿podría explicar cómo el hilo principal de ese show de seguimiento de pila está 'inactivo'? Eso sería genial.
Blundell
20
El seguimiento de la pila muestra que el hilo principal está en el Looper (la implementación del bucle de mensajes) y está haciendo una espera temporizada a través de Object.wait. Esto significa que los bucles de mensajes actualmente no tienen ningún mensaje para enviar y está esperando que lleguen nuevos mensajes. Un ANR ocurre cuando el sistema se da cuenta de que un bucle de mensajes está gastando mucho tiempo procesando un mensaje y no procesando otros mensajes en el cola. Si los bucles están esperando mensajes, obviamente esto no está sucediendo.
Pronto
3
@Soonil Hola, ¿sabes lo que significa el resto de las secciones como Binder thread 3, Binder thread 2 JDWP demon prio 5. ¿Qué es sCount, dsCount, obj, sysTid, agradable programación? también tiene información como VMWAIT, RUNNABLE,
NATIVE
1
Mi aplicación está basada en NDK, veo el mismo ANR. Además, el hilo principal está bien. Intenté DDMS y actualicé mi hilo de trabajo cuando se congela. Lamentablemente, todo lo que obtengo es una sola línea NativeStart :: run. ¿La vista de subprocesos DDMS es incluso capaz de inspeccionar subprocesos NDK nativos? Además: StrictMode no encontró nada.
Bram
66
Ver elliotth.blogspot.com/2012/08/… para una buena explicación de la salida.
Pronto
96

Puede habilitar StrictMode en el nivel de API 9 y superior.

StrictMode se usa más comúnmente para detectar accesos accidentales a disco o red en el hilo principal de la aplicación, donde se reciben operaciones de IU y se realizan animaciones. Al mantener el hilo principal de su aplicación receptivo, también evita que se muestren cuadros de diálogo ANR a los usuarios.

public void onCreate() {
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                           .detectAll()
                           .penaltyLog()
                           .penaltyDeath()
                           .build());
    super.onCreate();
}

usando penaltyLog()puede ver la salida de adb logcat mientras usa su aplicación para ver las violaciones a medida que ocurren.

Dheeraj Vepakomma
fuente
StrictMode no se puede resolver a un tipo. ¿Hay algo que deba importar primero? Presionar CTRL + MAYÚS + O no ayuda.
kuchi
23
pequeño consejo: úsalo (BuildConfig.DEBUG) ... para evitar su inclusión en producción
Amir Uval
@uval, ¿qué quieres decir con "para evitar la inclusión en la producción"?
Muhammed Refaat
2
@ MuhammedRefaat no evita ningún ANR. Bloqueará la aplicación inmediatamente en lugar de después de 5 segundos. Por ejemplo, si accede a la base de datos en el subproceso principal y tarda 2 segundos, no obtendrá un ANR, pero StrictMode bloqueará la aplicación. StrictMode es estrictamente para su fase de depuración, no para la producción.
Amir Uval
1
@ MuhammedRefaat agregó mi respuesta a su pregunta.
Amir Uval
80

Se pregunta qué tarea tiene un subproceso de interfaz de usuario. El archivo de seguimiento le da una pista para encontrar la tarea. necesitas investigar un estado de cada hilo

Estado del hilo

  • ejecutando - ejecutando código de aplicación
  • durmiendo - llamado Thread.sleep ()
  • monitor - esperando adquirir un bloqueo de monitor
  • esperar - en Object.wait ()
  • native: ejecución de código nativo
  • vmwait: esperando en un recurso de VM
  • zombie - el hilo está en proceso de morir
  • init - el hilo se está inicializando (no deberías ver esto)
  • start - el hilo está por comenzar (tampoco deberías ver esto)

Centrarse en el estado SUSPENDIDO, MONITOREAR. El estado del monitor indica qué subproceso se investiga y el estado SUSPENDIDO del subproceso es probablemente la razón principal del punto muerto.

Pasos básicos de investigación

  1. Encuentra "esperando para bloquear"
    • puede encontrar el estado del monitor "Binder Thread # 15" prio = 5 tid = 75 MONITOR
    • tienes suerte si encuentras "esperando para bloquear"
    • ejemplo: esperando para bloquear <0xblahblah> (un com.foo.A) retenido por threadid = 74
  2. Puede notar que "tid = 74" retiene una tarea ahora. Así que ve a tid = 74
  3. tid = 74 quizás estado suspendido! encontrar la razón principal!

trace no siempre contiene "esperando para bloquear". En este caso es difícil encontrar la razón principal.

Horyun Lee
fuente
1
Buena explicación Ahora es más fácil para mí entender los registros ANR. Pero todavía tengo que entender un problema porque en el paso 1 puedo encontrar fácilmente la identificación del hilo pero cuando, en el paso 2, intento ir a donde está, para verificar el estado, no puedo encontrarlo. . ¿Alguna idea de cómo proceder?
THZ
1
Tengo - waiting to lock an unknown objectadentro "HeapTaskDaemon" daemon prio=5 tid=8 Blocked . ¿Qué significa que alguien puede ayudar?
Hilal
13

He estado aprendiendo Android durante los últimos meses, así que estoy lejos de ser un experto, pero me ha decepcionado mucho la documentación sobre los ANR.

La mayoría de los consejos parecen estar orientados a evitarlos o solucionarlos mirando a ciegas a través de su código, lo cual es genial, pero no pude encontrar nada al analizar el rastro.

Hay tres cosas que realmente debe buscar con los registros ANR.

1) Puntos muertos: cuando un hilo está en el estado de ESPERA, puede mirar a través de los detalles para encontrar a quién está "retenido =". La mayoría de las veces, se llevará a cabo solo, pero si está sujeto por otro hilo, es probable que sea una señal de peligro. Ve a mirar ese hilo y mira qué contiene. Es posible que encuentre un bucle, que es una clara señal de que algo salió mal. Esto es bastante raro, pero es el primer punto porque cuando sucede, es una pesadilla

2) Hilo principal en espera: si su hilo principal está en el estado ESPERAR, verifique si está en otro hilo. Esto no debería suceder, porque su subproceso de interfaz de usuario no debe estar retenido por un subproceso de fondo.

Ambos escenarios significan que debe volver a trabajar su código de manera significativa.

3) Operaciones pesadas en el hilo principal: esta es la causa más común de ANR, pero a veces es una de las más difíciles de encontrar y solucionar. Mira los detalles del hilo principal. Desplácese hacia abajo por el seguimiento de la pila y hasta que vea las clases que reconoce (desde su aplicación). Mire los métodos en la traza y descubra si está haciendo llamadas de red, llamadas db, etc. en estos lugares.

Finalmente, y me disculpo por enchufar descaradamente mi propio código, puede usar el analizador de registro de Python que escribí en https://github.com/HarshEvilGeek/Android-Log-Analyzer Esto revisará sus archivos de registro, abrirá archivos ANR, encontrará puntos muertos, encontrar subprocesos principales en espera, encontrar excepciones no detectadas en los registros de su agente e imprimirlo todo en la pantalla de una manera relativamente fácil de leer. Lea el archivo Léame (que estoy a punto de agregar) para aprender cómo usarlo. ¡Me ha ayudado mucho en la última semana!

Akhil Cherian Verghese
fuente
4

Siempre que esté analizando problemas de tiempo, la depuración a menudo no ayuda, ya que congelar la aplicación en un punto de interrupción hará que el problema desaparezca.

Su mejor opción es insertar muchas llamadas de registro (Log.XXX ()) en los diferentes hilos y devoluciones de llamadas de la aplicación y ver dónde está el retraso. Si necesita un seguimiento de pila, cree una nueva Excepción (solo cree una instancia) y regístrela.

Ulrich
fuente
2
Gracias por los consejos sobre cómo crear una nueva excepción si necesita un stacktrace. Eso es muy útil al depurar :)
kuchi
3

¿Qué desencadena ANR?

Generalmente, el sistema muestra un ANR si una aplicación no puede responder a la entrada del usuario.

En cualquier situación en la que su aplicación realice una operación potencialmente larga, no debe realizar el trabajo en el subproceso de interfaz de usuario, sino crear un subproceso de trabajo y hacer la mayor parte del trabajo allí. Esto mantiene el subproceso de la interfaz de usuario (que controla el bucle de eventos de la interfaz de usuario) y evita que el sistema concluya que su código se ha congelado.

Cómo evitar los ANR

Las aplicaciones de Android normalmente se ejecutan completamente en un solo subproceso de forma predeterminada el "subproceso de interfaz de usuario" o "subproceso principal"). Esto significa que cualquier cosa que su aplicación esté haciendo en el subproceso de interfaz de usuario que tarde mucho tiempo en completarse puede activar el cuadro de diálogo ANR porque su aplicación no se está dando la oportunidad de manejar el evento de entrada o las transmisiones intencionales.

Por lo tanto, cualquier método que se ejecute en el subproceso de la interfaz de usuario debe hacer el menor trabajo posible en ese subproceso. En particular, las actividades deben hacer lo menos posible para establecerse en métodos clave del ciclo de vida como onCreate () y onResume (). Las operaciones de ejecución potencialmente prolongada, como las operaciones de red o de base de datos, o los cálculos computacionalmente costosos, como el cambio de tamaño de los mapas de bits, se deben realizar en un subproceso de trabajo (o en el caso de operaciones de bases de datos, a través de una solicitud asincrónica).

Código: hilo de trabajo con la clase AsyncTask

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    // Do the long-running work in here
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    // This is called each time you call publishProgress()
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    // This is called when doInBackground() is finished
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}

Código: Ejecutar hilo de trabajo

Para ejecutar este hilo de trabajo, simplemente cree una instancia y llame a execute ():

new DownloadFilesTask().execute(url1, url2, url3);

Fuente

http://developer.android.com/training/articles/perf-anr.html

Jack
fuente
1

mi problema con ANR, después de mucho trabajo descubrí que un hilo estaba llamando a un recurso que no existía en el diseño, en lugar de devolver una excepción, obtuve ANR ...

Yaniv
fuente
eso es extremadamente extraño
Nilabja
0

Básico en la respuesta de @Horyun Lee, escribí un pequeño script de Python para ayudar a investigar ANR traces.txt.

Los ANR se generarán como gráficos graphvizsi los ha instalado grapvhvizen su sistema.

$ ./anr.py --format png ./traces.txt

Se generará un png como el siguiente si se detectan ANR en el archivo traces.txt. Es mas intuitivo.

ingrese la descripción de la imagen aquí

El traces.txtarchivo de muestra utilizado anteriormente fue obtener de aquí .

alijandro
fuente
0

Considere usar la biblioteca ANR-Watchdog para rastrear y capturar con precisión los rastros de la pila ANR con un alto nivel de detalle. Luego puede enviarlos a su biblioteca de informes de fallas. Recomiendo usar setReportMainThreadOnly()en este escenario. Puede hacer que la aplicación arroje una excepción no fatal del punto de congelación o hacer que la aplicación se cierre forzosamente cuando ocurre el ANR.

Tenga en cuenta que los informes ANR estándar enviados a su consola de Google Play Developer a menudo no son lo suficientemente precisos como para identificar el problema exacto. Es por eso que se necesita una biblioteca de terceros.

Mr-IDE
fuente