J'ai une question concernant cette simple situation fréquente dans Android.
Nous avons une activité principale, nous invoquons une AsyncTask avec la référence de l'activité principale, afin que l'AsyncTask puisse mettre à jour les vues sur la MainActivity.
Je décomposerai l'événement en étapes
La solution pour ce qui précède est de conserver une référence faible dans la tâche AsyncTask comme recommandé par le livre "Pro Android 4"
WeakReference<Activity> weakActivity;
in method onPostExecute
Activity activity = weakActivity.get();
if (activity != null) {
// do your stuff with activity here
}
Comment cela résout-il la situation?
Ma question, si mon asynctask télécharge dix fichiers, et à la fin de 5 l'activité est redémarrée (en raison d'un changement d'orientation) alors mon FileDownloadingTask serait-il invoqué une fois de plus?.
Qu'adviendrait-il de la précédente AsyncTask qui avait été initialement invoquée?
Merci et je m'excuse pour la longueur de la question.
Comment cela résout-il la situation?
Le WeakReference
permet au Activity
d'être récupéré, donc vous n'avez pas de fuite de mémoire.
Une référence nulle signifie que AsyncTask
ne peut pas essayer aveuglément de mettre à jour une interface utilisateur qui n'est plus attachée, ce qui déclencherait des exceptions ( par exemple, vue non attachée au gestionnaire de fenêtres). Bien sûr, vous devez vérifier null pour éviter NPE.
si mon asynctask télécharge dix fichiers, et à la fin de 5 l'activité est redémarrée (en raison d'un changement d'orientation) alors mon FileDownloadingTask sera-t-il invoqué à nouveau?.
Cela dépend de votre implémentation, mais probablement oui - si vous ne faites pas délibérément quelque chose pour rendre inutile un téléchargement répété, comme mettre en cache les résultats quelque part.
Qu'arriverait-il au précédent
AsyncTask
qui avait été initialement appelé?
Dans les versions antérieures de Android, il s'exécutait jusqu'à la fin, téléchargeant tous les fichiers uniquement pour les jeter (ou peut-être les mettre en cache, selon votre implémentation).
Dans les nouveaux Android, je soupçonne que AsyncTask
sont tués avec le Activity
qui les a démarrés, mais ma base de suspicion est seulement que les démos de fuite de mémoire pour RoboSpice = (voir ci-dessous) ne fuit pas réellement sur mes appareils JellyBean.
Si je peux offrir quelques conseils: AsyncTask
ne convient pas pour effectuer des tâches potentiellement longues telles que la mise en réseau.
IntentService
est une meilleure approche (et toujours relativement simple), si un seul thread de travail vous convient. Utilisez un (local) Service
si vous voulez contrôler le pool de threads - et faites attention à ne pas travailler sur le thread principal!
RoboSpice semble bon si vous cherchez un moyen d'effectuer un réseau de manière fiable en arrière-plan (avertissement: je ne l'ai pas essayé; je ne suis pas affilié). Il y a une application de démonstration de RoboSpice Motivations dans le Play Store qui explique pourquoi vous devriez l'utiliser en faisant une démonstration de toutes les choses cela peut mal tourner avec AsyncTask
- y compris la solution de contournement WeakReference.
Voir aussi ce fil: AsyncTask est-il vraiment défectueux sur le plan conceptuel ou ai-je juste quelque chose à manquer?
Mise à jour:
J'ai créé un projet github avec un exemple de téléchargement en utilisant IntentService
pour une autre SO question ( Comment réparer Android.os.NetworkOnMainThreadException) ? ), mais il est également pertinent ici, je pense. Il a l'avantage supplémentaire qu'en renvoyant le résultat via onActivityResult
, un téléchargement qui est en vol lorsque vous tournez l'appareil livrera au redémarré Activity
.
La classe WeakReference
empêche simplement le JRE d'augmenter le compteur de référence pour l'instance donnée.
Je ne vais pas entrer dans la gestion de la mémoire de Java et répondre directement à votre question: le WeakReference
résout la situation en fournissant au AsyncTask
un moyen d'apprendre si son activité parent est toujours valide.
Le changement d'orientation lui-même ne redémarrera pas automatiquement le AsyncTask
. Vous devez coder le comportement souhaité avec les mécanismes connus (onCreate
/onDestroy
, onSave/RestoreInstanceState
).
Concernant l'original AsyncTask
, je ne suis pas sûr à 100% laquelle de ces options se produira:
AsyncTask
, car le seul objet qui y contient une référence (l'original Activity
) est détruitAsyncTask
, bloquant sa récupération de place, laissant effectivement le AsyncTask
pour terminer en arrière-planQuoi qu'il en soit, il serait bon d'interrompre/suspendre et de redémarrer/reprendre manuellement le AsyncTask
(ou de le remettre au nouveau Activity
), ou d'utiliser un Service
à la place .
Comment cela résout-il la situation?
Ce n'est pas le cas.
Le référent d'un WeakReference
est défini sur null
lorsque le garbage collector détermine que le référent est faiblement accessible. Cela ne se produit pas lorsqu'une activité est suspendue, et ne se produit pas nécessairement immédiatement lorsque l'activité est détruite et que le cadre rejette toutes les références à celle-ci. Si le GC ne s'est pas exécuté, il est tout à fait possible que le AsyncTask
se termine tandis que son WeakReference
contient toujours une référence à une activité morte.
Non seulement cela, mais cette approche n'empêche pas le AsyncTask
de consommer inutilement le CPU.
Une meilleure approche consiste à faire en sorte que Activity
conserve une référence forte à AsyncTask
et cancel(...)
it dans la méthode de cycle de vie de démontage appropriée. Le AsyncTask
doit surveiller isCancelled()
et cesser de fonctionner s'il n'est plus nécessaire.
Si vous voulez qu'un AsyncTask
survive aux changements de configuration (mais pas autres formes de destruction d'activité), vous pouvez l'héberger dans un fragment conservé.