ANR (visualizzazioni)

Concetti e implementazione di Jetpack Compose

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

Finestra di dialogo ANR mostrata all'utente.
Figura 1. Finestra di dialogo ANR mostrata all'utente

Gli errori ANR sono un problema perché il thread principale dell'app, responsabile dell'aggiornamento della UI, non può elaborare gli eventi di input dell'utente o disegnare, causando frustrazione nell'utente. Per saperne di più sul thread principale dell'app, consulta la panoramica di processi e thread.

Un errore ANR viene attivato per la tua app quando si verifica una delle seguenti condizioni:

  • Timeout dell'invio dell'input: se l'app non ha risposto a un evento di input (ad esempio la pressione di un tasto o il tocco dello schermo) entro 5 secondi.
  • Esecuzione del servizio: se un servizio dichiarato dalla tua app non riesce a completare l'esecuzione di Service.onCreate() e Service.onStartCommand()/Service.onBind() entro 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.
  • Broadcast of intent: se un BroadcastReceiver non ha terminato l'esecuzione entro un determinato periodo di tempo. Se l'app ha attività in primo piano, questo timeout è di 10 secondi.
  • Interazioni **JobScheduler**: se un JobService non restituisce un valore da JobService.onStartJob() o JobService.onStopJob() entro pochi secondi o se un job avviato dall'utente viene avviato e la tua app non chiama JobService.setNotification() entro pochi secondi dalla chiamata di JobService.onStartJob(). Per le app che hanno come target Android 13 e versioni precedenti, gli ANR sono silenziosi e non vengono segnalati all'app. Per le app che hanno come target Android 14 e versioni successive, gli ANR sono espliciti e vengono segnalati all'app.

Se la tua app presenta errori ANR, puoi utilizzare le indicazioni riportate in questo articolo per diagnosticare e risolvere il problema.

Risolvi i problemi

Dopo aver identificato il problema, puoi utilizzare i suggerimenti in questa sezione per risolvere i problemi più comuni.

Codice lento nel thread principale

Identifica i punti del 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'ANR.

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

Figura 2. Cronologia di Traceview che mostra un thread principale occupato

Figura 2. Cronologia di Traceview che mostra un thread principale occupato

La Figura 2 mostra che la maggior parte del codice che causa problemi si trova nel gestore onClick(View), come mostrato nell'esempio di codice seguente:

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 eseguito nel thread principale in un thread di lavoro. Il framework Android include classi che possono aiutare a spostare l'attività in un thread di lavoro. Per saperne di più, consulta Thread di lavoro.

I/O nel thread principale

L'esecuzione di operazioni di I/O sul thread principale è una causa comune di operazioni lente sul thread principale, che possono causare errori ANR. In Compose, gli sviluppatori spesso attivano accidentalmente letture del disco (come SharedPreferences o chiamate al database) mentre cercano di ricavare lo stato iniziale.

Esegui operazioni di I/O a lunga esecuzione lontano dal livello UI. Utilizza withContext(Dispatchers.IO) in un ViewModel o, ancora meglio, utilizza un Repository nel livello dati. Ti consigliamo di spostare tutte le operazioni di I/O in un thread di lavoro, come mostrato nella sezione precedente.

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

Conflitto blocco

In alcuni scenari, il lavoro che causa l'ANR non viene eseguito direttamente sul thread principale dell'app. Se un thread di lavoro blocca una risorsa necessaria al thread principale per completare il suo lavoro, potrebbe verificarsi un errore ANR.

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

Figura 3. Cronologia di Traceview che mostra il lavoro in esecuzione su un thread di lavoro

Figura 3. Cronologia di Traceview che mostra il lavoro in esecuzione su un thread di lavoro

Tuttavia, se gli utenti continuano a riscontrare ANR, devi controllare lo stato del thread principale in Android Device Monitor. In genere, il thread principale si trova nello stato RUNNABLE se è pronto per aggiornare la UI ed è generalmente reattivo.

Tuttavia, se il thread principale non può riprendere l'esecuzione, si trova nello stato BLOCKED e non può rispondere agli eventi. Lo stato viene visualizzato in Android Device Monitor come Monitor (Monitora) o Wait (Attendi), come mostrato nella Figura 5.

Figura 4. Thread principale nello stato Monitor

Figura 4. Thread principale nello stato Monitor

La seguente traccia mostra il thread principale di un'app 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'esame della traccia può aiutarti a individuare il codice che blocca il thread principale. Il seguente codice è responsabile del mantenimento 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 che attende un risultato da un thread di lavoro, come mostrato nel codice seguente. Tieni presente che l'utilizzo di wait() e notify() non è un pattern consigliato in Kotlin, che ha i propri meccanismi per la gestione della concorrenza. Quando utilizzi Kotlin, se possibile dovresti usare i 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 altre situazioni che possono bloccare il thread principale, tra cui 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).

In generale, dovresti valutare i blocchi che la tua app applica alle risorse, ma se vuoi evitare gli ANR, dovresti esaminare i blocchi applicati alle risorse richieste dal thread principale.

Assicurati che i blocchi vengano mantenuti per il minor tempo possibile o, ancora meglio, valuta se l'app ha effettivamente bisogno del blocco. Se utilizzi il blocco per determinare quando aggiornare la UI in base all'elaborazione di un thread di lavoro, utilizza meccanismi come onProgressUpdate() e onPostExecute() per comunicare tra i thread di lavoro e principale.

Broadcast receiver lenti

Le app possono rispondere ai messaggi di trasmissione, ad esempio attivando o disattivando la modalità aereo o una modifica dello stato della connettività, tramite i broadcast receiver. Un ANR si verifica quando un'app impiega troppo tempo per elaborare l'annuncio.

Un errore ANR si verifica nei seguenti casi:

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

La tua app deve eseguire solo operazioni brevi nel metodo onReceive di un BroadcastReceiver. Tuttavia, se l'app richiede un'elaborazione più complessa come risultato di un annuncio, devi rimandare l'attività a un ViewModel (sfruttando la potenza delle coroutine, degli ambiti e dei dispatcher di Kotlin) se l'attività dovrebbe richiedere al massimo alcuni secondi, qualsiasi tipo di contenitore di stato, o a WorkManager per le attività che dovrebbero richiedere più di alcuni secondi.

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

Figura 5. Cronologia di Traceview che mostra il lavoro di &quot;BroadcastReceiver&quot; sul thread principale

Figura 5. Cronologia di Traceview che mostra il lavoro di BroadcastReceiver sul thread principale

Questo comportamento può essere causato dall'esecuzione di operazioni a lunga esecuzione sul metodo onReceive() di 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, è consigliabile spostare l'operazione a lunga esecuzione in un IntentService perché utilizza un thread di lavoro per eseguire il lavoro. Il seguente codice mostra come utilizzare un 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 6. Cronologia di Traceview che mostra il messaggio di trasmissione elaborato su un thread di lavoro

Figura 6. Cronologia di Traceview che mostra il messaggio di trasmissione elaborato su un thread di lavoro

Il broadcast receiver può utilizzare goAsync() per segnalare al sistema che ha bisogno di più tempo per elaborare il messaggio. Tuttavia, devi chiamare finish() sull'oggetto PendingResult. L'esempio seguente mostra come chiamare finish() per consentire al sistema di riciclare il broadcast receiver ed evitare un 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 broadcast receiver lento a un altro thread e l'utilizzo di goAsync() non risolveranno l'ANR se la trasmissione è in background. Il timeout ANR rimane valido.