android - rotacion - ¿Cómo manejar una AsyncTask durante la rotación de pantalla?
manual de programacion android pdf (13)
¡Esta es la pregunta más interesante que he visto sobre Android! En realidad, ya he estado buscando la solución durante los últimos meses. Aún no lo he resuelto
Tenga cuidado, simplemente anulando el
android:configChanges="keyboardHidden|orientation"
cosas no son suficientes
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.
Leí mucho sobre cómo guardar mi estado de instancia o cómo manejar mi actividad destruida durante la rotación de pantalla.
Parece que hay muchas posibilidades, pero no he descubierto cuál funciona mejor para recuperar los resultados de una AsyncTask.
Tengo algunos AsyncTasks que simplemente se vuelven a iniciar y llaman al método isFinishing()
de la actividad y, si la actividad está finalizando, 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 resultaría en una pérdida financiera para el usuario.
Como resolverias esto? ¿Cuáles son las ventajas o desventajas de las posibles soluciones?
¿Por qué no siempre mantienes una referencia a la AsyncTask actual en Singleton proporcionada por Android?
Cada vez que se inicia una tarea, en PreExecute o en el generador, se define:
((Application) getApplication()).setCurrentTask(asyncTask);
Siempre que termine, configúralo como nulo.
De esta forma, siempre tendrá una referencia que le permite hacer algo como, onCreate o onResume, según lo apropiado para su lógica específica:
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
Si es nulo, ¡sabes que actualmente no hay ninguno en ejecución!
:-)
Android: procesamiento en segundo plano / Operación Async con cambio de configuración
Para mantener los estados de operación asincrónica durante el proceso en segundo plano, puede tomar una ayuda de fragmentos.
Vea los siguientes pasos:
Paso 1: cree un fragmento sin encabezado, digamos tarea de fondo y agregue una clase de tarea asíncrona privada con ella.
Paso 2 (Paso opcional): si desea colocar un cursor de carga sobre 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
}
}
Desde mi punto de vista, es mejor almacenar asynctask vía onRetainNonConfigurationInstance
desacoplándolo del objeto Activity actual y vinculándolo a un nuevo objeto Activity después de que cambie la orientación. Here encontré un ejemplo muy bueno de cómo trabajar con AsyncTask y ProgressDialog.
Eche un vistazo a esta 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 la forja de origen
En Pro android 4
. autor ha sugerido una buena manera, que debe utilizar weak reference
.
La forma más adecuada de hacerlo es usar un fragmento para retener la instancia de la tarea asincrónica, durante rotaciones.
Aquí hay un enlace a un ejemplo muy simple que hace que sea fácil seguir esta técnica en sus aplicaciones.
Mi primera sugerencia sería asegurarme de que realmente necesite restablecer 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 extraño, pero lo que hace se onConfigurationChanged()
a su método onConfigurationChanged()
, si no proporciona uno, simplemente no hace nada más que volver a medir el diseño, que parece ser una forma perfectamente adecuada de manejar la rotación más del tiempo.
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();
}
Puede verificar cómo manejo AsyncTask
y los cambios de orientación 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 e iniciar una nueva con el estado guardado cuando se crea la nueva 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 usar onRetainNonConfigurationInstance()
para pasar su AsyncTask
a la nueva Activity
(tenga cuidado de no filtrar la Activity
anterior de esta manera).
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, en onPostExecute
puede usar LocalBroadcastManager
.
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));
También deberá asegurarse de que la actividad maneje correctamente la situación cuando se envía la transmisión mientras la actividad está en pausa.
también puedes agregar android: configChanges = "keyboardHidden | orientation | 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">
@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);
}
}
}