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?
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;
}
// ...
}
Si vous faites des calculs :
isCancelled()
périodiquement.Si vous faites une requête HTTP :
HttpGet
ou HttpPost
quelque part (par exemple, un champ public).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
.
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;
}
}
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.
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;
}
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.
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.
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;}
}
// ...
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.