Android – Cancele AsyncTask con fuerza

Implementé AsyncTask en mi actividad:

performBackgroundTask asyncTask = new performBackgroundTask(); asyncTask.execute(); 

Ahora, necesito implementar la funcionalidad del botón “Cancelar”, así que tengo que detener la ejecución de la tarea en ejecución. No sé cómo detengo la tarea en ejecución (tarea de fondo).

Entonces, por favor sugiérame, ¿cómo cancelo el AsyncTask con fuerza?

Actualizar:

Encontré el método Cancel() del mismo, pero encontré que llamar cancel(boolean mayInterruptIfRunning) no necesariamente detiene la ejecución del proceso en segundo plano. Todo lo que parece suceder es que AsyncTask se ejecutará en Cancelar () y no se ejecutará en PostExecute () cuando finalice.

Simplemente marque isCancelled() vez en cuando:

  protected Object doInBackground(Object... x) { while (/* condition */) { // work... if (isCancelled()) break; } return null; } 

Call cancel() en AsyncTask . Si esto cancelará realmente algo depende un poco de lo que está haciendo. Para citar a Romain Guy:

Si llama a cancelar (verdadero), se enviará una interrupción al hilo de fondo, lo que puede ayudar a las tareas interrumpibles. De lo contrario, simplemente debería asegurarse de marcar isCancelled () regularmente en su método doInBackground (). Puede ver ejemplos de esto en code.google.com/p/shelves.

Realmente depende de lo que estás haciendo en tu asynctask.

Si se trata de un bucle que procesa una gran cantidad de archivos, simplemente puede verificar después de cada archivo si el indicador isCanceled () está activado o no y luego romper el bucle si es así.

Si se trata de un comando de una línea que realiza una operación muy larga, no hay mucho que pueda hacer.

La mejor solución sería no utilizar el método de cancelar de la asynctask y usar su propio cancelFlag booleano. A continuación, puede probar este cancelFlag en su postExecute para decidir qué hacer con el resultado.

El caso mencionado en comentarios que es isCancelled() always returns false even i call asynctask.cancel(true); es especialmente perjudicial si cierro mi aplicación, pero la AsyncTask continúa funcionando.

Para resolver esto modifiqué el código propuesto por Jacob Nordfalk de la siguiente manera:

 protected Object doInBackground(Object... x) { while (/* condition */) { // work... if (isCancelled() || (FlagCancelled == true)) break; } return null; } 

y agregó lo siguiente a la actividad principal:

 @Override protected void onStop() { FlagCancelled = true; super.onStop(); } 

Como mi AsyncTask era una clase privada de una de vistas, por lo que los captadores o instaladores de la bandera eran necesarios para informar a la AsyncTask sobre el valor actual de la bandera.

Mis múltiples pruebas (AVD Android 4.2.2, Api 17) han demostrado que si una AsyncTask ya está ejecutando su doInBackground , entonces es isCancelled() reactjs de ninguna manera (es decir, sigue siendo falso) a cualquier bash de cancelarlo, por ejemplo, durante mViewGroup.removeAllViews(); o durante un OnDestroy de MainActivity , cada uno de los cuales conduce a la separación de puntos de vista

  @Override protected void onDetachedFromWindow() { mAsyncTask.cancel(false); // and the same result with mAsyncTask.cancel(true); super.onDetachedFromWindow(); } 

Si doInBackground() forzar la detención de doInBackground() gracias al FlagCancelled introducido, se onPostExecute() , pero ni onCancelled() ni onCancelled(Void result) (desde el nivel API 11) no se invocan. (No tengo idea de por qué, porque deberían invocarse y onPostExecute() no debería, “dice el API de Android: Llamar al método cancel () garantiza que onPostExecute (Object) nunca se invoca.” – IdleSun , respondiendo una pregunta similar ) .

Por otro lado, si el mismo AsyncTask no había iniciado su doInBackground() antes de cancelar, entonces todo está bien, isCancelled() cambia a verdadero y puedo verificarlo en

 @Override protected void onCancelled() { Log.d(TAG, String.format("mAsyncTask - onCancelled: isCancelled = %b, FlagCancelled = %b", this.isCancelled(), FlagCancelled )); super.onCancelled(); } 

Aunque un AsyncTask no se debe usar para operaciones de larga ejecución, a veces puede quedar atrapado en una tarea que no responde (como una llamada HTTP que no responde). En ese caso, puede ser necesario cancelar AsyncTask.

Tenemos desafíos al hacer esto. 1. El diálogo de progreso habitual que se muestra con una AsyncTask es lo primero que se cancela en una AsyncTask cuando el usuario presiona el botón Atrás. 2. AsyncTask puede estar en el método doInBackground

Al crear un dismissDialogListerner en ProgressDialog, un usuario puede presionar el botón Atrás y anular la AsycnTask y cerrar el cuadro de diálogo.

Aquí hay un ejemplo:

 public void openMainLobbyDoor(String username, String password){ if(mOpenDoorAsyncTask == null){ mOpenDoorAsyncTask = (OpenMainDoor) new OpenMainDoor(username, password, Posts.API_URL, mContext, "Please wait while I unlock the front door for you!").execute(null, null, null); } } private class OpenMainDoor extends AsyncTask{ //declare needed variables String username, password, url, loadingMessage; int userValidated; boolean canConfigure; Context context; ProgressDialog progressDialog; public OpenMainDoor(String username, String password, String url, Context context, String loadingMessage){ userValidated = 0; this.username = username; this.password = password; this.url = url; this.context = context; this.loadingMessage = loadingMessage; } /** * used to cancel dialog on configuration changes * @param canConfigure */ public void canConfigureDialog(boolean canConfigure){ this.canConfigure = canConfigure; } @Override protected void onPreExecute(){ progressDialog = new ProgressDialog(this.context); progressDialog.setMessage(loadingMessage); progressDialog.setIndeterminate(true); progressDialog.setCancelable(true); progressDialog.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { mOpenDoorAsyncTask.cancel(true); } }); progressDialog.show(); this.canConfigure = true; } @Override protected Void doInBackground(Void... params) { userValidated = Posts.authenticateNTLMUserLogin(username, password, url, context); while(userValidated == 0){ if(isCancelled()){ break; } } return null; } @Override protected void onPostExecute(Void unused){ //determine if this is still attached to window if(canConfigure) progressDialog.dismiss(); if(userValidated == 1){ saveLoginValues(username, password, true); Toast.makeText(context, R.string.main_login_pass, Toast.LENGTH_SHORT).show(); }else{ saveLoginValues(username, password, false); Toast.makeText(context, R.string.main_login_fail, Toast.LENGTH_SHORT).show(); } nullifyAsyncTask(); } @Override protected void onCancelled(){ Toast.makeText(context, "Open door request cancelled!", Toast.LENGTH_SHORT).show(); nullifyAsyncTask(); } } 

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.