Android AsyncTask pour les opérations de longue durée

90

Citant la documentation pour AsyncTask trouvée ici , il est dit:

AsyncTasks devrait idéalement être utilisé pour des opérations courtes (quelques secondes au maximum). Executor, ThreadPoolExecutor et FutureTask.

Maintenant ma question se pose: pourquoi? La doInBackground()fonction s'exécute sur le thread de l'interface utilisateur, alors quel mal y a-t-il à avoir une opération de longue durée ici?

user1730789
la source
2
Le problème que j'ai rencontré lors du déploiement d'une application (qui utilise asyncTask) sur un appareil réel était que la fonction de longue durée doInBackgroundgèle l'écran si une barre de progression n'est pas utilisée.
venkatKA
2
Parce qu'une AsyncTask est liée à l'activité dans laquelle elle est lancée. Ainsi, si l'activité est tuée, votre instance AsyncTask peut également être tuée.
IgorGanapolsky
Que faire si je crée l'AsyncTask dans un service? Cela ne résoudrait-il pas le problème?
eddy
1
Utilisez IntentService, la solution parfaite pour exécuter une longue opération en arrière-plan.
Keyur Thumar

Réponses:

120

C'est une très bonne question, il faut du temps en tant que programmeur Android pour bien comprendre le problème. En effet, AsyncTask a deux problèmes principaux qui sont liés:

  • Ils sont mal liés au cycle de vie de l'activité
  • Ils créent très facilement des fuites de mémoire.

Dans l' application RoboSpice Motivations ( disponible sur Google Play ), nous répondons à cette question en détail. Il vous donnera une vue approfondie des AsyncTasks, des chargeurs, de leurs fonctionnalités et inconvénients et vous présentera également une solution alternative pour les requêtes réseau: RoboSpice. Les requêtes réseau sont une exigence courante dans Android et sont par nature des opérations de longue durée. Voici un extrait de l'application:

Le cycle de vie AsyncTask et Activity

AsyncTasks ne suit pas le cycle de vie des instances Activity. Si vous démarrez une AsyncTask à l'intérieur d'une activité et que vous faites pivoter l'appareil, l'activité sera détruite et une nouvelle instance sera créée. Mais l'AsyncTask ne mourra pas. Il continuera à vivre jusqu'à ce qu'il soit terminé.

Et une fois terminé, AsyncTask ne mettra pas à jour l'interface utilisateur de la nouvelle activité. En effet, il met à jour l'ancienne instance de l'activité qui n'est plus affichée. Cela peut conduire à une exception de type java.lang.IllegalArgumentException: vue non attachée au gestionnaire de fenêtres si vous utilisez, par exemple, findViewById pour récupérer une vue à l'intérieur de l'activité.

Problème de fuite de mémoire

Il est très pratique de créer des AsyncTasks en tant que classes internes de vos activités. Comme l'AsyncTask devra manipuler les vues de l'activité lorsque la tâche est terminée ou en cours, l'utilisation d'une classe interne de l'activité semble pratique: les classes internes peuvent accéder directement à n'importe quel champ de la classe externe.

Néanmoins, cela signifie que la classe interne contiendra une référence invisible sur son instance de classe externe: l'activité.

Sur le long terme, cela produit une fuite de mémoire: si l'AsyncTask dure longtemps, elle maintient l'activité "vivante" alors qu'Android voudrait s'en débarrasser car elle ne peut plus être affichée. L'activité ne peut pas être récupérée et c'est un mécanisme central pour Android pour préserver les ressources sur l'appareil.


C'est vraiment une très très mauvaise idée d'utiliser AsyncTasks pour des opérations de longue durée. Néanmoins, ils conviennent pour ceux qui sont de courte durée, tels que la mise à jour d'une vue après 1 ou 2 secondes.

Je vous encourage à télécharger l' application RoboSpice Motivations , elle explique vraiment cela en profondeur et fournit des exemples et des démonstrations des différentes façons d'effectuer certaines opérations en arrière-plan.

Snicolas
la source
@Snicolas Salut. J'ai une application où il scanne les données d'une balise NFC et les envoie au serveur. Cela fonctionne bien dans les bonnes zones de signal, mais où aucun signal, l'AsyncTask qui fait l'appel Web continue de fonctionner. Par exemple, la boîte de dialogue de progression s'exécute pendant des minutes, puis lorsqu'elle disparaît, l'écran devient noir et ne répond plus. My AsyncTask est une classe interne. J'écris un gestionnaire pour annuler la tâche après X secondes. L'application semble envoyer d'anciennes données au serveur quelques heures après l'analyse. Cela pourrait-il être dû au fait qu'AsyncTask ne se termine pas, puis peut-être se terminer des heures après? J'apprécierais toute perspicacité. merci
turtleboy
Tracez pour voir ce qui se passe. Mais oui, c'est tout à fait possible! Si vous desin bien votre asynctask, vous pouvez l'annuler plutôt correctement, ce serait un bon point de départ si vous ne voulez pas migrer vers RS ou un service ...
Snicolas
@Snicolas Merci pour la réponse. J'ai publié hier un article sur SO décrivant mon problème et montrant le code du gestionnaire que j'ai écrit pour essayer d'arrêter AsyncTask après 8 secondes. Cela vous dérangerait-il d'y jeter un coup d'œil, si vous en avez le temps? L'appel de AsyncTask.cancel (true) à partir d'un gestionnaire annulera-t-il correctement la tâche? Je sais que je devrais vérifier périodiquement la valeur de iscancelled () dans mon doInBackgroud, mais je ne pense pas que cela s'applique à ma situation car je fais juste un appel Web d'une ligne HttpPost et ne publie pas de mises à jour sur l'interface utilisateur. par exemple, est-il possible de créer un HttPost à partir d'un IntentService
turtleboy
Vous devriez essayer RoboSpice (sur github). ;)
Snicolas
38

Pourquoi ?

Car AsyncTask , par défaut, utilise un pool de threads que vous n'avez pas créé . Ne jamais lier des ressources d'un pool que vous n'avez pas créé, car vous ne savez pas quelles sont les exigences de ce pool. Et n'attachez jamais de ressources d'un pool que vous n'avez pas créé si la documentation de ce pool vous dit de ne pas le faire, comme c'est le cas ici.

En particulier, à partir d'Android 3.2, le pool de threads utilisé par AsyncTaskdéfaut (pour les applications avec une valeur android:targetSdkVersionde 13 ou plus) ne contient qu'un seul thread - si vous liez ce thread indéfiniment, aucune de vos autres tâches ne s'exécutera.

CommonsWare
la source
Merci pour cette explication. Je n'ai rien trouvé de vraiment défectueux à propos de l'utilisation d'AsyncTasks pour les opérations de longue durée (bien que je les délègue généralement moi-même aux Services), mais votre argument sur la liaison de ce ThreadPool (pour lequel vous pouvez en effet ne faire aucune supposition sur sa taille) semble juste. En complément de l'article d'Anup sur l'utilisation d'un service: ce service devrait lui-même exécuter la tâche sur un thread d'arrière-plan et ne pas bloquer son propre thread principal. Une option peut être un IntentService ou, pour des exigences de concurrence plus complexes, appliquer votre propre stratégie multithreading.
baske
Est-ce la même chose même si je lance l'AsyncTask dans un service ?? Je veux dire, est-ce que l'opération de longue durée serait encore un problème?
eddy
1
@eddy: Oui, car cela ne change pas la nature du pool de threads. Pour a Service, utilisez simplement a Threadou a ThreadPoolExecutor.
CommonsWare
Merci @CommonsWare juste la dernière question, est-ce que TimerTasks partage les mêmes inconvénients que AsyncTasks? ou est-ce une tout autre chose?
eddy
1
@eddy: TimerTaskprovient de Java standard, pas d'Android. TimerTaska été largement abandonné au profit de ScheduledExecutorService(qui, malgré son nom, fait partie de Java standard). Ni l'un ni l'autre ne sont liés à Android, et vous avez donc toujours besoin d'un service si vous vous attendez à ce que ces choses s'exécutent en arrière-plan. Et, vous devriez vraiment envisager AlarmManager, à partir d'Android, de sorte que vous n'avez pas besoin d'un service qui traîne juste en regardant le tic-tac de l'horloge.
CommonsWare
4

Les tâches Aysnc sont des threads spécialisés qui sont toujours destinés à être utilisés avec l'interface graphique de vos applications, mais tout en conservant les tâches lourdes en ressources du thread d'interface utilisateur. Ainsi, lorsque des éléments tels que la mise à jour de listes, la modification de vos vues, etc. vous obligent à effectuer des opérations de récupération ou de mise à jour, vous devez utiliser des tâches asynchrones afin de pouvoir garder ces opérations hors du thread de l'interface utilisateur, mais notez que ces opérations sont toujours connectées à l'interface utilisateur. .

Pour les tâches plus longues, qui ne nécessitent pas de mise à jour de l'interface utilisateur, vous pouvez utiliser des services à la place, car ils peuvent vivre même sans interface utilisateur.

Donc, pour les tâches courtes, utilisez des tâches asynchrones car elles peuvent être tuées par le système d'exploitation après la mort de votre activité de frai (généralement ne mourra pas en cours d'opération mais terminera sa tâche). Et pour les tâches longues et répétitives, utilisez plutôt des services.

pour plus d'informations, voir les fils:

AsyncTask pendant plus de quelques secondes?

et

AsyncTask ne s'arrête pas même lorsque l'activité est détruite

Anup Cowkur
la source
1

Le problème avec AsyncTask est que s'il est défini comme une classe interne non statique de l'activité, il aura une référence à l'activité. Dans le scénario où l'activité du conteneur de la tâche asynchrone se termine, mais le travail d'arrière-plan dans AsyncTask se poursuit, l'objet activité ne sera pas récupéré car il y a une référence à celui-ci, cela provoque la fuite de mémoire.

La solution pour résoudre ce problème est de définir la tâche asynchrone en tant que classe interne statique d'activité et à utiliser une référence faible au contexte.

Mais toujours, c'est une bonne idée de l'utiliser pour des tâches d'arrière-plan simples et rapides. Pour développer une application avec un code propre, il est préférable d'utiliser RxJava pour exécuter des tâches d'arrière-plan complexes et mettre à jour l'interface utilisateur avec les résultats.

Arnav Rao
la source