¿Cómo manejar una AsyncTask durante la rotación de pantalla?

88

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?

Janusz
fuente
1
Vea mi respuesta aquí . También puede encontrar útil esta información sobre lo que setRetainInstance(true)realmente hace .
Timmmm
lo que haría es simplemente implementar un servicio local que realiza el procesamiento (en un hilo) que está haciendo su asyncTask. Para mostrar los resultados, transmita los datos a su actividad. Ahora la actividad solo se encarga de mostrar los datos y el procesamiento nunca es interrumpido por una rotación de pantalla.
Alguien en algún lugar
¿Qué hay de usar AsyncTaskLoader en lugar de AsyncTask?
Sourangshu Biswas

Respuestas:

6

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. .

Jim Blackler
fuente
5
Pero eso evitará que la Actividad cambie el diseño. Y por tanto obliga al usuario a utilizar su dispositivo en una orientación específica dictada por su aplicación y no por sus necesidades.
Janusz
77
El uso de esta técnica le impide utilizar fácilmente recursos específicos de la configuración. Por ejemplo, si desea que su diseño o dibujables o cadenas o lo que sea sea diferente en retratos y paisajes, querrá el comportamiento predeterminado. La anulación del cambio de configuración solo debe hacerse en casos muy específicos (un juego, un navegador web, etc.) y no por pereza o conveniencia porque se está restringiendo.
Romain Guy
38
Bueno, eso es todo, Romain. "si desea que su diseño, dibujables, cadenas o lo que sea que sea diferente en retratos y paisajes, querrá el comportamiento predeterminado", creo que es un caso de uso mucho más raro de lo que imagina. Lo que usted llama "casos muy específicos" es la mayoría de los desarrolladores, creo. Usar diseños relativos que funcionen en todas las dimensiones es una buena práctica y no es tan difícil. Hablar de pereza es muy equivocado, estas técnicas son para mejorar la experiencia del usuario, no para reducir el tiempo de desarrollo.
Jim Blackler
2
Descubrí que esto funciona perfecto para LinearLayout, pero cuando se usa RelativeLayout, no vuelve a dibujar el diseño correctamente cuando se cambia al modo horizontal (al menos no en el N1). Vea estas preguntas: stackoverflow.com/questions/2987049/…
JohnRock
9
Estoy de acuerdo con Romain (él sabe de lo que está hablando, desarrolla el SO). ¿Qué sucede cuando desea portar su aplicación a una tableta y su interfaz de usuario se ve horrible cuando se estira? Si adopta el enfoque de esta respuesta, deberá volver a codificar toda su solución porque fue con este truco perezoso.
Austyn Mahoney
46

Puede ver cómo manejo AsyncTasklos 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 cuando Activityse 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 su AsyncTaskal nuevo Activity(sin Activityembargo, tenga cuidado de no filtrar el anterior de esta manera).

Romain Guy
fuente
1
Lo probé y rotar durante la búsqueda de libros se interrumpe y me da menos resultados que cuando no lo
hago
1
No pude encontrar un solo uso de AsyncTask en ese código. Hay una clase UserTask que tiene un aspecto similar. ¿Este proyecto es anterior a AsyncTask?
devconsole
7
AsyncTask vino de UserTask. Originalmente escribí UserTask para mis propias aplicaciones y luego lo convertí en AsyncTask. Lo siento, olvidé que se cambió el nombre.
Romain Guy
@RomainGuy Hola, espero que estés bien. De acuerdo con su código, las solicitudes 2 se envían al servidor, aunque en la primera tarea se cancela pero no se cancela correctamente. No sé por qué. ¿Podría decirme si hay alguna forma de resolver esto?
iamcrypticcoder
10

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.

Vit Khudenko
fuente
6

¿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!

:-)

Neteinstein
fuente
esto funcionara? ¿Alguien ha probado esto? ¿La tarea seguirá siendo eliminada por el sistema si ocurre una interrupción de la llamada telefónica o si navegamos a una nueva actividad y luego regresamos?
Robert
6
ApplicationLa 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.
Vit Khudenko
7
Pensé: si se mata la aplicación, ¿se mata toda la aplicación (y por lo tanto, también todas las AsyncTasks)?
manmal
Creo que la aplicación se puede eliminar sin que todos los asynctasks sean (muy raros). Pero @Arhimed con algunas verificaciones fáciles de hacer al comienzo y al final de cada asynctask, puede evitar los errores.
Neteinstein
3

Desde mi punto de vista, es mejor almacenar asynctask mediante su onRetainNonConfigurationInstancedesacoplamiento 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.

Yury
fuente
2

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
}

}

Piyush Gupta
fuente
1

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 onPostExecutepuede 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 una transmisión mientras la actividad está en pausa.

Juozas Kontvainis
fuente
1

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.

Vahid
fuente
0

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()o onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}
Pitt90
fuente
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);
        }
    }
}
Atif Mahmood
fuente
0

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">
eng mohamed emam
fuente