web-dev-qa-db-fra.com

Moyen idéal pour annuler une AsyncTask en cours d'exécution

J'exécute des opérations de récupération de fichier audio et de lecture de fichier audio à distance dans un fil d'arrière-plan en utilisant AsyncTask. Une barre de progression Cancellable s'affiche pour indiquer le temps d'exécution de l'opération d'extraction. 

Je souhaite annuler/abandonner l'exécution AsyncTask lorsque l'utilisateur annule (décide de) l'opération. Quel est le moyen idéal pour gérer un tel cas?

107
Samuh

Je viens de découvrir que AlertDialogs 'boolean cancel(...); que j'utilise partout ne fait en réalité rien. Génial.
Alors... 

public class MyTask extends AsyncTask<Void, Void, Void> {

    private volatile boolean running = true;
    private final ProgressDialog progressDialog;

    public MyTask(Context ctx) {
        progressDialog = gimmeOne(ctx);

        progressDialog.setCancelable(true);
        progressDialog.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                // actually could set running = false; right here, but I'll
                // stick to contract.
                cancel(true);
            }
        });

    }

    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }

    @Override
    protected void onCancelled() {
        running = false;
    }

    @Override
    protected Void doInBackground(Void... params) {

        while (running) {
            // does the hard work
        }
        return null;
    }

    // ...

}
78
yanchenko

Si vous faites des calculs :

  • Vous devez vérifier isCancelled() périodiquement.

Si vous faites une requête HTTP :

  • Enregistrez l’instance de votre HttpGet ou HttpPost quelque part (par exemple, un champ public).
  • Après avoir appelé cancel, appelez request.abort(). Cela fera que IOException sera projeté dans votre doInBackground.

Dans mon cas, j'avais une classe de connecteur que j'ai utilisée dans diverses tâches asynchrones. Pour rester simple, j'ai ajouté une nouvelle méthode abortAllRequests à cette classe et ai appelé cette méthode directement après avoir appelé cancel.

75
wrygiel

Le fait est que l'appel AsyncTask.cancel () appelle uniquement la fonction onCancel dans votre tâche. C'est ici que vous souhaitez gérer la demande d'annulation.

Voici une petite tâche que j'utilise pour déclencher une méthode de mise à jour

private class UpdateTask extends AsyncTask<Void, Void, Void> {

        private boolean running = true;

        @Override
        protected void onCancelled() {
            running = false;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
            onUpdate();
        }

        @Override
        protected Void doInBackground(Void... params) {
             while(running) {
                 publishProgress();
             }
             return null;
        }
     }
20
DonCroco

Simple: n'utilisez pas une AsyncTask. AsyncTask est conçu pour des opérations courtes qui se terminent rapidement (plusieurs dizaines de secondes) et ne doivent donc pas être annulées. La "lecture de fichier audio" n'est pas admissible. Vous n'avez même pas besoin d'un fil de fond pour la lecture d'un fichier audio ordinaire.

11
CommonsWare

Voici comment j'écris mon AsyncTask
le point clé est d’ajouter Thread.sleep (1); 

@Override   protected Integer doInBackground(String... params) {

        Log.d(TAG, PRE + "url:" + params[0]);
        Log.d(TAG, PRE + "file name:" + params[1]);
        downloadPath = params[1];

        int returnCode = SUCCESS;
        FileOutputStream fos = null;
        try {
            URL url = new URL(params[0]);
            File file = new File(params[1]);
            fos = new FileOutputStream(file);

            long startTime = System.currentTimeMillis();
            URLConnection ucon = url.openConnection();
            InputStream is = ucon.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);

            byte[] data = new byte[10240]; 
            int nFinishSize = 0;
            while( bis.read(data, 0, 10240) != -1){
                fos.write(data, 0, 10240);
                nFinishSize += 10240;
                **Thread.sleep( 1 ); // this make cancel method work**
                this.publishProgress(nFinishSize);
            }              
            data = null;    
            Log.d(TAG, "download ready in"
                  + ((System.currentTimeMillis() - startTime) / 1000)
                  + " sec");

        } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                returnCode = FAIL;
        } catch (Exception e){
                 e.printStackTrace();           
        } finally{
            try {
                if(fos != null)
                    fos.close();
            } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                e.printStackTrace();
            }
        }

        return returnCode;
    }
4
Andrew Chan

La seule façon de le faire consiste à vérifier la valeur de la méthode isCancelled () et à arrêter la lecture lorsqu'elle renvoie true.

4
dbyrne

Notre variable de classe AsyncTask globale 

LongOperation LongOperationOdeme = new LongOperation();

Et l'action KEYCODE_BACK qui interrompt AsyncTask

   @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            LongOperationOdeme.cancel(true);
        }
        return super.onKeyDown(keyCode, event);
    }

Ça marche pour moi.

0
Göksel Güren

En ce qui concerne la réponse de Yanchenko du 29 avril 2010: L’utilisation de la méthode "while (running)" est judicieuse lorsque votre code sous "doInBackground" doit être exécuté plusieurs fois à chaque exécution de la tâche Async. Si votre code sous 'doInBackground' ne doit être exécuté qu'une seule fois par exécution de la AsyncTask, le fait d'encapsuler tout votre code sous 'doInBackground' dans une boucle 'while (en cours)' n'arrêtera pas le code d'arrière-plan (thread d'arrière-plan) lorsque le AsyncTask elle-même est annulée, car la condition 'while (running)' ne sera évaluée que lorsque tout le code de la boucle while aura été exécuté au moins une fois. Vous devriez donc soit (A.) Diviser votre code sous 'doInBackground' en plusieurs blocs 'while (en cours d'exécution' ') ou (B.) Effectuer de nombreuses vérifications' isCancelled 'tout au long de votre code' doInBackground ', comme expliqué sous "Annuler une tâche" à https://developer.Android.com/reference/Android/os/AsyncTask.html .

Pour l'option (a.), On peut donc modifier la réponse de Yanchenko comme suit:

public class MyTask extends AsyncTask<Void, Void, Void> {

private volatile boolean running = true;

//...

@Override
protected void onCancelled() {
    running = false;
}

@Override
protected Void doInBackground(Void... params) {

    // does the hard work

    while (running) {
        // part 1 of the hard work
    }

    while (running) {
        // part 2 of the hard work
    }

    // ...

    while (running) {
        // part x of the hard work
    }
    return null;
}

// ...

Pour l'option (b.), Votre code dans 'doInBackground' ressemblera à ceci:

public class MyTask extends AsyncTask<Void, Void, Void> {

//...

@Override
protected Void doInBackground(Void... params) {

    // part 1 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // part 2 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // ...

    // part x of the hard work
    // ...
    if (isCancelled()) {return null;}
}

// ...
0
Alex Ivan Howard

Je n'aime pas forcer inutilement l'interruption de mes tâches asynchrones avec cancel(true) car elles peuvent avoir des ressources à libérer, telles que la fermeture de sockets ou de flux de fichiers, l'écriture de données dans la base de données locale, etc. la tâche asynchrone refuse de se terminer une partie du temps, par exemple parfois lorsque l'activité principale est fermée et que je demande à la tâche asynchrone de se terminer depuis la méthode onPause() de l'activité. Il ne suffit donc pas d'appeler running = false. Je dois choisir une solution mixte: appelez running = false, puis donnez quelques millisecondes à la tâche asynchrone pour terminer, puis appelez cancel(false) ou cancel(true).

if (backgroundTask != null) {
    backgroundTask.requestTermination();
    try {
        Thread.sleep((int)(0.5 * 1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (backgroundTask.getStatus() != AsyncTask.Status.FINISHED) {
        backgroundTask.cancel(false);
    }
    backgroundTask = null;
}

De ce fait, une fois que doInBackground() est terminé, la méthode onCancelled() est parfois appelée et parfois onPostExecute(). Mais au moins, la fin de la tâche asynchrone est garantie.

0
Piovezan