programacion - curso android desarrollo de aplicaciones móviles pdf
Forma ideal de cancelar una ejecución de AsyncTask (9)
Estoy ejecutando operaciones remotas de reproducción de archivos de audio y archivos de audio en una AsyncTask
fondo usando AsyncTask
. Se muestra una barra de progreso cancelable para el momento en que se ejecuta la operación de búsqueda.
Quiero cancelar / cancelar la ejecución de AsyncTask
cuando el usuario cancela (decide en contra de) la operación. ¿Cuál es la forma ideal de manejar un caso así?
Acabo de descubrir que la AlertDialogs
boolean cancel(...);
AlertDialogs
boolean cancel(...);
He estado usando todo el mundo en realidad no hace nada. Estupendo.
Asi que...
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;
}
// ...
}
Así es como escribo mi AsyncTask
el punto clave es agregar 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;
}
Con referencia a la respuesta de Yanchenko del 29 de abril de 2010: Usar un enfoque ''while (running)'' es claro cuando tu código en ''doInBackground'' debe ejecutarse varias veces durante cada ejecución de AsyncTask. Si su código en ''doInBackground'' tiene que ejecutarse solo una vez por ejecución de AsyncTask, envolver todo su código en ''doInBackground'' en un ciclo ''while (running)'' no detendrá la ejecución del código de fondo (hilo de fondo) cuando el AsyncTask se cancela porque la condición ''while (running)'' solo se evaluará una vez que todo el código dentro del ciclo while haya sido ejecutado al menos una vez. Por lo tanto, debe (a) dividir su código en ''doInBackground'' en múltiples bloques ''while (running)'' o (b.) Realizar numerosas comprobaciones ''isCancelled'' en su código ''doInBackground'', como se explica en "Cancelación de una tarea "en https://developer.android.com/reference/android/os/AsyncTask.html .
Para la opción (a.) Uno puede modificar la respuesta de Yanchenko de la siguiente manera:
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;
}
// ...
Para la opción (b.) Su código en ''doInBackground'' se verá más o menos así:
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;}
}
// ...
El problema es que la llamada AsyncTask.cancel () solo llama a la función onCancel en su tarea. Aquí es donde desea manejar la solicitud de cancelación.
Aquí hay una pequeña tarea que uso para activar un método de actualización
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;
}
}
La única forma de hacerlo es comprobando el valor del método isCancelled () y deteniendo la reproducción cuando se devuelve verdadero.
No me gusta forzar la interrupción de mis tareas asíncronas con cancel(true)
innecesariamente porque pueden tener recursos para liberar, como cerrar sockets o flujos de archivos, escribir datos en la base de datos local, etc. Por otro lado, me he enfrentado situaciones en las que la tarea asincrónica se niega a terminar a sí misma una parte del tiempo, por ejemplo, algunas veces cuando la actividad principal se cierra y solicito que la tarea asíncrona termine desde el método onPause()
la actividad. Entonces no se trata simplemente de llamar a running = false
. Tengo que ir por una solución mixta: ambos invocan running = false
, luego le doy a la tarea asincrónica unos milisegundos para que termine y luego llamo cancel(false)
o 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;
}
Como resultado secundario, una vez finalizado doInBackground()
, a veces se llama al método onCancelled()
y, a veces, onPostExecute()
. Pero al menos la terminación de la tarea asincrónica está garantizada.
Nuestra variable de clase AsyncTask global
LongOperation LongOperationOdeme = new LongOperation();
Y acción KEYCODE_BACK que interrumpe AsyncTask
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
LongOperationOdeme.cancel(true);
}
return super.onKeyDown(keyCode, event);
}
Esto funciona para mi.
Simple: no use una AsyncTask
. AsyncTask
está diseñado para operaciones cortas que finalizan rápidamente (decenas de segundos) y, por lo tanto, no necesitan ser canceladas. La "reproducción de archivos de audio" no cumple los requisitos. Ni siquiera necesita un hilo de fondo para la reproducción normal de archivos de audio.
Si estás haciendo cálculos :
-
isCancelled()
verificarisCancelled()
periódicamente.
Si estás haciendo una solicitud HTTP :
- Guarde la instancia de su
HttpGet
oHttpPost
algún lugar (por ejemplo, un campo público). - Después de llamar a
cancel
, llame arequest.abort()
. Esto causará queIOException
sea lanzada dentro de sudoInBackground
.
En mi caso, tenía una clase de conector que utilicé en varias AsyncTasks. Para simplificar, agregué un nuevo método abortAllRequests
a esa clase y llamé a este método directamente después de llamar a cancel
.