web-dev-qa-db-fra.com

AsyncTask ne s'arrêtera pas même lorsque l'activité a été détruite

J'ai un objet AsyncTask qui commence à s'exécuter lorsque l'activité est créée et fait des choses en arrière-plan (télécharge jusqu'à 100 images). Tout fonctionne bien mais il y a ce comportement particulier que je ne peux pas comprendre.

Par exemple: lorsque l'orientation de l'écran Android Android change alors l'activité est détruite et recréée. Je remplace donc la méthode onRetainNonConfigurationInstance () et enregistre toutes les données téléchargées exécutées dans AsyncTask. Mon but de le faire cela ne signifie pas que AsyncTask s'exécute à chaque fois que l'activité est détruite-créée pendant les changements d'orientation, mais comme je peux le voir dans mes journaux, la précédente AsyncTask est toujours en cours d'exécution (les données sont cependant enregistrées correctement).

J'ai même essayé d'annuler AsyncTask dans la méthode onDestroy () de l'activité mais les journaux montrent toujours AsyncTask en cours d'exécution.

C'est un comportement vraiment étrange et je serais vraiment reconnaissant si quelqu'un pouvait me dire la bonne procédure pour arrêter/annuler la tâche AsyncTask.

Merci

67
Raja

La réponse donnée par @Romain Guy est correcte. Néanmoins, je voudrais ajouter un complément d'informations et donner un pointeur vers une bibliothèque ou 2 qui peuvent être utilisés pour une AsyncTask de longue durée et encore plus pour des asynctasks orientés réseau.

Les AsyncTasks ont été conçus pour faire des choses en arrière-plan. Et oui, vous pouvez l'arrêter en utilisant la méthode cancel. Lorsque vous téléchargez des trucs sur Internet, je vous suggère fortement prenez soin de votre fil quand il s'agit de l'état de blocage IO . Vous devez organiser votre téléchargement comme suit:

public void download() {
    //get the InputStream from HttpUrlConnection or any other
    //network related stuff
    while( inputStream.read(buffer) != -1 && !Thread.interrupted() ) {
      //copy data to your destination, a file for instance
    }
    //close the stream and other resources
}

En utilisant le Thread.interrupted flag aidera votre thread à quitter correctement un état io de blocage. Votre thread sera plus réactif à une invocation de la méthode cancel.

Faille de conception AsyncTask

Mais si votre AsyncTask dure trop longtemps, vous serez confronté à 2 problèmes différents:

  1. Les activités sont mal liées au cycle de vie de l'activité et vous n'obtiendrez pas le résultat de votre AsyncTask si votre activité meurt. En effet, oui, vous pouvez, mais ce sera la voie à suivre.
  2. AsyncTask n'est pas très bien documenté. Une implémentation et une utilisation naïves, quoique intuitives, d'une tâche asynctrone peuvent rapidement entraîner des fuites de mémoire.

RoboSpice , la bibliothèque que je voudrais présenter, utilise un service d'arrière-plan pour exécuter ce type de requêtes. Il a été conçu pour les requêtes réseau. Il fournit des fonctionnalités supplémentaires telles que la mise en cache automatique des résultats des demandes.

Voici la raison pour laquelle les AsyncTasks sont mauvais pour les tâches de longue durée. Le raisonnement suivant est une adaptation d'extraits de motivations RoboSpice : l'application qui explique pourquoi l'utilisation de RoboSpice répond à un besoin sur la plateforme Android Android).

Le cycle de vie AsyncTask et Activity

Les AsyncTasks ne suivent pas le cycle de vie des instances d'activité. Si vous démarrez une AsyncTask dans une activité et que vous faites pivoter le périphérique, 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 se termine.

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 entraîner 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 la tâche 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é.

À long terme, cela produit une fuite de mémoire: si la tâche AsyncTask dure longtemps, elle maintient l'activité "vivante" alors que 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.

La progression de votre tâche sera perdue

Vous pouvez utiliser certaines solutions de contournement pour créer une tâche asynchrone de longue durée et gérer son cycle de vie en fonction du cycle de vie de l'activité. Vous pouvez soit annuler la tâche AsyncTask dans la méthode onStop de votre activité ou vous pouvez laisser votre tâche asynchrone se terminer, et ne pas perdre sa progression et la relier à l'instance suivante de votre activité =.

C'est possible et nous montrons comment dans les motivations de RobopSpice, mais cela devient compliqué et le code n'est pas vraiment générique. De plus, vous perdrez quand même la progression de votre tâche si l'utilisateur quitte l'activité et revient. Ce même problème apparaît avec les chargeurs, bien qu'il s'agirait d'un équivalent plus simple que la tâche AsyncTask avec la liaison de contournement mentionnée ci-dessus.

Utilisation d'un service Android

La meilleure option consiste à utiliser un service pour exécuter vos tâches d'arrière-plan de longue durée. Et c'est exactement la solution proposée par RoboSpice. Encore une fois, il est conçu pour la mise en réseau mais pourrait être étendu à des éléments non liés au réseau. Cette bibliothèque a un grand nombre de fonctionnalités .

Vous pouvez même vous en faire une idée en moins de 30 secondes grâce à une infographie .


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 sont parfaits pour ceux de courte durée comme la mise à jour d'une vue après 1 ou 2 secondes.

Je vous encourage à télécharger RoboSpice Motivations app , il explique vraiment cela en profondeur et fournit des exemples et des démonstrations des différentes façons de faire des choses liées au réseau.


Si vous cherchez une alternative à RoboSpice pour les tâches non liées au réseau (par exemple sans mise en cache), vous pouvez également consulter Bande .

142
Snicolas

Romain Guy a raison. En fait, une tâche asynchrone est responsable de terminer son propre travail dans tous les cas. L'interruption n'est pas le meilleur moyen, vous devez donc vérifier en permanence si quelqu'un veut que vous annuliez ou arrêtiez votre tâche.

Disons que votre AsyncTask fait plusieurs fois une boucle. Ensuite, vous devriez vérifier isCancelled() dans chaque boucle.

while ( true ) {
    if ( isCancelled())
        break;
    doTheTask();
}

doTheTask() est votre vrai travail et avant de le faire dans chaque boucle, vous vérifiez si votre tâche doit être annulée.

En règle générale, vous devez définir un indicateur dans votre classe AsyncTask ou renvoyer un résultat approprié à partir de votre doInBackground() afin que, dans votre onPostExecute(), vous puissiez vérifier si vous pouvez terminer ce vous voulez ou si votre travail a été annulé au milieu.

14
Cagatay Kalan

l'activité est recréée lors d'un changement d'orientation, oui c'est vrai. mais vous pouvez continuer votre tâche asynctasque chaque fois que cet événement se produit.

tu vérifies

@Override
protected void onCreate(Bundle savedInstanceState) { 
     if ( savedInstanceState == null ) {
           startAsyncTask()
     } else {
           // ** Do Nothing async task will just continue.
     }
}

-à votre santé

1
ralphgabb

Ce qui suit ne résout pas votre problème, mais l'empêche: Dans le manifeste de l'application, procédez comme suit:

    <activity
        Android:name=".(your activity name)"
        Android:label="@string/app_name"
        Android:configChanges="orientation|keyboardHidden|screenSize" > //this line here
    </activity>

Lorsque vous ajoutez ceci, votre activité ne se recharge pas en cas de changement de configuration et si vous souhaitez apporter des modifications lorsque l'orientation change, vous remplacez simplement la méthode suivante de l'activité:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

        //your code here
    }
1
Frane Poljak