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 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.
Gli errori ANR sono un problema perché il thread principale dell'app, responsabile dell'aggiornamento dell'interfaccia utente, non può elaborare gli eventi di input dell'utente o disegnare, causando frustrazione all'utente. Per ulteriori informazioni sul thread principale dell'app, consulta la panoramica su processi e thread.
Un errore ANR viene attivato per la tua app quando si verifica una delle seguenti condizioni:
- Input dispatching timed out: se la tua app non ha risposto a un evento di input (ad esempio una pressione di un tasto o un tocco dello schermo) entro 5 secondi.
- Executing service: 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()not called**: 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 un'attività in primo piano, questo timeout è di 5 secondi. **JobSchedulerinteractions**: se unJobServicenon restituisceJobService.onStartJob()oJobService.onStopJob()entro pochi secondi, oppure 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 errori ANR sono silenziosi e non vengono segnalati all'app. Per le app che hanno come target Android 14 e versioni successive, gli errori ANR sono espliciti e vengono segnalati all'app.
Se la tua app riscontra 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 sul 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'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. Sequenza temporale di Traceview che mostra un thread principale occupato
La Figura 2 mostra che la maggior parte del codice problematico si verifica nel
onClick(View) gestore, come mostrato nel seguente esempio di codice:
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 worker. Android Framework include classi che possono aiutarti a spostare l'attività in un thread di lavoro. Per ulteriori informazioni, consulta la sezione Thread worker.
I/O sul 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 chiamate a SharedPreferences o al database) mentre cercano di derivare lo stato iniziale.
Esegui operazioni di I/O a lunga esecuzione al di fuori del 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 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 Eseguire operazioni di rete e Salvare i dati.
Conflitto blocco
In alcuni scenari, il lavoro che causa l'errore ANR non viene eseguito direttamente sul thread principale dell'app. Se un thread di lavoro mantiene un blocco su una risorsa di cui il thread principale ha bisogno per completare il suo lavoro, potrebbe verificarsi un errore ANR.
Ad esempio, la Figura 3 mostra una sequenza temporale di Traceview in cui la maggior parte del lavoro viene eseguita su un thread di lavoro.

Figura 3. Sequenza temporale di Traceview che mostra il lavoro eseguito su un thread worker
Tuttavia, se gli utenti continuano a riscontrare errori ANR, devi esaminare lo stato del thread principale in Android Device Monitor. In genere, il thread principale è nello stato
RUNNABLE se è pronto per aggiornare l'interfaccia utente ed è generalmente
reattivo.
Tuttavia, se il thread principale non può riprendere l'esecuzione, si trova nello BLOCKED
stato e non può rispondere agli eventi. Lo stato viene visualizzato in Android Device Monitor come
Monitor o Wait, 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'analisi 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 in attesa del risultato di un thread di lavoro, come mostrato nel seguente codice. 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, devi utilizzare i meccanismi specifici di Kotlin, se possibile.
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, 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)
meccanismi.
In generale, devi valutare i blocchi che la tua app mantiene sulle risorse, ma se vuoi evitare errori ANR, devi esaminare i blocchi mantenuti per le risorse richieste dal thread principale.
Assicurati che i blocchi vengano mantenuti per il minor tempo possibile o, ancora meglio, valuta se l'app ha bisogno di mantenere il blocco. Se utilizzi il blocco per determinare quando aggiornare l'interfaccia utente in base all'elaborazione di un thread di lavoro, utilizza meccanismi come onProgressUpdate() e onPostExecute() per comunicare tra i thread di lavoro e principali.
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. Si verifica un errore ANR quando un'app impiega troppo tempo per elaborare l'annuncio.
Si verifica un errore ANR 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 chiamafinishsull'oggettoPendingResult.
La tua app deve eseguire solo operazioni brevi 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 un
ViewModel (sfruttando la potenza di coroutine, ambiti e dispatcher Kotlin)
se l'attività dovrebbe richiedere al massimo pochi secondi, a qualsiasi tipo di contenitore di stato,
o a WorkManager per le attività che dovrebbero richiedere più di pochi 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 sequenza temporale di un broadcast receiver che elabora un messaggio sul thread principale per circa 100 secondi.

Figura 5. Sequenza temporale di Traceview che mostra il lavoro di BroadcastReceiver sul thread principale
Questo comportamento può essere causato dall'esecuzione di operazioni a lunga esecuzione nel
onReceive() metodo di BroadcastReceiver, come mostrato nel
seguente esempio:
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, ti consigliamo di spostare l'operazione a lunga esecuzione in un IntentService perché utilizza un thread di lavoro per eseguire il suo 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);
}
}
IntentServiceDi conseguenza, l'operazione a lunga esecuzione viene eseguita su un thread di lavoro anziché sul thread principale. La Figura 7 mostra il lavoro rimandato al thread di lavoro nella sequenza temporale di Traceview.

Figura 6. Sequenza temporale di Traceview che mostra l'annuncio 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() sul PendingResult oggetto. Il seguente esempio mostra come chiamare finish() per consentire al sistema di riciclare il broadcast receiver 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 broadcast receiver lento a un altro thread e
l'utilizzo di goAsync() non risolveranno l'errore ANR se la trasmissione è in background.
Il timeout ANR è ancora valido.