Leí mucho sobre cómo guardar el estado de mi instancia o cómo lidiar con la destrucción de mi actividad durante la rotación de la pantalla.
Parece haber muchas posibilidades, pero no he descubierto cuál funciona mejor para recuperar los resultados de una AsyncTask.
Tengo algunas AsyncTasks que simplemente se inician nuevamente y llaman al isFinishing()
método de la actividad y si la actividad está terminando, no actualizarán nada.
El problema es que tengo una tarea que realiza una solicitud a un servicio web que puede fallar o tener éxito y reiniciar la tarea resultaría en una pérdida financiera para el usuario.
Como resolverias esto? ¿Cuáles son las ventajas o desventajas de las posibles soluciones?
setRetainInstance(true)
realmente hace .Respuestas:
Mi primera sugerencia sería asegurarse de que realmente necesita que su actividad se restablezca en una rotación de pantalla (el comportamiento predeterminado). Cada vez que tuve problemas con la rotación, agregué este atributo a mi
<activity>
etiqueta en AndroidManifest.xml, y me fue bien.android:configChanges="keyboardHidden|orientation"
Parece extraño, pero lo que le da a su
onConfigurationChanged()
método, si no proporciona uno, no hace nada más que volver a medir el diseño, que parece ser una forma perfectamente adecuada de manejar la rotación la mayor parte del tiempo. .fuente
Puede ver cómo manejo
AsyncTask
los cambios de orientación y correo electrónico en code.google.com/p/shelves . Hay varias formas de hacerlo, la que elegí en esta aplicación es cancelar cualquier tarea que se esté ejecutando actualmente, guardar su estado y comenzar una nueva con el estado guardado cuandoActivity
se crea la nueva . Es fácil de hacer, funciona bien y, además, se encarga de detener sus tareas cuando el usuario abandona la aplicación.También puede usar
onRetainNonConfigurationInstance()
para pasar suAsyncTask
al nuevoActivity
(sinActivity
embargo, tenga cuidado de no filtrar el anterior de esta manera).fuente
Esta es la pregunta más interesante que he visto con respecto a Android !!! De hecho, ya he estado buscando la solución durante los últimos meses. Aún no lo he resuelto.
Tenga cuidado, simplemente anule
android:configChanges="keyboardHidden|orientation"
las cosas no son suficientes.
Considere el caso en el que el usuario recibe una llamada telefónica mientras se ejecuta AsyncTask. Su solicitud ya está siendo procesada por el servidor, por lo que AsyncTask está esperando respuesta. En este momento, su aplicación pasa a segundo plano, porque la aplicación Teléfono acaba de pasar a primer plano. El sistema operativo puede acabar con su actividad ya que está en segundo plano.
fuente
¿Por qué no mantiene siempre una referencia a la AsyncTask actual en el Singleton proporcionado por Android?
Siempre que se inicie una tarea, en PreExecute o en el constructor, defina:
((Application) getApplication()).setCurrentTask(asyncTask);
Siempre que termine, lo establece en nulo.
De esa manera, siempre tendrá una referencia que le permita hacer algo como onCreate o onResume según corresponda a su lógica específica:
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
Si es nulo, ¡sabes que actualmente no hay ninguno en ejecución!
:-)
fuente
Application
La instancia tiene su propio ciclo de vida: el sistema operativo también puede eliminarla, por lo que esta solución puede causar un error difícil de reproducir.La forma más adecuada de hacerlo es usar un fragmento para retener la instancia de la tarea asíncrona, sobre rotaciones.
Aquí hay un enlace a un ejemplo muy simple que facilita la integración de esta técnica en sus aplicaciones.
https://gist.github.com/daichan4649/2480065
fuente
En
Pro android 4
. El autor ha sugerido una forma agradable que debería utilizarweak reference
.Nota de referencia débil
fuente
Desde mi punto de vista, es mejor almacenar asynctask mediante su
onRetainNonConfigurationInstance
desacoplamiento del objeto de actividad actual y vinculándolo a un nuevo objeto de actividad después del cambio de orientación. Aquí encontré un ejemplo muy bueno de cómo trabajar con AsyncTask y ProgressDialog.fuente
Android: procesamiento en segundo plano / operación asincrónica con cambio de configuración
Para mantener los estados de operación asincrónica durante el proceso en segundo plano: puede tomar la ayuda de fragmentos.
Vea los siguientes pasos:
Paso 1: Cree un fragmento sin encabezado, digamos tarea en segundo plano y agregue una clase de tarea asíncrona privada con ella.
Paso 2 (Paso opcional): si desea colocar un cursor de carga en la parte superior de su actividad, utilice el siguiente código:
Paso 3: En su actividad principal, implemente la interfaz BackgroundTaskCallbacks definida 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 }
}
fuente
Una cosa a considerar es si el resultado de AsyncTask debería estar disponible solo para la actividad que inició la tarea. Si es así, entonces la respuesta de Romain Guy es la mejor. Si debe estar disponible para otras actividades de su aplicación, entonces
onPostExecute
puede usarLocalBroadcastManager
.LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));
También deberá asegurarse de que la actividad maneje correctamente la situación cuando se envía una transmisión mientras la actividad está en pausa.
fuente
Echa un vistazo a esta publicación . Esta publicación implica que AsyncTask realice una operación de ejecución prolongada y una pérdida de memoria cuando la rotación de pantalla ocurre en una aplicación de muestra. La aplicación de muestra está disponible en la fragua de origen.
fuente
Mi solución.
En mi caso, tengo una cadena de AsyncTasks con el mismo contexto. La actividad tenía acceso solo al primero. 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()
oonPause()
:protected void onStop() { super.onStop(); TaskLoader.cancel(); }
fuente
@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); } } }
fuente
también puede agregar android: configChanges = "keyboardHidden | Orientación | ScreenSize"
a su ejemplo manifiesto 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">
fuente