Android AsyncTask per operazioni di lunga durata


90

Citando la documentazione per AsyncTask trovata qui , dice:

Gli AsyncTasks dovrebbero idealmente essere usati per operazioni brevi (pochi secondi al massimo). Se hai bisogno di mantenere i thread in esecuzione per lunghi periodi di tempo, ti consigliamo vivamente di usare le varie API fornite dal pacakge java.util.concurrent come Executor, ThreadPoolExecutor e FutureTask.

Ora sorge la mia domanda: perché? La doInBackground()funzione viene eseguita dal thread dell'interfaccia utente, quindi che male c'è dall'avere un'operazione di lunga durata qui?


2
Il problema che ho dovuto affrontare durante la distribuzione di un'app (che utilizza asyncTask) su un dispositivo reale è stato che la funzione di lunga durata doInBackgroundblocca lo schermo se non viene utilizzata una barra di avanzamento.
venkatKA

2
Poiché un AsyncTask è legato all'attività in cui viene avviato. Quindi, se l'attività viene interrotta, anche la tua istanza AsyncTask può essere interrotta.
IgorGanapolsky

Cosa succede se creo AsyncTask all'interno di un servizio? Non risolverebbe il problema?
Eddy

1
Usa IntentService, soluzione perfetta per eseguire operazioni lunghe in background.
Keyur Thumar

Risposte:


120

È un'ottima domanda, ci vuole tempo come programmatore Android per comprendere appieno il problema. Infatti AsyncTask ha due problemi principali correlati:

  • Sono scarsamente legati al ciclo di vita dell'attività
  • Creano perdite di memoria molto facilmente.

All'interno dell'app RoboSpice Motivations ( disponibile su Google Play ) rispondiamo in dettaglio a questa domanda. Fornirà una visione approfondita di AsyncTask, Loader, le loro caratteristiche e svantaggi e ti introdurrà anche a una soluzione alternativa per le richieste di rete: RoboSpice. Le richieste di rete sono un requisito comune in Android e sono per natura operazioni di lunga durata. Ecco un estratto dall'app:

Il ciclo di vita AsyncTask e Activity

Gli AsyncTask non seguono il ciclo di vita delle istanze di Activity. Se avvii un AsyncTask all'interno di un'attività e ruoti il ​​dispositivo, l'attività verrà distrutta e verrà creata una nuova istanza. Ma AsyncTask non morirà. Continuerà a vivere finché non sarà completo.

E al termine, AsyncTask non aggiornerà l'interfaccia utente della nuova attività. Infatti aggiorna l'istanza precedente dell'attività che non viene più visualizzata. Ciò può portare a un'eccezione del tipo java.lang.IllegalArgumentException: vista non allegata al gestore delle finestre se si utilizza, ad esempio, findViewById per recuperare una vista all'interno dell'attività.

Problema di perdita di memoria

È molto comodo creare AsyncTask come classi interne delle tue attività. Poiché AsyncTask dovrà manipolare le visualizzazioni dell'attività quando l'attività è completa o in corso, l'uso di una classe interna dell'attività sembra conveniente: le classi interne possono accedere direttamente a qualsiasi campo della classe esterna.

Tuttavia, significa che la classe interna manterrà un riferimento invisibile sulla sua istanza di classe esterna: l'attività.

A lungo andare, questo produce una perdita di memoria: se l'AsyncTask dura a lungo, mantiene "viva" l'attività mentre Android vorrebbe sbarazzarsene in quanto non può più essere visualizzato. L'attività non può essere raccolta dai rifiuti e questo è un meccanismo centrale per Android per preservare le risorse sul dispositivo.


È davvero una pessima idea usare AsyncTasks per operazioni di lunga durata. Tuttavia, vanno bene per quelli di breve durata come l'aggiornamento di una vista dopo 1 o 2 secondi.

Ti incoraggio a scaricare l' app RoboSpice Motivations , lo spiega davvero in modo approfondito e fornisce esempi e dimostrazioni dei diversi modi per eseguire alcune operazioni in background.


@Snicolas Ciao. Ho un'app in cui scansiona i dati da un tag NFC e li invia al server. Funziona bene in aree con un buon segnale ma dove nessun segnale l'AsyncTask che fa la webcall continua a funzionare. ad esempio, la finestra di dialogo di avanzamento viene eseguita per minuti e poi quando scompare lo schermo diventa nero e non risponde. Il mio AsyncTask è una classe interna. Sto scrivendo un gestore per annullare l'attività dopo X secondi. L'app sembra inviare vecchi dati al server ore dopo la scansione. Potrebbe essere dovuto al fatto che AsyncTask non termina e quindi forse si completa ore dopo? Apprezzerei qualsiasi intuizione. grazie
turtleboy

Traccia per vedere cosa succede. Ma sì, è del tutto possibile! Se decidi bene la tua attività asincrona, puoi cancellarla piuttosto correttamente, sarebbe un buon punto di partenza se non vuoi migrare a RS oa un servizio ...
Snicolas

@Snicolas Grazie per la risposta. Ho scritto un post su SO ieri delineando il mio problema e mostrando il codice del gestore che ho scritto per provare a fermare AsyncTask dopo 8 secondi. Ti dispiacerebbe dare un'occhiata, se hai tempo? La chiamata di AsyncTask.cancel (true) da un gestore annullerà l'attività correttamente? So che dovrei controllare periodicamente il valore di iscancelled () nel mio doInBackgroud, ma non credo che si applichi alle mie circostanze dato che sto solo effettuando una chiamata web di una riga HttpPost e non pubblicando aggiornamenti sull'interfaccia utente. ad esempio, è possibile creare un HttPost da un IntentService
turtleboy


Dovresti provare RoboSpice (su GitHub). ;)
Snicolas

38

perché ?

Perché AsyncTask, per impostazione predefinita, utilizza un pool di thread che non hai creato . Non legare mai risorse da un pool che non hai creato, poiché non sai quali sono i requisiti di quel pool. E non legare mai risorse da un pool che non hai creato se la documentazione di quel pool ti dice di non farlo, come nel caso qui.

In particolare, a partire da Android 3.2, il pool di thread utilizzato per AsyncTaskimpostazione predefinita (per le app con android:targetSdkVersionimpostato su 13 o superiore) contiene un solo thread: se leghi questo thread a tempo indeterminato, nessuna delle altre attività verrà eseguita.


Grazie per questa spiegazione .. Non sono riuscito a trovare nulla di veramente difettoso nell'uso di AsyncTasks per operazioni di lunga durata (anche se di solito li delego io stesso ai Servizi), ma il tuo argomento sul legare quel ThreadPool (per il quale puoi effettivamente non fare supposizioni sulle sue dimensioni) sembra perfetto. Come supplemento al post di Anup sull'utilizzo di un servizio: quel servizio dovrebbe anche eseguire l'attività su un thread in background e non bloccare il proprio thread principale. Un'opzione potrebbe essere un IntentService o, per requisiti di concorrenza più complessi, applicare la propria strategia di multithreading.
crogiolarsi il

È lo stesso anche se avvio AsyncTask all'interno di un servizio? Voglio dire, l'operazione di lunga durata sarebbe ancora un problema?
Eddy

1
@eddy: Sì, poiché ciò non cambia la natura del pool di thread. Per a Service, usa semplicemente a Threado a ThreadPoolExecutor.
CommonsWare

Grazie @CommonsWare solo l'ultima domanda, TimerTasks condivide gli stessi svantaggi di AsyncTasks? o è una cosa completamente diversa?
Eddy

1
@eddy: proviene TimerTaskda Java standard, non da Android. TimerTaskè stato in gran parte abbandonato a favore di ScheduledExecutorService(che, nonostante il nome, fa parte dello standard Java). Nessuno dei due è legato ad Android, quindi hai ancora bisogno di un servizio se ti aspetti che queste cose vengano eseguite in background. E dovresti davvero considerare AlarmManager, da Android, quindi non hai bisogno di un servizio in giro solo a guardare il ticchettio dell'orologio.
CommonsWare

4

Le attività Aysnc sono thread specializzati che sono ancora pensati per essere utilizzati con la GUI delle app ma pur mantenendo le attività pesanti in termini di risorse del thread dell'interfaccia utente. Quindi, quando cose come l'aggiornamento degli elenchi, la modifica delle visualizzazioni ecc. Richiedono di eseguire alcune operazioni di recupero o di aggiornamento, è necessario utilizzare attività asincrone in modo da poter mantenere queste operazioni fuori dal thread dell'interfaccia utente, ma si noti che queste operazioni sono ancora connesse all'interfaccia utente in qualche modo .

Per le attività a esecuzione più lunga, che non richiedono l'aggiornamento dell'interfaccia utente, è possibile utilizzare i servizi invece perché possono vivere anche senza un'interfaccia utente.

Quindi, per attività brevi, usa attività asincrone perché possono essere uccise dal sistema operativo dopo che la tua attività di spawn muore (di solito non morirà a metà operazione ma completerà il suo compito). E per attività lunghe e ripetitive, utilizza invece i servizi.

per maggiori informazioni, vedi discussioni:

AsyncTask per più di pochi secondi?

e

AsyncTask non si interromperà nemmeno quando l'attività è stata distrutta


1

Il problema con AsyncTask è che se è definita come classe interna non statica dell'attività, avrà un riferimento all'attività. Nello scenario in cui l'attività del contenitore dell'attività asincrona termina, ma il lavoro in background in AsyncTask continua, l'oggetto attività non verrà sottoposto a garbage collection in quanto vi è un riferimento, ciò causa la perdita di memoria.

La soluzione per risolvere questo problema è definire un'attività asincrona come classe interna statica di attività e utilizzare un riferimento debole al contesto.

Tuttavia, è una buona idea utilizzarlo per attività in background semplici e veloci. Per sviluppare un'app con codice pulito, è meglio utilizzare RxJava per eseguire complesse attività in background e aggiornare l'interfaccia utente con i risultati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.