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.
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()eService.onStartCommand()/Service.onBind()entro pochi secondi. **Service.startForeground()non chiamato**: se la tua app utilizzaContext.startForegroundService()per avviare un nuovo servizio in primo piano, ma il servizio non chiamastartForeground()entro 5 secondi.- Broadcast of intent: se un
BroadcastReceivernon 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 unJobServicenon restituisce un valore daJobService.onStartJob()oJobService.onStopJob()entro pochi secondi o se un job avviato dall'utente viene avviato e la tua app non chiamaJobService.setNotification()entro pochi secondi dalla chiamata diJobService.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
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
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
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
onReceiveentro un periodo di tempo considerevole. - Un broadcast receiver chiama
goAsynce non riesce a chiamarefinishsull'oggettoPendingResult.
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 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
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.