Question

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

Pour exemple: lorsque l'orientation de l'écran Android change alors l'activité est détruite et recréée. Donc je remplacer la méthode onRetainNonConfigurationInstance () et enregistrer toutes les données téléchargées exécutées dans le AsyncTask. Mon but de le faire est de ne pas avoir AsyncTask exécuter chaque activité de temps est détruite créé lors des changements d'orientation, mais comme je peux voir dans mes journaux l'AsyncTask précédente exécution est encore. (Les données sont enregistrées correctement si)

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

Ce comportement est vraiment étrange et serait vraiment reconnaissant si quelqu'un peut me dire la procédure correcte pour arrêter / annuler la AsyncTask.

Merci

Était-ce utile?

La solution

La réponse donnée par @Romain Guy est correct. Néanmoins, je voudrais ajouter un complément d'information et donner un pointeur vers une bibliothèque ou 2 qui peut être utilisé pour longtemps AsyncTask de course et encore plus pour asynctasks orientée réseau.

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 de cancel. Comme vous téléchargez des choses de l'Internet, je vous suggère fortement prendre soin de votre fil quand il est le blocage IO de l'État. 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
}

Utilisation du drapeau Thread.interrupted aidera votre fil à quitter correctement un état io blocage. Votre fil sera plus sensible à un appel de la méthode de cancel.

AsyncTask défaut de conception

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

  1. Les activités sont mal liées au cycle de vie d'activité et vous ne serez pas obtenir le résultat de votre AsyncTask si votre activité meurt. En effet, oui, vous pouvez, mais ce sera la manière approximative.
  2. AsyncTask ne sont pas très bien documenté. Un naïf, si intuitive, la mise en œuvre et l'utilisation d'un AsyncTask peut rapidement conduire à des fuites de mémoire.

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

Voici la raison pour laquelle AsyncTasks sont mauvaises pour de longues tâches en cours d'exécution. Les raisonnements: suivant est une adaptation de exerpts de motivations RoboSpice :. l'application qui explique pourquoi l'utilisation RoboSpice répond à un besoin sur la plate-forme Android

Le cycle de vie et d'activité AsyncTask

AsyncTasks ne suivent pas le cycle de vie des cas d'activité. Si vous démarrez une AsyncTask dans une activité et vous faites pivoter l'appareil, l'activité sera détruite et sera créé une nouvelle instance. Mais le AsyncTask ne mourra pas. Il sera en vie jusqu'à ce qu'elle se termine.

Et quand il est terminé, le AsyncTask ne sera pas mise à jour de l'interface utilisateur de la nouvelle activité. En effet, il met à jour l'ancienne instance de l'activité n'apparaît pas plus. Cela peut conduire à une exception du type java.lang.IllegalArgumentException: Vue attaché au gestionnaire de fenêtres si vous utiliser, 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 pour créer AsyncTasks que les classes internes de vos activités. Comme le AsyncTask devra manipuler les points de vue de l'activité lorsque la tâche est terminée ou en cours, en utilisant une classe interne de l'activité semble pratique: les classes internes peuvent accès directement tous les champs de la classe externe.

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

À long terme, cela produit une fuite de mémoire: si la AsyncTask dure longtemps, il conserve l'activité « en vie » tandis que Android voudrait se débarrasser de lui car il ne peut plus être affiché. L'activité ne peut pas être ordures collectées et qui est un central mécanisme pour Android de préserver les ressources sur l'appareil.

Les progrès de votre tâche seront perdues

Vous pouvez utiliser des solutions de contournement pour créer une longue course AsyncTask et gérer son cycle de vie en conséquence au cycle de vie de l'activité. Vous pouvez annuler la AsyncTask dans la méthode onStop de votre activité ou vous pouvez laisser votre finition tâche async, et past perdre ses progrès et à l'réassocier instance suivante de votre activité .

Ceci est possible et nous montrons comment, dans les motivations RobopSpice, mais il devient compliqué et le code est vraiment générique. De plus, vous aurez toujours perdre les progrès 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 serait un équivalent plus simple à la AsyncTask avec solution de contournement édition de liens mentionnés ci-dessus.

L'utilisation d'un service Android

La meilleure option est d'utiliser un service pour exécuter vos tâches d'arrière-plan à long fonctionnement. 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 étendue à des choses liées à la non-réseau. Cette bibliothèque a .

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


Il est vraiment une idée très très mal à utiliser AsyncTasks longtemps des opérations en cours d'exécution. Néanmoins, ils sont très bien pour les vivants courts tels que la mise à jour d'un View après 1 ou 2 secondes.

Je vous invite à télécharger le RoboSpice Motivations application , il explique vraiment ce en profondeur et fournit des exemples et des démonstrations des différentes façons de faire un certain réseau des choses liées.


Si vous êtes à la recherche d'une alternative à RoboSpice pour réseau non tâches liées (par exemple sans mise en cache), vous pouvez aussi jeter un oeil à Ruban .

Autres conseils

Romain Guy a raison. En fait, une tâche async est responsable de terminer son propre emploi dans tous les cas. Interrompre n'est pas la meilleure façon, de sorte que vous devriez vérifier en permanence si quelqu'un veut que vous annuler ou d'arrêter votre tâche.

Disons que votre AsyncTask fait quelque chose dans une boucle plusieurs fois. 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 général, vous devez définir un drapeau dans votre classe AsyncTask ou retourner un résultat approprié de votre doInBackground() de telle sorte que, dans votre onPostExecute(), vous pouvez vérifier si vous pouvez terminer ce que vous voulez ou si votre travail a été annulé au milieu.

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

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

Lorsque vous ajoutez cela, votre activité ne se recharge pas sur le changement de configuration, et si vous voulez faire quelques changements lorsque l'orientation vous change juste passer outre la méthode suivante de l'activité:

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

        //your code here
    }

l'activité est recréée sur le changement d'orientation, oui des thats vrai. mais vous pouvez continuer à vous AsyncTask chaque fois que cet événement se produise.

vous vérifiez sur

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

-cheers

De MVC point de vue, l'activité est le Contrôleur ; il est faux pour la Controller pour effectuer des opérations qui survivra Voir (dérivé de android.view.View, vous habituellement réutiliser simplement les classes existantes). Il devrait donc être Modèle de responsabilité pour commencer AsyncTasks.

Si AsyncTask n'est pas dans la piscine de fil (traitement parallèle) vous ne pouvez pas arrêter l'exécution AsyncTask puisqu'il est déjà en cours d'exécution par le CPU et votre arrêt ou commande d'annulation sera exécutée après CPU est libre (cpu est fait avec AsyncTask). Cependant, il est dans la piscine de fil, les tâches seront mises en attente et seront exécutés un par un. Donc, si votre commande d'annulation mis en attente pendant l'exécution de la tâche async, il peut arrêter votre tâche async.

Vous pouvez utiliser class MagicAppRestart de ce poste tuer le processus avec tous AsyncTasks; Android rétablira la pile d'activité (l'utilisateur ne mentionne rien). Il est important de noter que la seule notification avant un redémarrage du processus appelle onPause(); selon le Android logique du cycle de vie de l'application , votre demande doit être prêt à une telle résiliation de toute façon.

Je l'ai essayé, et il semble fonctionner. Néanmoins, au moment que je prévois d'utiliser des méthodes « plus civilisées » comme références faibles de la classe d'application (mes AsyncTasks sont plutôt à court vivre et je l'espère pas tant la consommation mémoire).

Voici un code que vous pouvez jouer avec:

MagicAppRestart.java

package com.xyz;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

/** This activity shows nothing; instead, it restarts the android process */
public class MagicAppRestart extends Activity {
    // Do not forget to add it to AndroidManifest.xml
    // <activity android:name="your.package.name.MagicAppRestart"/>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.exit(0);
    }
    public static void doRestart(Activity anyActivity) {
        anyActivity.startActivity(new Intent(anyActivity.getApplicationContext(), MagicAppRestart.class));
    }
}

Le reste est ce que Eclipse a créé un nouveau projet Android pour com.xyz.AsyncTaskTestActivity :

AsyncTaskTestActivity.java

package com.xyz;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class AsyncTaskTestActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d("~~~~","~~~onCreate ~~~ "+this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    public void onStartButton(View view) {
        Log.d("~~~~","~~~onStartButton {");
        class MyTask extends AsyncTask<Void, Void, Void> {

            @Override
            protected Void doInBackground(Void... params) {
                // TODO Auto-generated method stub
                Log.d("~~~~","~~~doInBackground started");
                try {
                    for (int i=0; i<10; i++) {
                        Log.d("~~~~","~~~sleep#"+i);
                        Thread.sleep(200);
                    }
                    Log.d("~~~~","~~~sleeping over");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Log.d("~~~~","~~~doInBackground ended");
                return null;
            }
            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);
                taskDone();
            }
        }
        MyTask task = new MyTask();
        task.execute(null);
        Log.d("~~~~","~~~onStartButton }");
    }
    private void taskDone() {
        Log.d("~~~~","\n\n~~~taskDone ~~~ "+this+"\n\n");
    }
    public void onStopButton(View view) {
        Log.d("~~~~","~~~onStopButton {");
        MagicAppRestart.doRestart(this);
        Log.d("~~~~","~~~onStopButton }");
    }
    public void onPause() {   Log.d("~~~~","~~~onPause ~~~ "+this);   super.onPause(); }
    public void onStop() {    Log.d("~~~~","~~~onStop ~~~ "+this);    super.onPause(); }
    public void onDestroy() { Log.d("~~~~","~~~onDestroy ~~~ "+this); super.onDestroy(); }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button android:text="Start" android:onClick="onStartButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
    <Button android:text="Stop" android:onClick="onStopButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

</LinearLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xyz"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="7" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AsyncTaskTestActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MagicAppRestart"/>
    </application>
</manifest>

et une partie pertinente des journaux (note que ne onPause est appelé ):

D/~~~~    (13667): ~~~onStartButton {
D/~~~~    (13667): ~~~onStartButton }
D/~~~~    (13667): ~~~doInBackground started
D/~~~~    (13667): ~~~sleep#0
D/~~~~    (13667): ~~~sleep#1
D/~~~~    (13667): ~~~sleep#2
D/~~~~    (13667): ~~~sleep#3
D/~~~~    (13667): ~~~sleep#4
D/~~~~    (13667): ~~~sleep#5
D/~~~~    (13667): ~~~sleep#6
D/~~~~    (13667): ~~~sleep#7
D/~~~~    (13667): ~~~sleep#8
D/~~~~    (13667): ~~~sleep#9
D/~~~~    (13667): ~~~sleeping over
D/~~~~    (13667): ~~~doInBackground ended
D/~~~~    (13667): 
D/~~~~    (13667): 
D/~~~~    (13667): ~~~taskDone ~~~ com.xyz.AsyncTaskTestActivity@40516988
D/~~~~    (13667): 




D/~~~~    (13667): ~~~onStartButton {
D/~~~~    (13667): ~~~onStartButton }
D/~~~~    (13667): ~~~doInBackground started
D/~~~~    (13667): ~~~sleep#0
D/~~~~    (13667): ~~~sleep#1
D/~~~~    (13667): ~~~sleep#2
D/~~~~    (13667): ~~~sleep#3
D/~~~~    (13667): ~~~sleep#4
D/~~~~    (13667): ~~~sleep#5
D/~~~~    (13667): ~~~onStopButton {
I/ActivityManager(   81): Starting: Intent { cmp=com.xyz/.MagicAppRestart } from pid 13667
D/~~~~    (13667): ~~~onStopButton }
D/~~~~    (13667): ~~~onPause ~~~ com.xyz.AsyncTaskTestActivity@40516988
I/ActivityManager(   81): Process com.xyz (pid 13667) has died.
I/WindowManager(   81): WIN DEATH: Window{4073ceb8 com.xyz/com.xyz.AsyncTaskTestActivity paused=false}
I/ActivityManager(   81): Start proc com.xyz for activity com.xyz/.AsyncTaskTestActivity: pid=13698 uid=10101 gids={}
I/ActivityManager(   81): Displayed com.xyz/.AsyncTaskTestActivity: +44ms (total +65ms)
D/~~~~    (13698): ~~~onCreate ~~~ com.xyz.AsyncTaskTestActivity@40517238
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top