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 AsyncTask de larga ejecución e incluso más para asynctasks orientados a la red.
AsyncTasks ha sido diseñado para hacer cosas en segundo plano. Y sí, puedes detenerlo usando el cancel
método. Como descargar cosas de Internet, le recomiendo encarecidamente que se ocupa de su hilo cuando es el estado de bloqueo IO . Debe organizar su descarga de la siguiente manera:
public void download() {
while( inputStream.read(buffer) != -1 && !Thread.interrupted() ) {
}
}
El uso de la Thread.interrupted
bandera ayudará a su hilo a salir correctamente de un estado io de bloqueo. Su hilo responderá mejor a una invocación del cancel
método.
Defecto de diseño de AsyncTask
Pero si su AsyncTask dura demasiado, enfrentará 2 problemas diferentes:
- Las actividades están mal vinculadas al ciclo de vida de la actividad y no obtendrá el resultado de su AsyncTask si su actividad muere. De hecho, sí, puede, pero será el camino más difícil.
- AsyncTask no está muy bien documentado. Una implementación y un uso ingenuo, aunque intuitivo, de un asynctask puede conducir rápidamente a pérdidas de memoria.
RoboSpice , la biblioteca que me gustaría presentar, utiliza 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 que AsyncTasks es malo para tareas de larga ejecución. El siguiente razonamiento es una adaptación de los ejercicios de las motivaciones de RoboSpice : la aplicación que explica por qué el uso de RoboSpice satisface una necesidad en la plataforma Android.
El ciclo de vida de AsyncTask y Activity
AsyncTasks no sigue 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 AsyncTask no morirá. Seguirá viviendo hasta que se complete.
Y cuando se complete, AsyncTask no actualizará la interfaz de usuario 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 adjunta 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, usar 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 externa: la Actividad.
A largo plazo, esto produce una pérdida de memoria: si AsyncTask dura mucho, mantiene la actividad "viva" mientras que a Android le gustaría deshacerse de ella, ya que ya no se puede mostrar. La actividad no se puede recolectar como basura y ese es un mecanismo central para que Android preserve los recursos en el dispositivo.
Se perderá el progreso de su tarea
Puede utilizar algunas soluciones para crear un asynctask de ejecución prolongada 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 pierda su progreso y vuelva a vincularla a la siguiente instancia de su actividad. .
Esto es posible y mostramos cómo en RobopSpice motivaciones, 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 Loaders, aunque sería un equivalente más simple a AsyncTask con la solución alternativa de vinculación mencionada anteriormente.
Usando un servicio de Android
La mejor opción es utilizar un servicio para ejecutar sus tareas en segundo plano de larga duración. Y esa es exactamente la solución propuesta por RoboSpice. Nuevamente, 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 puedes hacerte una idea en menos de 30 segundos gracias a una infografía .
Realmente es una muy muy mala idea usar AsyncTasks para operaciones de larga ejecución. Sin embargo, están bien para los de vida corta, como actualizar una Vista después de 1 o 2 segundos.
Te animo a que descargues la aplicación RoboSpice Motivations , que realmente explica esto en profundidad y proporciona muestras 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 puede echar un vistazo a Tape .
Romain Guy tiene razón. De hecho, una tarea asincrónica es responsable de terminar su propio trabajo en cualquier caso. Interrumpir no es la mejor manera, por lo que debe verificar continuamente si alguien quiere que cancele o detenga su tarea.
Digamos que
AsyncTask
haces algo en un bucle muchas veces. Entonces deberías revisarisCancelled()
cada bucle.while ( true ) { if ( isCancelled()) break; doTheTask(); }
doTheTask()
es su trabajo real y antes de hacerlo en cada ciclo, verifica si su tarea debe cancelarse.Generalmente debes poner una bandera en tu
AsyncTask
clase o devolver un resultado apropiado de tudoInBackground()
para que, en tuonPostExecute()
, puedas comprobar si pudiste terminar lo que quieres o si tu trabajo fue cancelado en el medio.fuente
Lo siguiente no resuelve su problema, pero lo previene: En el manifiesto de la aplicación, haga 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 en el cambio de configuración, y si desea realizar algunos cambios cuando cambia la orientación, simplemente anule el siguiente método de la actividad:
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); //your code here }
fuente
la actividad se recrea al cambiar de orientación, sí, eso es cierto. pero puede 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. } }
-salud
fuente
Desde el punto de vista de MVC , la actividad es el controlador ; es incorrecto que el controlador realice operaciones que sobreviven a la vista (derivada de android.view.View, generalmente solo reutiliza las clases existentes). Por lo tanto, debería ser responsabilidad del modelo iniciar AsyncTasks.
fuente
Se puede utilizar
class MagicAppRestart
a partir de este post para matar el proceso , junto con todos los 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 llamaronPause()
; de acuerdo con la lógica del ciclo de vida de la aplicación de Android , su aplicación debe estar lista para dicha terminación de todos modos.Lo he probado y parece funcionar. Sin embargo, por el momento planeo usar métodos "más civilizados" como referencias débiles de la clase Application (mis AsyncTasks son de corta duración y espero que no consuman tanta 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 creó Eclipse 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
onPause
se llama ):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
fuente