Questa sezione mostra come eseguire il debug di un errore ANR (L'applicazione non risponde) utilizzando
ProfilingManager con una traccia di esempio.
Configurare l'app per raccogliere gli errori ANR
Inizia configurando un trigger ANR nella tua 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); }
Dopo aver acquisito e caricato una traccia ANR, aprila nell'UI Perfetto.
Analizzare la traccia
Poiché l'errore ANR ha attivato la traccia, sai che la traccia è terminata quando il sistema ha rilevato la mancata risposta nel thread principale della tua app. La Figura 1 mostra come navigare al thread principale dell'app, che è contrassegnato di conseguenza all'interno dell'UI.
La fine della traccia corrisponde al timestamp dell'errore ANR, come mostrato nella Figura 2.
La traccia mostra anche le operazioni che l'app stava eseguendo quando si è verificato l'errore ANR.
In particolare, l'app ha eseguito il codice nella sezione della traccia handleNetworkResponse. Questa sezione si trovava all'interno della sezione MyApp:SubmitButton. Ha utilizzato 1,48 secondi di tempo della CPU (Figura 3).
Se al momento dell'errore ANR ti affidi esclusivamente alle analisi dello stack per il debug, potresti attribuire erroneamente l'errore ANR interamente al codice in esecuzione nella sezione della traccia handleNetworkResponse, che non era terminata al termine della registrazione del profilo. Tuttavia, 1,48 secondi non sono sufficienti per attivare un errore ANR da soli, anche se si tratta di un'operazione costosa. Devi tornare indietro nel tempo per capire cosa ha bloccato il thread principale prima di questo metodo.
Per trovare un punto di partenza per cercare la causa dell'errore ANR, iniziamo a cercare dopo l'ultimo frame generato dal thread dell'interfaccia utente, che corrisponde alla sezione Choreographer#doFrame 551275. Non ci sono grandi fonti di ritardo prima di iniziare la sezione MyApp:SubmitButton che ha portato all'errore ANR (Figura 4).
Per comprendere il blocco, riduci lo zoom per esaminare l'intera sezione MyApp:SubmitButton. Noterai un dettaglio fondamentale negli stati del thread, come mostrato nella
Figura 4: il thread ha trascorso il 75% del tempo (6,7 secondi) nello stato Sleeping
e solo il 24% del tempo nello stato Running .
Ciò indica che la causa principale dell'errore ANR era l'attesa, non il calcolo. Esamina le singole occorrenze di sospensione per trovare un pattern.
I primi tre intervalli di sospensione (Figure 6-8) sono quasi identici, circa 2 secondi ciascuno. Un quarto periodo di sospensione anomalo (Figura 9) è di 0,7 secondi. Una durata di esattamente 2 secondi è raramente una coincidenza in un ambiente di computing. Ciò suggerisce fortemente un timeout programmato anziché una contesa di risorse casuale. L'ultima sospensione potrebbe essere causata dal fatto che il thread ha terminato l'attesa perché l'operazione su cui stava aspettando è andata a buon fine.
L'ipotesi è che l'app abbia raggiunto più volte un timeout definito dall'utente di 2 secondi e alla fine sia andata a buon fine, causando un ritardo sufficiente per attivare un errore ANR.
Per verificarlo, esamina il codice associato alla sezione della traccia 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(); }
Il codice conferma questa ipotesi. Il metodo onClickSubmit esegue una richiesta di rete sul thread dell'interfaccia utente con un valore NETWORK_TIMEOUT_MILLISECS hardcoded di 2000 ms.
Fondamentalmente, viene eseguito all'interno di un loop while che riprova fino a 10 volte.
In questa traccia specifica, l'utente probabilmente aveva una scarsa connettività di rete. I primi tre tentativi non sono andati a buon fine, causando tre timeout di 2 secondi (per un totale di 6 secondi).
Il quarto tentativo è andato a buon fine dopo 0,7 secondi, consentendo al codice di procedere a handleNetworkResponse. Tuttavia, il tempo di attesa accumulato ha già attivato l'errore ANR.
Evita questo tipo di errore ANR inserendo le operazioni correlate alla rete con latenze variabili in un thread in background anziché eseguirle nel thread principale. In questo modo, l'UI rimane reattiva anche in caso di scarsa connettività, eliminando completamente questa classe di errori ANR.