Tarea en segundo plano, diálogo de progreso, cambio de orientación: ¿hay alguna solución 100% funcional?


Descargo algunos datos de Internet en subproceso en segundo plano (uso AsyncTask) y muestro un diálogo de progreso durante la descarga. La orientación cambia, la actividad se reinicia y luego mi AsyncTask se completa-Quiero descartar el diálogo de progreso e iniciar una nueva Actividad. Pero llamar a dismissDialog a veces genera una excepción (probablemente porque la Actividad fue destruida y la nueva Actividad aún no se ha iniciado).

¿Cuál es la mejor manera de manejar este tipo de problema (actualizar la interfaz de usuario desde hilo de fondo que funciona incluso si el usuario cambia de orientación)? ¿Alguien de Google proporcionó alguna "solución oficial"?

Author: grebulon, 2010-09-29

8 answers

Paso #1: Haga su AsyncTask una clase anidada static, o una clase completamente separada, solo que no una clase interna (no estática anidada).

Paso #2: Haga que AsyncTask se aferre a Activity a través de un miembro de datos, establecido a través del constructor y un setter.

Paso #3: Al crear el AsyncTask, proporcione el Activity actual al constructor.

Paso #4: En onRetainNonConfigurationInstance(), devuelve el AsyncTask, después de separarlo de la actividad original, ahora desaparecida.

Paso #5: En onCreate(), si getLastNonConfigurationInstance() no es null, envíalo a tu clase AsyncTask y llama a tu setter para asociar tu nueva actividad con la tarea.

Paso #6: No haga referencia al miembro de datos de actividad de doInBackground().

Si sigue la receta anterior, todo funcionará. onProgressUpdate() y onPostExecute() se suspenden entre el comienzo de onRetainNonConfigurationInstance() y el final del siguiente onCreate().

Aquí hay un proyecto de ejemplo que demuestra la técnica.

Otro enfoque es deshacerse del AsyncTask y mover su trabajo a un IntentService. Esto es particularmente útil si el trabajo a realizar puede ser largo y debe continuar independientemente de lo que el usuario haga en términos de actividades (por ejemplo, descargar un archivo grande). Puede usar una emisión ordenada Intent para que la actividad responda al trabajo que se está realizando (si todavía está en primer plano) o para que el usuario sepa si el trabajo se ha realizado. Aquí hay una entrada de blog con más sobre este patrón.

 335
Author: CommonsWare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-15 02:02:03

La respuesta aceptada fue muy útil, pero no tiene un diálogo de progreso.

Afortunadamente para usted, lector, he creado un ejemplo extremadamente completo y funcional de una AsyncTask con un diálogo de progreso!

  1. La rotación funciona, y el diálogo sobrevive.
  2. Puede cancelar la tarea y el diálogo presionando el botón atrás (si desea este comportamiento).
  3. Utiliza fragmentos.
  4. El diseño del fragmento debajo de la actividad cambia correctamente cuando el dispositivo gira.
 13
Author: Timmmm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-17 07:17:03

He trabajado durante una semana para encontrar una solución a este dilema sin recurrir a la edición del archivo de manifiesto. Los supuestos para esta solución son:

  1. Siempre necesita usar un diálogo de progreso
  2. Solo se realiza una tarea a la vez
  3. Necesita que la tarea persista cuando el teléfono se gira y el diálogo de progreso se descarta automáticamente.

Aplicación

Tendrá que copiar los dos archivos que se encuentran en el al final de esta publicación en tu espacio de trabajo. Solo asegúrese de que:

  1. Todos sus Activity s deben extenderse BaseActivity

  2. En onCreate(), super.onCreate() debe ser llamado después de inicializar cualquier miembro que necesite ser accedido por sus ASyncTasks. También, invalide getContentViewId() para proporcionar el id de diseño del formulario.

  3. Anular onCreateDialog() como de costumbre para crear diálogos administrados por la actividad.

  4. Vea el código a continuación para una clase interna estática de muestra para hacer su AsyncTasks. Puede almacenar su resultado en mResult para acceder más tarde.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

Y finalmente, para lanzar tu nueva tarea:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

¡Eso es! Espero que esta solución robusta ayude a alguien.

BaseActivity.java (organice las importaciones usted mismo)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}
 8
Author: Oleg Vaskevich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-08 03:26:43

¿Alguien de Google proporcionó alguna "solución oficial"?

Sí.

La solución es más una propuesta de arquitectura de aplicación que solo un código.

Ellos propusieron 3 patrones de diseño eso permite que una aplicación funcione en sincronización con un servidor, independientemente del estado de la aplicación (funcionará incluso si el usuario termina la aplicación, el usuario cambia de pantalla, la aplicación se termina, cada otro posible estado donde una operación de datos de fondo podría interrumpirse, esto lo cubre)

La propuesta se explica en el discurso Android REST client applications durante Google I/O 2010 de Virgil Dobjanschi. Es de 1 hora de duración, pero es extremadamente vale la pena ver.

La base de esto es abstraer las operaciones de red a un Service que funciona de forma independiente a cualquier Activity en la aplicación. Si está trabajando con bases de datos, el uso de ContentResolver y Cursor le daría un patrón de observador listo para usar que es conveniente para actualizar la interfaz de usuario sin ninguna lógica adicional, una vez que haya actualizado su base de datos local con los datos remotos obtenidos. Cualquier otro código después de la operación se ejecutaría a través de una devolución de llamada pasada a Service (uso una subclase ResultReceiver para esto).

De todos modos, mi explicación es bastante vaga, definitivamente deberías ver el discurso.

 4
Author: Christopher Francisco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-08 16:49:31

Si bien la respuesta de Mark (CommonsWare) funciona para los cambios de orientación, falla si la Actividad se destruye directamente (como en el caso de una llamada telefónica).

Puede manejar los cambios de orientación Y los eventos de actividad destruidos raros usando un objeto de aplicación para hacer referencia a su AsyncTask.

Hay una excelente explicación del problema y la solución aquí:

El crédito va completamente a Ryan por averiguar esto.

 2
Author: Scott Biggs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-01 09:15:04

Después de 4 años, Google resolvió el problema simplemente llamando a setRetainInstance(true) en Activity onCreate. Conservará su instancia de actividad durante la rotación del dispositivo. También tengo una solución simple para Android más viejo.

 1
Author: Singagirl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-26 01:54:43

Debe llamar a todas las acciones de actividad usando el controlador de actividad. Así que si estás en algún hilo deberías crear un Ejecutable y publicarlo usando el Manejador de Activitie. De lo contrario, su aplicación se bloqueará a veces con la excepción fatal.

 0
Author: xpepermint,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-01 00:14:08

Esta es mi solución: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

Básicamente los pasos son:

  1. Utilizo onSaveInstanceState para guardar la tarea si todavía está procesamiento.
  2. En onCreate obtengo la tarea si fue guardada.
  3. En onPause descarto el ProgressDialog si se muestra.
  4. En onResume muestro el ProgressDialog si la tarea está todavía procesamiento.
 0
Author: Gotcha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-05 22:22:05