2010-04-12 27 views
84

He leído mucho sobre cómo guardar mi estado de instancia o cómo manejar mi actividad que se destruye durante la rotación de la pantalla.¿Cómo se maneja una AsyncTask durante la rotación de la pantalla?

Parece que hay muchas posibilidades, pero no he descubierto cuál funciona mejor para recuperar los resultados de una AsyncTask.

Tengo algunas AsyncTasks que simplemente se vuelven a iniciar y llaman al método isFinishing() de la actividad y si la actividad está terminando no actualizarán nada.

El problema es que tengo una tarea que hace una solicitud a un servicio web que puede fallar o tener éxito y reiniciar la tarea daría lugar a una pérdida financiera para el usuario.

¿Cómo solucionaría esto? ¿Cuáles son las ventajas o desventajas de las posibles soluciones?

+1

Véase mi respuesta [aquí] (http://stackoverflow.com/a/12303649/265521). También puede encontrar [esta información acerca de qué 'setRetainInstance (true)' realmente hace] (http://stackoverflow.com/a/12642237/265521) útil. – Timmmm

+0

lo que haría sería simplemente implementar un servicio local que realice el procesamiento (en un hilo) que está haciendo su tarea asíncrona. Para mostrar los resultados, transmita los datos a su actividad. Ahora la actividad solo es responsable de mostrar los datos y el procesamiento * nunca * se interrumpe por una rotación de pantalla. –

+0

¿Qué hay de usar AsyncTaskLoader en lugar de AsyncTask? –

Respuesta

7

Mi primera sugerencia sería asegurarme de que realmente necesita reiniciar su actividad en una rotación de pantalla (el comportamiento predeterminado). Cada vez que he tenido problemas con la rotación, he agregado este atributo a mi etiqueta <activity> en AndroidManifest.xml, y he estado bien.

android:configChanges="keyboardHidden|orientation" 

Parece raro, pero lo que lo hace a mano de su método de , si no se proporciona uno simplemente lo hace otra cosa que volver a medir la distribución, lo que parece ser una manera perfectamente adecuado de manejar la rotación la mayor parte del tiempo.

+5

Pero eso evitará que la actividad cambie el diseño. Y por lo tanto obliga al usuario a usar su dispositivo en una orientación específica dictada por su aplicación y no por sus necesidades. – Janusz

+0

@Janusz, no hace eso. La actividad se reorienta a las dimensiones de paisaje/retrato y se vuelve a dibujar por el teléfono. Simplemente detiene el reinicio de la actividad, llama a onCreate(), etc. Esto es si está usando un diseño general para todas las dimensiones (como debería en mi opinión). –

+0

¿por qué no es ése el valor predeterminado? Entonces, ¿todos los problemas de matar actividad nunca sucederían? Siempre sospeché por qué esto es necesario – Janusz

10

¡Esta es la pregunta más interesante que he visto con respecto a Android! En realidad, ya he estado buscando la solución durante los últimos meses. Aún no lo he resuelto

tener cuidado, simplemente reemplazando el material

android:configChanges="keyboardHidden|orientation" 

no es suficiente.

Considere el caso cuando el usuario recibe una llamada telefónica mientras su AsyncTask se está ejecutando. Su solicitud ya está siendo procesada por el servidor, por lo que AsyncTask está esperando respuesta. En este momento, tu aplicación entra en segundo plano, porque la aplicación del teléfono acaba de aparecer en primer plano. El sistema operativo puede matar su actividad ya que está en segundo plano.

45

Puede consultar cómo manejo AsyncTask y cambios de orientación en code.google.com/p/shelves. Hay varias maneras de hacerlo, la que elegí en esta aplicación es cancelar cualquier tarea que se esté ejecutando actualmente, guardar su estado e iniciar una nueva con el estado guardado cuando se cree el nuevo Activity. Es fácil de hacer, funciona bien y, como bonificación, se ocupa de detener las tareas cuando el usuario abandona la aplicación.

También puede utilizar onRetainNonConfigurationInstance() para pasar su AsyncTask a la nueva Activity (tenga cuidado de no filtrar el anterior Activity esta manera sin embargo.)

+1

la probé y roté durante las interrupciones de búsqueda de libros y me da menos resultados que cuando no rota, lástima – max4ever

+1

I no pudo encontrar un solo uso de AsyncTask en ese código. Hay una clase UserTask que se ve similar. ¿Este proyecto es anterior a AsyncTask? – devconsole

+6

AsyncTask vino de UserTask. Originalmente escribí UserTask para mis propias aplicaciones y luego lo convertí en AsyncTask. Lo siento, olvidé que se renombró. –

6

¿Por qué no siempre guarda una referencia a la actual en la AsyncTask Singleton proporcionado por Android?

Siempre que una tarea se inicia, en PreExecute o en el constructor, se define:

((Application) getApplication()).setCurrentTask(asyncTask);

Cada vez que termine lo establece en NULL.

De esta manera siempre tiene una referencia que le permite hacer algo así, onCreate o onResume como apropiado para su lógica específica:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

Si es nula usted sabe que en la actualidad no hay ninguno en marcha!

:-)

+0

¿Funcionará? ¿Alguien ha probado esto? ¿Seguirá siendo asesinada la tarea por el sistema si ocurre una interrupción de llamadas telefónicas o si navegamos hacia una actividad nueva y luego regresamos? – Robert

+6

La instancia de la "aplicación" tiene su propio ciclo de vida: el sistema operativo también puede matarlo, por lo que esta solución puede causar un error difícil de reproducir. –

+7

Pensé: si se mata a la aplicación, se mata a toda la aplicación (y, por lo tanto, todas las AsyncTasks también). – manmal

3

En Pro android 4. autor ha sugerido una buena manera, que debe utilizar weak reference.

Weak reference note

3

Para mi punto de vista, es mejor para almacenar AsyncTask través onRetainNonConfigurationInstance desacoplamiento desde el objeto de actividad actual y enlazarlo a un nuevo objeto de actividad después del cambio de orientación. Here Encontré un muy buen ejemplo de cómo trabajar con AsyncTask y ProgressDialog.

1

Una cosa a considerar es si el resultado de AsyncTask debe estar disponible solo para la actividad que inició la tarea. Si es así, entonces Romain Guy's answer es lo mejor. Si debe estar disponible para otras actividades de su aplicación, entonces en onPostExecute puede usar LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished")); 

También deberá asegurarse de que la actividad maneja correctamente la situación cuando se envía la transmisión mientras la actividad está en pausa.

4

La forma más adecuada de hacerlo es utilizar un fragmento para retener la instancia de la tarea asincrónica, durante las rotaciones.

Aquí hay un enlace a un ejemplo muy simple que hace que sea fácil seguir esta técnica en sus aplicaciones.

https://gist.github.com/daichan4649/2480065

+0

Aquí hay otro tutorial que hace uso de fragmentos retenidos: http://blogactivity.wordpress.com/2011/09/01/proper-use-of-asynctask/ – devconsole

1

Tener un vistazo a este post. Esta publicación implica que AsyncTask realiza una operación de larga ejecución y una fuga de memoria cuando ocurre una rotación de pantalla en una aplicación de muestra. La aplicación de ejemplo está disponible en el Android source forge

2

: procesamiento en segundo plano/asíncrono opeartion con cambio de configuración

Para mantener los estados de opeartion asíncrono durante el proceso de fondo: se puede tomar una ayuda de fragmentos.

véanse las siguientes etapas:

Paso 1: Crear un let fragmento sin cabeceras decir tarea de fondo y añadir una clase de tarea asíncrona privado con en ella.

Paso 2 (Paso opcional): si usted quiere poner un cursor de carga en la parte superior para el uso por la actividad código:

Paso 3: En su actividad principal implementar la interfaz BackgroundTaskCallbacks definido en el paso 1

class BackgroundTask extends Fragment { 
public BackgroundTask() { 

} 

// Add a static interface 

static interface BackgroundTaskCallbacks { 
    void onPreExecute(); 

    void onCancelled(); 

    void onPostExecute(); 

    void doInBackground(); 
} 

private BackgroundTaskCallbacks callbacks; 
private PerformAsyncOpeation asyncOperation; 
private boolean isRunning; 
private final String TAG = BackgroundTask.class.getSimpleName(); 

/** 
* Start the async operation. 
*/ 
public void start() { 
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********"); 
    if (!isRunning) { 
     asyncOperation = new PerformAsyncOpeation(); 
     asyncOperation.execute(); 
     isRunning = true; 
    } 
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********"); 
} 

/** 
* Cancel the background task. 
*/ 
public void cancel() { 
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********"); 
    if (isRunning) { 
     asyncOperation.cancel(false); 
     asyncOperation = null; 
     isRunning = false; 
    } 
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********"); 
} 

/** 
* Returns the current state of the background task. 
*/ 
public boolean isRunning() { 
    return isRunning; 
} 

/** 
* Android passes us a reference to the newly created Activity by calling 
* this method after each configuration change. 
*/ 
public void onAttach(Activity activity) { 
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********"); 
    super.onAttach(activity); 
    if (!(activity instanceof BackgroundTaskCallbacks)) { 
     throw new IllegalStateException(
       "Activity must implement the LoginCallbacks interface."); 
    } 

    // Hold a reference to the parent Activity so we can report back the 
    // task's 
    // current progress and results. 
    callbacks = (BackgroundTaskCallbacks) activity; 
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********"); 
} 

public void onCreate(Bundle savedInstanceState) { 
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********"); 
    super.onCreate(savedInstanceState); 
    // Retain this fragment across configuration changes. 
    setRetainInstance(true); 
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********"); 
} 

public void onDetach() { 
    super.onDetach(); 
    callbacks = null; 
} 

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> { 
    protected void onPreExecute() { 
     Log.d(TAG, 
       "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********"); 
     if (callbacks != null) { 
      callbacks.onPreExecute(); 
     } 
     isRunning = true; 
     Log.d(TAG, 
       "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********"); 
    } 

    protected Void doInBackground(Void... params) { 
     Log.d(TAG, 
       "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********"); 
     if (callbacks != null) { 
      callbacks.doInBackground(); 
     } 
     Log.d(TAG, 
       "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********"); 
     return null; 
    } 

    protected void onCancelled() { 
     Log.d(TAG, 
       "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********"); 
     if (callbacks != null) { 
      callbacks.onCancelled(); 
     } 
     isRunning = false; 
     Log.d(TAG, 
       "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********"); 
    } 

    protected void onPostExecute(Void ignore) { 
     Log.d(TAG, 
       "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********"); 
     if (callbacks != null) { 
      callbacks.onPostExecute(); 
     } 
     isRunning = false; 
     Log.d(TAG, 
       "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********"); 
    } 
} 

public void onActivityCreated(Bundle savedInstanceState) { 
    super.onActivityCreated(savedInstanceState); 
    setRetainInstance(true); 
} 

public void onStart() { 
    super.onStart(); 
} 

public void onResume() { 
    super.onResume(); 
} 

public void onPause() { 
    super.onPause(); 
} 

public void onStop() { 
    super.onStop(); 
} 

public class ProgressIndicator extends Dialog { 

public ProgressIndicator(Context context, int theme) { 
    super(context, theme); 
} 

private ProgressBar progressBar; 

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    requestWindowFeature(Window.FEATURE_NO_TITLE); 
    setContentView(R.layout.progress_indicator); 
    this.setCancelable(false); 
    progressBar = (ProgressBar) findViewById(R.id.progressBar); 
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN); 
} 

@Override 
public void show() { 
    super.show(); 
} 

@Override 
public void dismiss() { 
    super.dismiss(); 
} 

@Override 
public void cancel() { 
    super.cancel(); 
} 

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{ 

private static final String KEY_CURRENT_PROGRESS = "current_progress"; 

ProgressIndicator progressIndicator = null; 

private final static String TAG = MyActivity.class.getSimpleName(); 

private BackgroundTask task = null; 

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(//"set your layout here"); 
    initialize your views and widget here ............. 



    FragmentManager fm = getSupportFragmentManager(); 
    task = (BackgroundTask) fm.findFragmentByTag("login"); 

    // If the Fragment is non-null, then it is currently being 
    // retained across a configuration change. 
    if (task == null) { 
     task = new BackgroundTask(); 
     fm.beginTransaction().add(task, "login").commit(); 
    } 

    // Restore saved state 
    if (savedInstanceState != null) { 
     Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: " 
       + task.isRunning()); 
     if (task.isRunning()) { 
      progressIndicator = new ProgressIndicator(this, 
        R.style.TransparentDialog); 
      if (progressIndicator != null) { 
       progressIndicator.show(); 
      } 
     } 
    } 
} 

@Override 
protected void onPause() { 
    // TODO Auto-generated method stub 
    super.onPause(); 

} 

@Override 
protected void onSaveInstanceState(Bundle outState) { 
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState); 
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: " 
      + task.isRunning()); 
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning()); 
    if (progressIndicator != null) { 
     progressIndicator.dismiss(); 
     progressIndicator.cancel(); 
    } 
    progressIndicator = null; 
} 


private void performOperation() { 

      if (!task.isRunning() && progressIndicator == null) { 
       progressIndicator = new ProgressIndicator(this, 
         R.style.TransparentDialog); 
       progressIndicator.show(); 
      } 
      if (task.isRunning()) { 
       task.cancel(); 
      } else { 
       task.start(); 
      } 
     } 


@Override 
protected void onDestroy() { 
    super.onDestroy(); 
    if (progressIndicator != null) { 
     progressIndicator.dismiss(); 
     progressIndicator.cancel(); 
    } 
    progressIndicator = null; 
} 

@Override 
public void onPreExecute() { 
    Log.i(TAG, "CALLING ON PRE EXECUTE"); 
} 

@Override 
public void onCancelled() { 
    Log.i(TAG, "CALLING ON CANCELLED"); 
    if (progressIndicator != null) { 
     progressIndicator.dismiss(); 
     progressIndicator.cancel(); 

} 

public void onPostExecute() { 
    Log.i(TAG, "CALLING ON POST EXECUTE"); 
    if (progressIndicator != null) { 
     progressIndicator.dismiss(); 
     progressIndicator.cancel(); 
     progressIndicator = null; 
    } 
} 

@Override 
public void doInBackground() { 
    // put your code here for background operation 
} 

}

0

Mi solución.

En mi caso, tengo una cadena de AsyncTasks con el mismo contexto. La actividad solo tenía acceso a la primera. Para cancelar cualquier tarea en ejecución hice lo siguiente:

public final class TaskLoader { 

private static AsyncTask task; 

    private TaskLoader() { 
     throw new UnsupportedOperationException(); 
    } 

    public static void setTask(AsyncTask task) { 
     TaskLoader.task = task; 
    } 

    public static void cancel() { 
     TaskLoader.task.cancel(true); 
    } 
} 

Tarea doInBackground():

protected Void doInBackground(Params... params) { 
    TaskLoader.setTask(this); 
    .... 
} 

Actividad onStop() o onPause():

protected void onStop() { 
    super.onStop(); 
    TaskLoader.cancel(); 
} 
0
@Override 
protected void onSaveInstanceState(Bundle outState) { 
    super.onSaveInstanceState(outState); 
    final AddTask task = mAddTask; 
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) { 
     final String bookId = task.getBookId(); 
     task.cancel(true); 

     if (bookId != null) { 
      outState.putBoolean(STATE_ADD_IN_PROGRESS, true); 
      outState.putString(STATE_ADD_BOOK, bookId); 
     } 

     mAddTask = null; 
    } 
} 

@Override 
protected void onRestoreInstanceState(Bundle savedInstanceState) { 
     super.onRestoreInstanceState(savedInstanceState); 
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) { 
     final String id = savedInstanceState.getString(STATE_ADD_BOOK); 
     if (!BooksManager.bookExists(getContentResolver(), id)) { 
      mAddTask = (AddTask) new AddTask().execute(id); 
     } 
    } 
} 
0

También puede agregar android: configChanges = "keyboardHidden | orientation | screenSize"

a su ejemplo manifiesta espero que ayude

<application 
    android:name=".AppController" 
    android:allowBackup="true" 
    android:icon="@mipmap/ic_launcher" 
    android:label="@string/app_name" 
    android:roundIcon="@mipmap/ic_launcher_round" 
    android:supportsRtl="true" 
    android:configChanges="keyboardHidden|orientation|screenSize" 
    android:theme="@style/AppTheme"> 
Cuestiones relacionadas