Android: come posso analizzare un ANR?


153

C'è un modo per scoprire dove la mia app ha lanciato un ANR (applicazione non rispondente). Ho dato un'occhiata al file traces.txt in / data e vedo una traccia per la mia applicazione. Questo è quello che vedo nella traccia.

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 -----

Come posso sapere dove si trova il problema? I metodi nella traccia sono tutti metodi SDK.

Grazie.


2
Ho un rapporto di questo tipo, anche in corso android.os.MessageQueue.nativePollOnce(Native Method). Posso tranquillamente ignorarlo?
RDS

Risposte:


124

Un ANR si verifica quando si verificano alcune operazioni lunghe nel thread "principale". Questo è il thread del loop degli eventi e, se è occupato, Android non può elaborare ulteriori eventi della GUI nell'applicazione e quindi genera una finestra di dialogo ANR.

Ora, nella traccia che hai pubblicato, il thread principale sembra andare bene, non ci sono problemi. È inattivo in MessageQueue, in attesa che arrivi un altro messaggio. Nel tuo caso, l'ANR era probabilmente un'operazione più lunga, piuttosto che qualcosa che ha bloccato il thread in modo permanente, quindi il thread dell'evento si è ripristinato al termine dell'operazione e la tua traccia è passata attraverso dopo l'ANR.

Rilevare dove si verificano gli ANR è facile se si tratta di un blocco permanente (ad esempio deadlock che acquisisce alcuni blocchi), ma più difficile se si tratta solo di un ritardo temporaneo. Innanzitutto, controlla il tuo codice e cerca i punti votabili e le operazioni di lunga durata. Gli esempi possono includere l'utilizzo di socket, blocchi, sleep del thread e altre operazioni di blocco all'interno del thread degli eventi. Dovresti assicurarti che tutto ciò avvenga in thread separati. Se nulla sembra il problema, utilizzare DDMS e abilitare la vista thread. Questo mostra tutti i thread nella tua applicazione simili alla traccia che hai. Riprodurre ANR e aggiornare contemporaneamente il thread principale. Ciò dovrebbe mostrarti esattamente cosa sta succedendo al momento dell'ANR


6
l'unico problema è "riprodurre l'ANR" :-). potresti per favore spiegare come quello stack show show sia il thread principale "inattivo", sarebbe fantastico.
Blundell,

20
La traccia dello stack mostra che il thread principale si trova nel Looper (l'implementazione del ciclo di messaggi) e sta eseguendo un'attesa temporizzata tramite Object.wait. Ciò significa che i loop di messaggi non hanno attualmente alcun messaggio da inviare e sono in attesa di nuovi messaggi. Un ANR si verifica quando il sistema si rende conto che un ciclo di messaggi impiega molto tempo a elaborare un messaggio e non a elaborare altri messaggi nel coda. Se i loop sono in attesa di messaggi, ovviamente questo non sta accadendo.
presto il

3
@Soonil Ciao, sai cosa significa il resto delle sezioni come Binder thread 3, Binder thread 2 JDWP demon prio 5. che cosa è sCount, dsCount, obj, sysTid, un bel programma significa. inoltre ha informazioni come VMWAIT, RUNNABLE, NATIVE
minhaz,

1
La mia app è basata su NDK, vedo lo stesso ANR. Inoltre, il thread principale va bene. Ho provato DDMS e aggiorna il mio thread di lavoro quando si blocca. Sfortunatamente tutto ciò che ottengo è una riga singola NativeStart :: run. La vista thread DDMS è anche in grado di ispezionare thread NDK nativi? Inoltre: StrictMode non ha trovato nulla.
Bram,

6
Vedi elliotth.blogspot.com/2012/08/… per una buona spiegazione dell'output.
Presto

96

È possibile abilitare StrictMode nel livello API 9 e versioni successive.

StrictMode è più comunemente usato per catturare l'accesso accidentale al disco o alla rete sul thread principale dell'applicazione, dove vengono ricevute le operazioni dell'interfaccia utente e si svolgono le animazioni. Mantenendo reattivo il thread principale dell'applicazione, si impedisce anche agli utenti di mostrare le finestre di dialogo ANR .

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

usando penaltyLog()puoi guardare l'output di adb logcat mentre usi la tua applicazione per vedere le violazioni mentre si verificano.


StrictMode non può essere risolto in un tipo. C'è qualcosa che devo importare per primo? Premere CTRL + MAIUSC + O non aiuta.
Kuchi,

23
piccolo suggerimento: utilizzare if (BuildConfig.DEBUG) ... per impedire l'inclusione nella produzione
Amir Uval,

@uval cosa intendi con "per prevenire l'inclusione nella produzione"? !!
Muhammed Refaat

2
@MuhammedRefaat non impedisce alcun ANR. Arresto anomalo dell'app immediatamente anziché dopo 5 secondi. Ad esempio, se accedi al database sul thread principale e impiega 2 secondi, non otterrai un ANR, ma StrictMode si bloccherà l'app. StrictMode è strettamente per la fase di debug, non per la produzione.
Amir Uval,

1
@MuhammedRefaat ha aggiunto la mia risposta alla tua domanda.
Amir Uval,

80

Ti stai chiedendo quale attività contiene un thread dell'interfaccia utente. Il file di traccia fornisce un suggerimento per trovare l'attività. devi investigare uno stato di ogni thread

Stato del thread

  • in esecuzione - esecuzione del codice dell'applicazione
  • dormire - chiamato Thread.sleep ()
  • monitor - in attesa di acquisire un blocco monitor
  • wait - in Object.wait ()
  • native: esegue il codice nativo
  • vmwait - in attesa su una risorsa VM
  • zombie - il thread è in procinto di morire
  • init: il thread si sta inizializzando (non dovresti vederlo)
  • inizio - il thread sta per iniziare (non dovresti vederlo neanche)

Focus su stato SOSPESO, MONITOR. Lo stato del monitor indica quale thread viene esaminato e lo stato SUSPENDED del thread è probabilmente il motivo principale del deadlock.

I passaggi di base indagano

  1. Trova "in attesa di blocco"
    • puoi trovare lo stato del monitor "Binder Thread # 15" prio = 5 tid = 75 MONITOR
    • sei fortunato se trovi "in attesa di bloccare"
    • esempio: in attesa di bloccare <0xblahblah> (un com.foo.A) tenuto da threadid = 74
  2. Si può notare che "tid = 74" contiene un'attività ora. Quindi vai a tid = 74
  3. tid = 74 forse stato SOSPESO! trova il motivo principale!

la traccia non contiene sempre "in attesa di blocco". in questo caso è difficile trovare il motivo principale.


1
Bella spiegazione. Ora è più facile per me capire i log ANR. Ma ho ancora un problema da capire perché nel passaggio 1 sono in grado di trovare facilmente l'id thread ma quando, nel passaggio 2, sto cercando di andare dove si trova, per controllare lo stato, non riesco a trovarlo . Qualche idea su come procedere?
THZ,

1
Ho - waiting to lock an unknown objectdentro "HeapTaskDaemon" daemon prio=5 tid=8 Blocked . Cosa vuol dire che qualcuno può aiutare?
Hilal,

13

Ho imparato Android negli ultimi mesi, quindi sono tutt'altro che un esperto, ma sono rimasto davvero deluso dalla documentazione sugli ANR.

La maggior parte dei consigli sembra essere orientata ad evitarli o risolverli guardando ciecamente il tuo codice, il che è fantastico, ma non sono riuscito a trovare nulla sull'analisi della traccia.

Ci sono tre cose che devi davvero cercare con i log ANR.

1) Deadlock: quando un thread è nello stato di ATTESA, puoi guardare attraverso i dettagli per scoprire chi è "holdby =". Il più delle volte, sarà tenuto da solo, ma se è trattenuto da un altro thread, è probabile che sia un segnale di pericolo. Vai a guardare quel thread e vedere da cosa è trattenuto. Potresti trovare un ciclo, che è un chiaro segno che qualcosa è andato storto. Questo è piuttosto raro, ma è il primo punto perché quando succede, è un incubo

2) Thread principale in attesa: se il thread principale è nello stato di ATTESA, controlla se è trattenuto da un altro thread. Questo non dovrebbe accadere, perché il tuo thread dell'interfaccia utente non dovrebbe essere trattenuto da un thread in background.

Entrambi questi scenari, significa che è necessario rielaborare il codice in modo significativo.

3) Operazioni pesanti sul thread principale: questa è la causa più comune di ANR, ma a volte una delle più difficili da trovare e risolvere. Guarda i dettagli della discussione principale. Scorri verso il basso la traccia dello stack e fino a visualizzare le classi che riconosci (dalla tua app). Guarda i metodi nella traccia e scopri se stai effettuando chiamate di rete, chiamate db, ecc. In questi luoghi.

Infine, e mi scuso per aver inserito spudoratamente il mio codice, puoi utilizzare l'analizzatore di log di Python che ho scritto su https://github.com/HarshEvilGeek/Android-Log-Analyzer Questo passerà attraverso i tuoi file di registro, aprirà i file ANR, trova deadlock, trova i thread principali in attesa, trova eccezioni non rilevate nei registri degli agenti e stampa tutto sullo schermo in modo relativamente facile da leggere. Leggi il file Leggimi (che sto per aggiungere) per imparare come usarlo. Mi ha aiutato moltissimo nell'ultima settimana!


4

Ogni volta che si analizzano i problemi di temporizzazione, il debug spesso non aiuta, poiché il blocco dell'app a un punto di interruzione risolverà il problema.

La soluzione migliore è inserire molte chiamate di registrazione (Log.XXX ()) nei diversi thread e callback dell'app e vedere dove si trova il ritardo. Se hai bisogno di uno stacktrace, crea una nuova eccezione (basta crearne una) e registrala.


2
Grazie per il consiglio sulla creazione di una nuova eccezione se è necessario uno stacktrace. Questo è molto utile durante il debug :)
kuchi

3

Cosa attiva l'ANR?

In genere, il sistema visualizza un ANR se un'applicazione non può rispondere all'input dell'utente.

In qualsiasi situazione in cui l'app esegue un'operazione potenzialmente lunga, non è necessario eseguire il lavoro sul thread dell'interfaccia utente, ma invece creare un thread di lavoro e svolgere la maggior parte del lavoro lì. Ciò mantiene attivo il thread dell'interfaccia utente (che guida il ciclo degli eventi dell'interfaccia utente) e impedisce al sistema di concludere che il codice è bloccato.

Come evitare gli ANR

Le applicazioni Android normalmente vengono eseguite interamente su un singolo thread per impostazione predefinita, "thread dell'interfaccia utente" o "thread principale"). Ciò significa che tutto ciò che l'applicazione sta eseguendo nel thread dell'interfaccia utente che richiede molto tempo per essere completato può attivare la finestra di dialogo ANR perché l'applicazione non si sta dando la possibilità di gestire l'evento di input o le trasmissioni di intenti.

Pertanto, qualsiasi metodo eseguito nel thread dell'interfaccia utente dovrebbe fare il minor lavoro possibile su quel thread. In particolare, le attività dovrebbero fare il meno possibile per impostare metodi chiave del ciclo di vita come onCreate () e onResume (). Operazioni potenzialmente lunghe come operazioni di rete o di database o calcoli computazionalmente costosi come il ridimensionamento di bitmap dovrebbero essere eseguiti in un thread di lavoro (o nel caso di operazioni di database, tramite una richiesta asincrona).

Codice: thread di lavoro con la classe 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");
    }
}

Codice: esegui il thread di lavoro

Per eseguire questo thread di lavoro, è sufficiente creare un'istanza e chiamare execute ():

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

fonte

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


1

il mio problema con ANR, dopo molto lavoro ho scoperto che un thread chiamava una risorsa che non esisteva nel layout, invece di restituire un'eccezione, ho ottenuto ANR ...


è estremamente strano
Nilabja,


0

Basandomi sulla risposta di @Horyun Lee, ho scritto un piccolo script in pitone per aiutare a indagare su ANR traces.txt.

Gli ANR verranno visualizzati come grafici graphvizse sono stati installati grapvhvizsul sistema.

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

Un png verrà emesso come di seguito se sono stati rilevati ANR nel file traces.txt. È più intuitivo.

inserisci qui la descrizione dell'immagine

Il traces.txtfile di esempio usato sopra è stato ottenuto da qui .


0

Prendi in considerazione l'utilizzo della libreria ANR-Watchdog per tracciare e acquisire accuratamente le tracce dello stack ANR con un alto livello di dettaglio. È quindi possibile inviarli alla libreria di segnalazione degli arresti anomali. Raccomando di usaresetReportMainThreadOnly() in questo scenario. Puoi fare in modo che l'app generi un'eccezione non fatale del punto di congelamento o far uscire forzatamente l'app quando si verifica l'ANR.

Tieni presente che i rapporti ANR standard inviati alla tua console per gli sviluppatori di Google Play spesso non sono abbastanza precisi da individuare il problema esatto. Ecco perché è necessaria una libreria di terze parti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.