ANR

Se il thread dell'interfaccia utente di un'app Android viene bloccato per troppo tempo, viene attivato un errore "L'applicazione non risponde" (ANR). Se l'app è in primo piano, il sistema mostra all'utente una finestra di dialogo, come mostrato nella Figura 1. La finestra di dialogo ANR offre all'utente la possibilità di forzare l'uscita dall'app.

Figura 1. Finestra di dialogo ANR mostrata all'utente

Figura 1. Finestra di dialogo ANR mostrata all'utente

Gli errori ANR rappresentano un problema perché il thread principale dell'app, responsabile dell'aggiornamento dell'interfaccia utente, non è in grado di elaborare o disegnare eventi di input utente, causando frustrazione all'utente. Per ulteriori informazioni sul thread principale dell'app, consulta Processi e thread.

Viene attivato un errore ANR per la tua app quando si verifica una delle seguenti condizioni:

  • Timeout invio input: se la tua app non ha risposto a un evento di input (ad esempio pressione di un tasto o tocco dello schermo) entro 5 secondi.
  • Esecuzione del servizio in corso: se un servizio dichiarato dalla tua app non può completare l'esecuzione di Service.onCreate() e Service.onStartCommand()/Service.onBind() in pochi secondi.
  • Service.startForeground() non chiamato: se la tua app utilizza Context.startForegroundService() per avviare un nuovo servizio in primo piano, ma il servizio non chiama startForeground() entro 5 secondi.
  • Trasmissione dell'intent: se l'esecuzione di BroadcastReceiver non è stata completata entro un determinato periodo di tempo. Se l'app ha attività in primo piano, questo timeout è di 5 secondi.
  • Interazioni di JobScheduler:se un elemento JobService non torna da JobService.onStartJob() o JobService.onStopJob() entro pochi secondi o se viene avviato un job avviato dall'utente e la tua app non chiama JobService.setNotification() entro pochi secondi dalla chiamata di JobService.onStartJob(). Per le app destinate ad Android 13 e versioni precedenti, gli errori ANR sono disattivati e non vengono segnalati all'app. Per le app destinate ad Android 14 e versioni successive, gli errori ANR sono espliciti e vengono segnalati all'app.

Se nella tua app si verificano errori ANR, puoi utilizzare le indicazioni riportate in questo articolo per individuare e risolvere il problema.

Rileva il problema

Se hai già pubblicato la tua app, puoi usare Android vitals per visualizzare informazioni sugli errori ANR per la tua app. Puoi usare altri strumenti per rilevare gli errori ANR sul campo, ma tieni presente che gli strumenti di terze parti non possono segnalare errori ANR su versioni precedenti di Android (Android 10 e versioni precedenti), a differenza di Android vitals.

Android vitals

Android vitals può aiutarti a monitorare e migliorare la percentuale di ANR della tua app. Android vitals misura diverse percentuali di ANR:

  • Percentuale di ANR: la percentuale di utenti attivi giornalieri che hanno riscontrato un tipo di errore ANR.
  • Percentuale di errore ANR percepito dall'utente: la percentuale di utenti attivi giornalieri che hanno riscontrato almeno un errore ANR percepito dall'utente. Attualmente, solo gli errori ANR di tipo Input dispatching timed out vengono considerati percepiti dall'utente.
  • Percentuale di ANR multipli: la percentuale di utenti attivi giornalieri che hanno riscontrato almeno due errori ANR.

Un utente attivo giornaliero è un utente unico che utilizza la tua app in un singolo giorno su un singolo dispositivo, potenzialmente in più sessioni. Se un utente utilizza la tua app su più dispositivi in un solo giorno, ogni dispositivo contribuirà al numero di utenti attivi per quel giorno. Se più utenti utilizzano lo stesso dispositivo in un solo giorno, viene conteggiato come un solo utente attivo.

La percentuale di errori ANR percepiti dall'utente è un'impostazione fondamentale, vale a dire che influisce sulla rilevabilità della tua app su Google Play. È importante perché gli errori ANR che conta si verificano sempre quando l'utente interagisce con l'app, causando il maggior numero di interruzioni.

Google Play ha definito due soglie relative alle prestazioni scadenti per questa metrica:

  • Soglia generale di comportamenti dannosi: almeno lo 0, 47% degli utenti attivi giornalieri ha riscontrato un errore ANR percepito dall'utente su tutti i modelli di dispositivi.
  • Soglia relativa alle prestazioni scadenti del dispositivo: almeno l'8% degli utenti giornalieri ha riscontrato un errore ANR percepito dall'utente su un singolo modello di dispositivo.

Se la tua app supera la soglia generale relativa alle prestazioni scadenti, è probabile che sia meno rilevabile su tutti i dispositivi. Se la tua app supera la soglia relativa alle prestazioni scadenti per dispositivo su alcuni dispositivi, è probabile che sia meno rilevabile su tali dispositivi e potrebbe essere mostrato un avviso nella tua scheda dello Store.

Android vitals può avvisarti tramite Play Console quando la tua app presenta un numero eccessivo di ANR.

Per informazioni su come Google Play raccoglie i dati Android vitals, consulta la documentazione di Play Console.

Diagnostica gli errori ANR

Esistono alcuni pattern comuni da cercare durante la diagnosi degli errori ANR:

  • L'app sta eseguendo operazioni lente che coinvolgono l'I/O sul thread principale.
  • L'app sta eseguendo un lungo calcolo sul thread principale.
  • Il thread principale sta eseguendo una chiamata binder sincrona a un altro processo e l'altro processo sta richiedendo molto tempo.
  • Il thread principale è bloccato in attesa di un blocco sincronizzato per una lunga operazione in corso su un altro thread.
  • Il thread principale è in un deadlock con un altro thread, nel tuo processo o tramite una chiamata a binder. Il thread principale non sta solo aspettando il completamento di un'operazione lunga, ma si trova in una situazione di deadlock. Per ulteriori informazioni, consulta la sezione Deadlock su Wikipedia.

Le tecniche riportate di seguito possono aiutarti a determinare la causa degli errori ANR.

Statistiche di salute

HealthStats fornisce metriche sull'integrità di un'applicazione acquisendo dati su tempo totale di utente e sistema, tempo di CPU, rete, statistiche radio, ora di accensione/spegnimento dello schermo e sveglie. Ciò può aiutare a misurare l'utilizzo complessivo della CPU e il consumo della batteria.

Debug

Debug aiuta a ispezionare le app Android durante lo sviluppo, inclusi i conteggi di tracciamento e allocazione per identificare i ritardi e i ritardi nelle app. Puoi anche utilizzare Debug per ottenere contatori di memoria di runtime e nativi, nonché metriche di memoria che possono aiutare a identificare l'impronta di memoria di un particolare processo.

InfoUscitaapplicazione

ApplicationExitInfo è disponibile su Android 11 (livello API 30) o versioni successive e fornisce informazioni sul motivo dell'uscita dall'applicazione. Sono inclusi errori ANR, memoria insufficiente, arresti anomali delle app, utilizzo eccessivo della CPU, interruzioni degli utenti, interruzioni del sistema o modifiche alle autorizzazioni di runtime.

Modalità più restrittiva

L'utilizzo di StrictMode ti consente di trovare operazioni di I/O involontari sul thread principale mentre sviluppi l'app. Puoi utilizzare StrictMode a livello di applicazione o di attività.

Attiva finestre di dialogo ANR in background

Android mostra finestre di dialogo ANR per le app che impiegano troppo tempo per elaborare il messaggio trasmesso solo se è attiva l'opzione Mostra tutti gli ANR nelle Opzioni sviluppatore del dispositivo. Per questo motivo, le finestre di dialogo ANR in background non vengono sempre mostrate all'utente, ma potrebbero comunque verificarsi problemi di prestazioni nell'app.

Traceview

Puoi utilizzare Traceview per ottenere una traccia della tua app in esecuzione durante i casi d'uso e identificare i punti in cui è impegnato il thread principale. Per informazioni su come utilizzare Traceview, consulta Profilazione con Traceview e dmtracedump.

Esegui il pull di un file di tracce

Android archivia le informazioni di traccia quando si verifica un errore ANR. Nelle release precedenti del sistema operativo, sul dispositivo è presente un solo file /data/anr/traces.txt. Nelle release più recenti del sistema operativo sono presenti più file /data/anr/anr_*. Puoi accedere alle tracce ANR da un dispositivo o emulatore utilizzando Android Debug Bridge (adb) come root:

adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>

Puoi acquisire una segnalazione di bug da un dispositivo fisico utilizzando l'opzione Crea segnalazione di bug per lo sviluppatore sul dispositivo o il comando adb bugreport sul computer di sviluppo. Per ulteriori informazioni, consulta Acquisire e leggere le segnalazioni di bug.

Risolvere i problemi

Una volta identificato il problema, puoi utilizzare i suggerimenti in questa sezione per risolvere i problemi riscontrati più di frequente.

Codice lento nel thread principale

Identifica i punti nel codice in cui il thread principale dell'app è occupato per più di 5 secondi. Cerca i casi d'uso sospetti nella tua app e prova a riprodurre l'errore ANR.

Ad esempio, la figura 2 mostra una sequenza temporale di Traceview in cui il thread principale è occupato per più di 5 secondi.

Figura 2. La sequenza temporale di Traceview mostra
un thread principale impegnato

Figura 2. Sequenza temporale di Traceview che mostra un thread principale impegnato

La Figura 2 mostra che la maggior parte del codice in questione si verifica nel gestore onClick(View), come mostrato nell'esempio di codice che segue:

Kotlin

override fun onClick(v: View) {
    // This task runs on the main thread.
    BubbleSort.sort(data)
}

Java

@Override
public void onClick(View view) {
    // This task runs on the main thread.
    BubbleSort.sort(data);
}

In questo caso, devi spostare il lavoro in esecuzione nel thread principale in un thread worker. Android Framework include classi che possono aiutarti a spostare l'attività in un thread di lavoro. Consulta Thread worker per ulteriori informazioni.

IO nel thread principale

L'esecuzione di operazioni di I/O sul thread principale è una causa comune di operazioni lente sul thread principale, che può causare errori ANR. Ti consigliamo di spostare tutte le operazioni di I/O in un thread di worker, come mostrato nella sezione precedente.

Alcuni esempi di operazioni di I/O sono le operazioni di rete e di archiviazione. Per ulteriori informazioni, consulta Esecuzione di operazioni di rete e Salvataggio dei dati.

Conflitto blocco

In alcuni scenari, l'opera che causa l'errore ANR non viene eseguita direttamente nel thread principale dell'app. Se un thread worker contiene un blocco su una risorsa che il thread principale richiede per completare il proprio lavoro, potrebbe verificarsi un errore ANR.

Ad esempio, la figura 4 mostra una sequenza temporale di Traceview in cui la maggior parte del lavoro viene eseguita su un thread di lavoro.

Figura 4. Sequenza temporale di Traceview che mostra il lavoro
in esecuzione su un thread worker

Figura 4. Sequenza temporale di Traceview che mostra il lavoro in esecuzione su un thread worker

Tuttavia, se i tuoi utenti continuano a riscontrare errori ANR, dovresti controllare lo stato del thread principale in Monitoraggio dispositivi Android. In genere, il thread principale si trova nello stato RUNNABLE se è pronto per l'aggiornamento dell'interfaccia utente ed è generalmente reattivo.

Tuttavia, se il thread principale non riesce a riprendere l'esecuzione, è nello stato BLOCKED e non può rispondere agli eventi. Lo stato viene visualizzato sul monitor del dispositivo Android come Monitor o Attendi, come mostrato nella figura 5.

Figura 5. Thread principale nella funzionalità
Monitora lo stato

Figura 5. Thread principale nello stato di monitoraggio

La traccia seguente mostra il thread principale di un'app che è bloccato in attesa di una risorsa:

...
AsyncTask #2" prio=5 tid=18 Runnable
  | group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
  | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
  | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
  | stack=0x94a7e000-0x94a80000 stackSize=1038KB
  | held mutexes= "mutator lock"(shared held)
  at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
  - locked <0x083105ee> (a java.lang.Boolean)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
  at android.os.AsyncTask$2.call(AsyncTask.java:305)
  at java.util.concurrent.FutureTask.run(FutureTask.java:237)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
  at java.lang.Thread.run(Thread.java:761)
...

L'analisi della traccia può aiutarti a individuare il codice che blocca il thread principale. Il codice seguente è responsabile della conservazione del blocco che blocca il thread principale nella traccia precedente:

Kotlin

override fun onClick(v: View) {
    // The worker thread holds a lock on lockedResource
    LockTask().execute(data)

    synchronized(lockedResource) {
        // The main thread requires lockedResource here
        // but it has to wait until LockTask finishes using it.
    }
}

class LockTask : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? =
            synchronized(lockedResource) {
                // This is a long-running operation, which makes
                // the lock last for a long time
                BubbleSort.sort(params[0])
            }
}

Java

@Override
public void onClick(View v) {
    // The worker thread holds a lock on lockedResource
   new LockTask().execute(data);

   synchronized (lockedResource) {
       // The main thread requires lockedResource here
       // but it has to wait until LockTask finishes using it.
   }
}

public class LockTask extends AsyncTask<Integer[], Integer, Long> {
   @Override
   protected Long doInBackground(Integer[]... params) {
       synchronized (lockedResource) {
           // This is a long-running operation, which makes
           // the lock last for a long time
           BubbleSort.sort(params[0]);
       }
   }
}

Un altro esempio è il thread principale di un'app in attesa di un risultato da un thread di lavoro, come mostrato nel codice seguente. Tieni presente che l'utilizzo di wait() e notify() non è un modello consigliato in Kotlin, che ha i propri meccanismi per la gestione della contemporaneità. Quando utilizzi Kotlin, se possibile, dovresti usare meccanismi specifici di Kotlin.

Kotlin

fun onClick(v: View) {
    val lock = java.lang.Object()
    val waitTask = WaitTask(lock)
    synchronized(lock) {
        try {
            waitTask.execute(data)
            // Wait for this worker thread’s notification
            lock.wait()
        } catch (e: InterruptedException) {
        }
    }
}

internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? {
        synchronized(lock) {
            BubbleSort.sort(params[0])
            // Finished, notify the main thread
            lock.notify()
        }
    }
}

Java

public void onClick(View v) {
   WaitTask waitTask = new WaitTask();
   synchronized (waitTask) {
       try {
           waitTask.execute(data);
           // Wait for this worker thread’s notification
           waitTask.wait();
       } catch (InterruptedException e) {}
   }
}

class WaitTask extends AsyncTask<Integer[], Integer, Long> {
   @Override
   protected Long doInBackground(Integer[]... params) {
       synchronized (this) {
           BubbleSort.sort(params[0]);
           // Finished, notify the main thread
           notify();
       }
   }
}

Esistono alcune altre situazioni che possono bloccare il thread principale, inclusi i thread che utilizzano Lock, Semaphore, nonché un pool di risorse (ad esempio un pool di connessioni al database) o altri meccanismi di esclusione reciproca (mutex).

Dovresti valutare i blocchi conservati nell'app per le risorse in generale, ma se vuoi evitare gli errori ANR, devi guardare i blocchi bloccati per le risorse richieste dal thread principale.

Assicurati che i blocchi siano trattenuti per il minor tempo possibile o, meglio ancora, valuta se l'app necessita della sospensione. Se utilizzi il blocco per determinare quando aggiornare la UI in base all'elaborazione di un thread worker, utilizza meccanismi come onProgressUpdate() e onPostExecute() per comunicare tra il worker e i thread principali.

Deadlock

Un deadlock si verifica quando un thread entra in uno stato di attesa perché una risorsa richiesta è trattenuta da un altro thread, che è anche in attesa di una risorsa mantenuta dal primo thread. Se il thread principale dell'app è in questa situazione, è probabile che si verifichino ANR.

I deadlock sono un fenomeno ben studiato nell'informatica e esistono algoritmi di prevenzione dei deadlock che possono essere utilizzati per evitare i deadlock.

Per ulteriori informazioni, consulta le pagine relative a Deadlock e Algoritmi di prevenzione dei deadlock su Wikipedia.

Ricevitori di trasmissioni lente

Le app possono rispondere a messaggi trasmessi, ad esempio tramite l'attivazione o la disattivazione della modalità aereo o una modifica dello stato di connettività, tramite ricevitori. Si verifica un errore ANR quando un'app impiega troppo tempo per elaborare l'annuncio.

Un errore ANR si verifica nei seguenti casi:

  • Un ricevitore non ha terminato l'esecuzione del metodo onReceive() in un periodo di tempo considerevole.
  • Un ricevitore broadcast chiama goAsync() e non riesce a chiamare finish() sull'oggetto PendingResult.

L'app dovrebbe eseguire operazioni brevi soltanto nel metodo onReceive() di un BroadcastReceiver. Tuttavia, se la tua app richiede un'elaborazione più complessa a seguito di un annuncio, devi rimandare l'attività a IntentService.

Puoi utilizzare strumenti come Traceview per identificare se il ricevitore della trasmissione esegue operazioni a lunga esecuzione sul thread principale dell'app. Ad esempio, la figura 6 mostra la sequenza temporale di un ricevitore di broadcast che elabora un messaggio nel thread principale per circa 100 secondi.

Figura 6. Sequenza temporale di Traceview che mostra il lavoro &quot;BroadcastRicevir&quot; sul
thread principale

Figura 6. Sequenza temporale di Traceview che mostra il lavoro BroadcastReceiver nel thread principale

Questo comportamento può essere causato dall'esecuzione di operazioni a lunga esecuzione sul metodo onReceive() dell'BroadcastReceiver, come mostrato nell'esempio seguente:

Kotlin

override fun onReceive(context: Context, intent: Intent) {
    // This is a long-running operation
    BubbleSort.sort(data)
}

Java

@Override
public void onReceive(Context context, Intent intent) {
    // This is a long-running operation
    BubbleSort.sort(data);
}

In situazioni come queste, consigliamo di spostare l'operazione a lunga esecuzione in una IntentService perché utilizza un thread di lavoro per l'esecuzione delle sue operazioni. Il codice seguente mostra come utilizzare un elemento IntentService per elaborare un'operazione a lunga esecuzione:

Kotlin

override fun onReceive(context: Context, intent: Intent) {
    Intent(context, MyIntentService::class.java).also { intentService ->
        // The task now runs on a worker thread.
        context.startService(intentService)
    }
}

class MyIntentService : IntentService("MyIntentService") {
    override fun onHandleIntent(intent: Intent?) {
        BubbleSort.sort(data)
    }
}

Java

@Override
public void onReceive(Context context, Intent intent) {
    // The task now runs on a worker thread.
    Intent intentService = new Intent(context, MyIntentService.class);
    context.startService(intentService);
}

public class MyIntentService extends IntentService {
   @Override
   protected void onHandleIntent(@Nullable Intent intent) {
       BubbleSort.sort(data);
   }
}

Come risultato dell'utilizzo di IntentService, l'operazione a lunga esecuzione viene eseguita su un thread di lavoro anziché sul thread principale. La Figura 7 mostra il lavoro differito al thread di lavoro nella sequenza temporale di Traceview.

Figura 7. Sequenza temporale di Traceview che mostra il messaggio
trasmesso elaborato in un thread di lavoro

Figura 7. Sequenza temporale di Traceview che mostra il messaggio trasmesso elaborato in un thread di lavoro

Il ricevitore può utilizzare goAsync() per segnalare al sistema che ha bisogno di più tempo per elaborare il messaggio. Tuttavia, devi chiamare finish() nell'oggetto PendingResult. L'esempio seguente mostra come chiamare finish() per consentire al sistema di riciclare il ricevitore della trasmissione ed evitare un errore ANR:

Kotlin

val pendingResult = goAsync()

object : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? {
        // This is a long-running operation
        BubbleSort.sort(params[0])
        pendingResult.finish()
        return 0L
    }
}.execute(data)

Java

final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
   @Override
   protected Long doInBackground(Integer[]... params) {
       // This is a long-running operation
       BubbleSort.sort(params[0]);
       pendingResult.finish();
   }
}.execute(data);

Tuttavia, lo spostamento del codice da un ricevitore della trasmissione lento a un altro thread e l'utilizzo di goAsync() non risolverà l'errore ANR se la trasmissione è in background. Il timeout ANR è ancora valido.

Attività di gioco

La libreria GameActivity ha ridotto gli errori ANR nei case study di giochi e app scritti in C o C++. Se sostituisci l'attività nativa esistente con GameActivity, puoi ridurre il blocco dei thread dell'interfaccia utente e impedire il verificarsi di alcuni ANR.

Per ulteriori informazioni sugli errori ANR, consulta la pagina Mantieni reattiva l'app. Per ulteriori informazioni sui thread, consulta Prestazioni del threading.

  • Nota: il testo del link viene visualizzato quando JavaScript è disattivato
  • Wakeup eccessivi