android - una - manejo de asynctask
AsyncTask no se detendrĂ¡ incluso cuando la actividad haya destruido (7)
Desde el punto de vista de MVC , Activity is the Controller ; es incorrecto que el Controlador realice operaciones que sobreviven a la Vista (derivadas de android.view.View, generalmente solo reutiliza las clases existentes). Por lo tanto, debería ser responsabilidad del Modelo iniciar AsyncTasks.
Tengo un objeto AsyncTask que comienza a ejecutarse cuando se crea la actividad y hace cosas en el fondo (descarga hasta 100 imágenes). Todo funciona bien, pero existe un comportamiento peculiar que no puedo entender.
Por ejemplo: cuando la orientación de la pantalla de Android cambia, la actividad se destruye y se crea de nuevo. De modo que anulo el método onRetainNonConfigurationInstance () y guardo todos los datos descargados ejecutados en AsyncTask. Mi propósito es no ejecutar AsyncTask cada vez que se destruye la actividad, creada durante los cambios de orientación, pero como puedo ver en mis registros, la AsyncTask anterior aún se está ejecutando. (Los datos se guardan correctamente)
Incluso traté de cancelar AsyncTask en el método onDestroy () de la actividad, pero los registros todavía muestran AsyncTask como en ejecución.
Este es un comportamiento realmente extraño y realmente estaría agradecido si alguien me puede decir el procedimiento correcto para detener / cancelar el AsyncTask.
Gracias
La respuesta dada por @Romain Guy es correcta. Sin embargo, me gustaría agregar un complemento de información y dar un puntero a una biblioteca o 2 que se pueden usar para ejecutar AsyncTask y aún más para las tareas de sincronización orientadas a la red.
AsyncTasks se han diseñado para hacer cosas en segundo plano. Y sí, puedes detenerlo usando el método cancel
. A medida que descargas cosas de Internet, te sugiero encarecidamente que cuides tu hilo cuando se trata del estado de bloqueo IO . Debe organizar su descarga de la siguiente manera:
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
}
El uso del indicador Thread.interrupted
ayudará a su hilo a salir correctamente de un estado io de bloqueo. Su hilo será más receptivo a una invocación del método de cancel
.
Defecto de diseño AsyncTask
Pero si tu AsyncTask dura demasiado tiempo, entonces enfrentarás 2 problemas diferentes:
- Las actividades están poco relacionadas con el ciclo de vida de la actividad y no obtendrá el resultado de su AsyncTask si su actividad muere. De hecho, sí, puedes, pero será el camino más difícil.
- AsyncTask no están muy bien documentados. Una implementación ingenua, aunque intuitiva, y el uso de una asynctask pueden conducir rápidamente a pérdidas de memoria.
RoboSpice , la biblioteca que me gustaría presentar, usa un servicio en segundo plano para ejecutar este tipo de solicitudes. Ha sido diseñado para solicitudes de red. Proporciona funciones adicionales como el almacenamiento en caché automático de los resultados de las solicitudes.
Esta es la razón por la cual las AsyncTasks son malas para las tareas de larga ejecución. El siguiente razonamiento es una adaptación de las motivaciones de RoboSpice : la aplicación que explica por qué el uso de RoboSpice está cubriendo una necesidad en la plataforma Android.
El ciclo de vida AsyncTask y Actividad
AsyncTasks no siguen el ciclo de vida de las instancias de actividad. Si inicia una AsyncTask dentro de una Actividad y gira el dispositivo, la Actividad se destruirá y se creará una nueva instancia. Pero el AsyncTask no morirá. Seguirá viviendo hasta que se complete.
Y cuando se complete, AsyncTask no actualizará la UI de la nueva Actividad. De hecho, actualiza la instancia anterior de la actividad que ya no se muestra. Esto puede llevar a una Excepción del tipo java.lang.IllegalArgumentException: Vista no asociada al administrador de ventanas si usa, por ejemplo, findViewById para recuperar una vista dentro de la Actividad.
Problema de pérdida de memoria
Es muy conveniente crear AsyncTasks como clases internas de sus Actividades. Como AsyncTask necesitará manipular las vistas de la Actividad cuando la tarea esté completa o en progreso, el uso de una clase interna de la Actividad parece conveniente: las clases internas pueden acceder directamente a cualquier campo de la clase externa.
Sin embargo, significa que la clase interna tendrá una referencia invisible en su instancia de clase exterior: la Actividad.
A largo plazo, esto produce una pérdida de memoria: si la AsyncTask dura mucho tiempo, mantiene la actividad "viva", mientras que a Android le gustaría deshacerse de ella, ya que ya no se puede mostrar. La actividad no puede ser recogida de basura y ese es un mecanismo central para que Android preserve los recursos en el dispositivo.
El progreso de tu tarea se perderá
Puede utilizar algunas soluciones para crear una asynctask de larga ejecución y administrar su ciclo de vida de acuerdo con el ciclo de vida de la actividad. Puede cancelar AsyncTask en el método onStop de su actividad o puede dejar que su tarea asincrónica finalice, y no perder su progreso y volver a vincularlo a la siguiente instancia de su actividad .
Esto es posible y mostramos cómo en las motivaciones de RobopSpice, pero se vuelve complicado y el código no es realmente genérico. Además, aún perderá el progreso de su tarea si el usuario abandona la actividad y regresa. Este mismo problema aparece con los cargadores, aunque sería un equivalente más simple al AsyncTask con la solución de reenvío mencionada anteriormente.
Usando un servicio de Android
La mejor opción es usar un servicio para ejecutar sus tareas de fondo de larga ejecución. Y esa es exactamente la solución propuesta por RoboSpice. Una vez más, está diseñado para redes, pero podría extenderse a cosas no relacionadas con la red. Esta biblioteca tiene una gran cantidad de características .
Incluso puede hacerse una idea de ello en menos de 30 segundos gracias a una infographics .
Realmente es una muy mala idea usar AsyncTasks para operaciones de larga ejecución. Sin embargo, están bien para los de corta vida como la actualización de una vista después de 1 o 2 segundos.
Los animo a descargar la aplicación RoboSpice Motivations , realmente explica esto en profundidad y proporciona ejemplos y demostraciones de las diferentes formas de hacer algunas cosas relacionadas con la red.
Si está buscando una alternativa a RoboSpice para tareas no relacionadas con la red (por ejemplo, sin almacenamiento en caché), también podría echarle un vistazo a Tape .
Lo siguiente no resuelve tu problema, pero lo evita: en el manifiesto de la aplicación haz esto:
<activity
android:name=".(your activity name)"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden|screenSize" > //this line here
</activity>
Cuando agrega esto, su actividad no se recarga al cambiar la configuración, y si desea realizar algunos cambios cuando la orientación cambia, simplemente anule el siguiente método de la actividad:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//your code here
}
Puedes usar la class MagicAppRestart
desde esta publicación para matar el proceso junto con todas las AsyncTasks; Android restaurará la pila de actividades (el usuario no mencionará nada). Es importante tener en cuenta que la única notificación antes de reiniciar un proceso es llamar a onPause()
; de acuerdo con la lógica del ciclo de vida de la aplicación Android , su aplicación debe estar lista para tal terminación de todos modos.
Lo probé y parece funcionar. Sin embargo, en el momento en que planeo usar métodos "más civilizados" como referencias débiles de la clase Application (mis AsyncTasks son más bien de vida corta y, con suerte, no consumen mucha memoria).
Aquí hay un código con el que puedes jugar:
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));
}
}
El resto es lo que Eclipse creó para un nuevo proyecto de Android para 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>
y una parte relevante de los registros (tenga en cuenta que solo se llama a onPause
):
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
Romain Guy tiene razón. De hecho, una tarea asincrónica es responsable de terminar su propio trabajo en cualquier caso. La interrupción no es la mejor manera, por lo que debe verificar continuamente si alguien desea que cancele o detenga su tarea.
Digamos que su AsyncTask
hace algo en un bucle muchas veces. Entonces debe verificar isCancelled()
en cada ciclo.
while ( true ) {
if ( isCancelled())
break;
doTheTask();
}
doTheTask()
es su verdadero trabajo y antes de hacerlo en cada ciclo, verifica si su tarea debe ser cancelada.
En general, debe establecer un indicador en su clase AsyncTask
o devolver un resultado apropiado de su doInBackground()
para que, en su onPostExecute()
, pueda verificar si puede finalizar lo que desea o si su trabajo fue cancelado en el medio.
Si asynctask no está en el grupo de subprocesos (procesamiento paralelo), no puede dejar de ejecutar asynctask ya que la CPU ya lo está ejecutando y su comando de detención o cancelación se ejecutará después de que la CPU esté libre (la CPU se realiza con asynctask). Sin embargo, está en el grupo de subprocesos, las tareas se pondrán en cola y se ejecutarán una por una. Por lo tanto, si su comando de cancelación se pone en cola mientras ejecuta la tarea asíncrona, puede detener su tarea asíncrona.
la actividad se recrea en el cambio de orientación, sí, eso es cierto. pero puedes continuar asynctask cada vez que ocurra este evento.
lo revisas
@Override
protected void onCreate(Bundle savedInstanceState) {
if ( savedInstanceState == null ) {
startAsyncTask()
} else {
// ** Do Nothing async task will just continue.
}
}
-aclamaciones