web-dev-qa-db-fra.com

Modèle WeakReference / AsyncTask dans android

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

  • MainActivity crée une AyncTask, lui transmet sa référence.
  • AysncTask, démarre son travail, téléchargeant dix fichiers par exemple
  • L'utilisateur a changé l'orientation de l'appareil. Il en résulte un pointeur orphelin dans la tâche AsyncTask
  • Lorsque la tâche AsyncTask se termine et tente d'accéder à l'activité pour mettre à jour l'état, elle se bloque en raison du pointeur nul.

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.

53

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.

34
Stevie

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:

  • Soit Java arrête le thread et supprime le AsyncTask, car le seul objet qui y contient une référence (l'original Activity) est détruit
  • Ou certains objets internes Java maintiennent une référence à l'objet AsyncTask, bloquant sa récupération de place, laissant effectivement le AsyncTask pour terminer en arrière-plan

Quoi 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 .

7
domsom

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é.

1
Kevin Krumwiede