Mejor práctica: AsyncTask durante el cambio de orientación

151

AsyncTask Es una gran cosa para ejecutar tareas complejas en otro hilo.

Pero cuando hay un cambio de orientación u otro cambio de configuración mientras AsyncTasktodavía se está ejecutando, la corriente Activityse destruye y se reinicia. Y como la instancia de AsyncTaskestá conectada a esa actividad, falla y provoca una ventana de mensaje de "forzar cierre".

Por lo tanto, estoy buscando algún tipo de "práctica recomendada" para evitar estos errores y evitar que AsyncTask falle.

Lo que he visto hasta ahora es:

  • Deshabilite los cambios de orientación (seguro que no es la forma en que debe manejar esto).
  • Permitir que la tarea sobreviva y actualizarla con la nueva instancia de actividad a través de onRetainNonConfigurationInstance
  • Simplemente cancele la tarea cuando Activityse destruya y reinicie cuando Activityse vuelva a crear.
  • Enlace de la tarea a la clase de aplicación en lugar de la instancia de actividad.
  • Algún método utilizado en el proyecto "estantes" (a través de onRestoreInstanceState)

Algunos ejemplos de código:

Android AsyncTasks durante una rotación de pantalla, Parte I y Parte II

ShelvesActivity.java

¿Me pueden ayudar a encontrar el mejor enfoque que resuelva mejor el problema y que también sea fácil de implementar? El código en sí también es importante ya que no sé cómo resolver esto correctamente.

graznar
fuente
Hay un duplicado, consulte esta stackoverflow.com/questions/4584015/… .
TeaCupApp
Esto es del blog de Mark Murphy ... AsyncTask y ScreenRotation podrían ayudar ... enlace
Gopal
Aunque esta es una publicación antigua, pero esta OMI, es un enfoque mucho más fácil (¿y mejor?).
DroidDev
Me pregunto por qué la documentación no habla de situaciones tan triviales.
Sreekanth Karumanaghat

Respuestas:

140

No NO utilizar android:configChangespara tratar este tema. Esta es una muy mala práctica.

No NO utilizar Activity#onRetainNonConfigurationInstance()cualquiera de los dos. Esto es menos modular y no es adecuado para Fragmentaplicaciones basadas.

Puede leer mi artículo que describe cómo manejar los cambios de configuración utilizando Fragments retenidos . Resuelve muy bien el problema de retener un AsyncTaskcambio transversal. Básicamente, necesita alojar su AsyncTaskinterior Fragment, llamar setRetainInstance(true)al Fragmente informar el AsyncTaskprogreso / resultados del mismo a Activitytravés de lo retenido Fragment.

Alex Lockwood
fuente
26
Buena idea, pero no todos usan Fragmentos. Hay mucho código heredado escrito mucho antes de que los Fragmentos fueran una opción.
Scott Biggs
14
@ScottBiggs Fragments están disponibles a través de la biblioteca de soporte hasta Android 1.6. ¿Y podría dar un ejemplo de algún código heredado que todavía se está utilizando activamente y que tendría problemas para utilizar los Fragmentos de la biblioteca de soporte? Porque honestamente no creo que sea un problema.
Alex Lockwood
44
@tactoth No sentí la necesidad de abordar estos problemas en mi respuesta, ya que el 99.9% de las personas ya no usan TabActivity. Para ser honesto, no estoy seguro de por qué estamos hablando de esto ... todos están de acuerdo en que ese Fragmentes el camino a seguir. :)
Alex Lockwood
2
¿Qué sucede si se debe llamar a AsyncTask desde un Fragmento anidado?
Eduardo Naveda
3
@AlexLockwood - "todos están de acuerdo en que los Fragmentos son el camino a seguir". ¡Los desarrolladores de Squared no estarían de acuerdo!
JBeckton
36

Por lo general, resuelvo esto haciendo que mis AsyncTasks activen Intentos de transmisión en la devolución de llamada .onPostExecute (), para que no modifiquen la Actividad que los inició directamente. Las Actividades escuchan estas transmisiones con BroadcastReceivers dinámicos y actúan en consecuencia.

De esta manera, las AsyncTasks no tienen que preocuparse por la instancia específica de la Actividad que maneja su resultado. Simplemente "gritan" cuando terminan, y si una Actividad es alrededor de ese tiempo (está activa y enfocada / está en estado reanudado) que está interesada en los resultados de la tarea, entonces será manejada.

Esto implica un poco más de sobrecarga, ya que el tiempo de ejecución necesita manejar la transmisión, pero generalmente no me importa. Creo que usar LocalBroadcastManager en lugar del sistema predeterminado en todo el mundo acelera un poco las cosas.

Zsombor Erdődy-Nagy
fuente
66
si puede agregar un ejemplo a la respuesta, sería más útil
Sankar V
1
Creo que esta es la solución que ofrece menos acoplamiento entre actividades y fragmentos
Roger Garzon Nieto
77
Esto podría ser parte de una solución, pero no parece que resuelva el problema de que AsyncTask se vuelva a crear después del cambio de orientación.
miguel
44
¿Qué pasa si no tienes suerte y ninguna actividad está presente durante la transmisión? (es decir, estás a mitad de rotación)
Sam
24

Aquí hay otro ejemplo de una AsyncTask que utiliza un Fragmentpara manejar los cambios de configuración en tiempo de ejecución (como cuando el usuario gira la pantalla) con setRetainInstance(true). También se demuestra una barra de progreso determinada (actualizada regularmente).

El ejemplo se basa en parte en los documentos oficiales, Retener un objeto durante un cambio de configuración .

En este ejemplo, el trabajo que requiere un subproceso de fondo es la mera carga de una imagen de Internet en la interfaz de usuario.

Alex Lockwood parece tener razón en que cuando se trata de manejar cambios de configuración de tiempo de ejecución con AsyncTasks usando un "Fragmento Retenido" es la mejor práctica. onRetainNonConfigurationInstance()queda en desuso en Lint, en Android Studio. Los documentos oficiales nos advierten que usemos android:configChanges, desde Manejando la configuración, cambie usted mismo , ...

Manejar el cambio de configuración usted mismo puede hacer que sea mucho más difícil usar recursos alternativos, porque el sistema no los aplica automáticamente. Esta técnica debe considerarse un último recurso cuando debe evitar reinicios debido a un cambio de configuración y no se recomienda para la mayoría de las aplicaciones.

Luego está la cuestión de si uno debería usar AsyncTask para el hilo de fondo.

La referencia oficial de AsyncTask advierte ...

Las AsyncTasks deberían usarse idealmente para operaciones cortas (unos segundos como máximo). Si necesita mantener los subprocesos en ejecución durante largos períodos de tiempo, es muy recomendable que utilice las diversas API proporcionadas por el paquete java.util.concurrent, como Ejecutor, ThreadPoolExecutor y FutureTask.

Alternativamente, uno podría usar un servicio, un cargador (usando un CursorLoader o AsyncTaskLoader) o un proveedor de contenido para realizar operaciones asincrónicas.

Rompo el resto de la publicación en:

  • El procedimiento; y
  • Todo el código para el procedimiento anterior.

El procedimiento

  1. Comience con una AsyncTask básica como una clase interna de una actividad (no es necesario que sea una clase interna pero probablemente sea conveniente). En esta etapa, AsyncTask no maneja los cambios de configuración en tiempo de ejecución.

    public class ThreadsActivity extends ActionBarActivity {
    
        private ImageView mPictureImageView;
    
        private class LoadImageFromNetworkAsyncTask
                              extends AsyncTask<String, Void, Bitmap> {
    
            @Override
            protected Bitmap doInBackground(String... urls) {
                return loadImageFromNetwork(urls[0]);
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                mPictureImageView.setImageBitmap(bitmap);
            }
        }
    
        /**
         * Requires in AndroidManifext.xml
         *  <uses-permission android:name="android.permission.INTERNET" />
         */
        private Bitmap loadImageFromNetwork(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeStream((InputStream)
                                              new URL(url).getContent());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            mPictureImageView =
                (ImageView) findViewById(R.id.imageView_picture);
        }
    
        public void getPicture(View view) {
            new LoadImageFromNetworkAsyncTask()
                .execute("http://i.imgur.com/SikTbWe.jpg");
        }
    
    }
    
  2. Agregue una clase anidada RetainedFragment que extienda la clase Fragement y no tenga su propia interfaz de usuario. Agregue setRetainInstance (true) al evento onCreate de este Fragmento. Proporcione procedimientos para configurar y obtener sus datos.

    public class ThreadsActivity extends Activity {
    
        private ImageView mPictureImageView;
        private RetainedFragment mRetainedFragment = null;
        ...
    
        public static class RetainedFragment extends Fragment {
    
            private Bitmap mBitmap;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
                // The key to making data survive
                // runtime configuration changes.
                setRetainInstance(true);
            }
    
            public Bitmap getData() {
                return this.mBitmap;
            }
    
            public void setData(Bitmap bitmapToRetain) {
                this.mBitmap = bitmapToRetain;
            }
        }
    
        private class LoadImageFromNetworkAsyncTask
                        extends AsyncTask<String, Integer,Bitmap> {
        ....
    
  3. En onCreate () de la clase de actividad más externa, maneje el RetainedFragment: haga referencia a él si ya existe (en caso de que la actividad se reinicie); crear y agregarlo si no existe; Luego, si ya existía, obtenga datos del RetainedFragment y configure su IU con esos datos.

    public class ThreadsActivity extends Activity {
    
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            final String retainedFragmentTag = "RetainedFragmentTag";
    
            mPictureImageView =
                      (ImageView) findViewById(R.id.imageView_picture);
            mLoadingProgressBar =
                    (ProgressBar) findViewById(R.id.progressBar_loading);
    
            // Find the RetainedFragment on Activity restarts
            FragmentManager fm = getFragmentManager();
            // The RetainedFragment has no UI so we must
            // reference it with a tag.
            mRetainedFragment =
              (RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);
    
            // if Retained Fragment doesn't exist create and add it.
            if (mRetainedFragment == null) {
    
                // Add the fragment
                mRetainedFragment = new RetainedFragment();
                fm.beginTransaction()
                    .add(mRetainedFragment, retainedFragmentTag).commit();
    
            // The Retained Fragment exists
            } else {
    
                mPictureImageView
                    .setImageBitmap(mRetainedFragment.getData());
            }
        }
    
  4. Inicie AsyncTask desde la IU

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
    
  5. Agregue y codifique una barra de progreso determinada:

    • Agregue una barra de progreso al diseño de la interfaz de usuario;
    • Obtenga una referencia en la Actividad oncreate ();
    • Hazlo visible e invisible al comienzo y al final del proceso;
    • Defina el progreso para informar a la interfaz de usuario en onProgressUpdate.
    • Cambie el segundo parámetro genérico AsyncTask de Void a un tipo que pueda manejar actualizaciones de progreso (por ejemplo, Integer).
    • Publicar progreso en puntos regulares en doInBackground ().

Todo el código para el procedimiento anterior.

Diseño de actividad.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.mysecondapp.ThreadsActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/imageView_picture"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:background="@android:color/black" />

        <Button
            android:id="@+id/button_get_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@id/imageView_picture"
            android:onClick="getPicture"
            android:text="Get Picture" />

        <Button
            android:id="@+id/button_clear_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/button_get_picture"
            android:layout_toEndOf="@id/button_get_picture"
            android:layout_toRightOf="@id/button_get_picture"
            android:onClick="clearPicture"
            android:text="Clear Picture" />

        <ProgressBar
            android:id="@+id/progressBar_loading"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/button_get_picture"
            android:progress="0"
            android:indeterminateOnly="false"
            android:visibility="invisible" />

    </RelativeLayout>
</ScrollView>

La actividad con: clase interna AsyncTask subclasificada; clase interna RetainedFragment subclase que maneja los cambios de configuración en tiempo de ejecución (por ejemplo, cuando el usuario gira la pantalla); y una barra de progreso determinada que se actualiza a intervalos regulares. ...

public class ThreadsActivity extends Activity {

    private ImageView mPictureImageView;
    private RetainedFragment mRetainedFragment = null;
    private ProgressBar mLoadingProgressBar;

    public static class RetainedFragment extends Fragment {

        private Bitmap mBitmap;

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

            // The key to making data survive runtime configuration changes.
            setRetainInstance(true);
        }

        public Bitmap getData() {
            return this.mBitmap;
        }

        public void setData(Bitmap bitmapToRetain) {
            this.mBitmap = bitmapToRetain;
        }
    }

    private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
            Integer, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... urls) {
            // Simulate a burdensome load.
            int sleepSeconds = 4;
            for (int i = 1; i <= sleepSeconds; i++) {
                SystemClock.sleep(1000); // milliseconds
                publishProgress(i * 20); // Adjust for a scale to 100
            }

            return com.example.standardapplibrary.android.Network
                    .loadImageFromNetwork(
                    urls[0]);
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            mLoadingProgressBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            publishProgress(100);
            mRetainedFragment.setData(bitmap);
            mPictureImageView.setImageBitmap(bitmap);
            mLoadingProgressBar.setVisibility(View.INVISIBLE);
            publishProgress(0);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threads);

        final String retainedFragmentTag = "RetainedFragmentTag";

        mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
        mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);

        // Find the RetainedFragment on Activity restarts
        FragmentManager fm = getFragmentManager();
        // The RetainedFragment has no UI so we must reference it with a tag.
        mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
                retainedFragmentTag);

        // if Retained Fragment doesn't exist create and add it.
        if (mRetainedFragment == null) {

            // Add the fragment
            mRetainedFragment = new RetainedFragment();
            fm.beginTransaction().add(mRetainedFragment,
                                      retainedFragmentTag).commit();

            // The Retained Fragment exists
        } else {

            mPictureImageView.setImageBitmap(mRetainedFragment.getData());
        }
    }

    public void getPicture(View view) {
        mLoadingProgressBar.setVisibility(View.VISIBLE);
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }

    public void clearPicture(View view) {
        mRetainedFragment.setData(null);
        mPictureImageView.setImageBitmap(null);
    }
}

En este ejemplo, la función de biblioteca (mencionada anteriormente con el prefijo de paquete explícito com.example.standardapplibrary.android.Network) que hace un trabajo real ...

public static Bitmap loadImageFromNetwork(String url) {
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
                .getContent());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bitmap;
}

Agregue todos los permisos que requiera su tarea en segundo plano al AndroidManifest.xml ...

<manifest>
...
    <uses-permission android:name="android.permission.INTERNET" />

Agregue su actividad a AndroidManifest.xml ...

<manifest>
...
    <application>
        <activity
            android:name=".ThreadsActivity"
            android:label="@string/title_activity_threads"
            android:parentActivityName=".MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.mysecondapp.MainActivity" />
        </activity>
John Bentley
fuente
Excelente. Deberías escribir un blog sobre esto.
Akh
2
@AKh. ¿Quiere sugerir que mi respuesta ocupa demasiado espacio en Stackoverflow?
John Bentley
1
¡Creo que solo quiere decir que tienes una respuesta increíble y que deberías escribir un blog! =) @JohnBentley
Sandy D.
@ SandyD.yesterday Gracias por la interpretación positiva. Espero que ella o él hayan querido eso.
John Bentley
También pensé que era una respuesta increíble y también la interpreté así. ¡Las respuestas muy completas como esta son geniales!
LeonardoSibela
3

Recientemente, he encontrado una buena solución aquí . Se basa en guardar un objeto de tarea a través de RetainConfiguration. Desde mi punto de vista, la solución es muy elegante y en cuanto a mí, he comenzado a usarla. Solo necesita anidar su asinctask de la tarea base y eso es todo.

Yury
fuente
Muchas gracias por esta interesante respuesta. Es una buena solución además de las mencionadas en la pregunta relacionada.
graznar
55
Desafortunadamente, esta solución utiliza métodos obsoletos.
Damien
3

Basado en la respuesta de @Alex Lockwood y en las respuestas de @William y @quickdraw mcgraw en esta publicación: Cómo manejar los mensajes del controlador cuando la actividad / fragmento está en pausa , escribí una solución genérica.

De esta forma se maneja la rotación, y si la actividad pasa a segundo plano durante la ejecución de la tarea asíncrona, la actividad recibirá las devoluciones de llamada (onPreExecute, onProgressUpdate, onPostExecute & onCancelled) una vez que se reanude, por lo que no se lanzará IllegalStateException (consulte Cómo manejar el controlador) mensajes cuando la actividad / fragmento está en pausa ).

Sería genial tener lo mismo pero con tipos de argumentos genéricos, como AsyncTask (por ejemplo: AsyncTaskFragment <Parámetros, Progreso, Resultado>), pero no pude hacerlo rápidamente y no tengo tiempo en este momento. Si alguien quiere mejorar, ¡no dude en hacerlo!

El código:

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class AsyncTaskFragment extends Fragment {

    /* ------------------------------------------------------------------------------------------ */
    // region Classes & Interfaces

    public static abstract class Task extends AsyncTask<Object, Object, Object> {

        private AsyncTaskFragment _fragment;

        private void setFragment(AsyncTaskFragment fragment) {

            _fragment = fragment;
        }

        @Override
        protected final void onPreExecute() {

            // Save the state :
            _fragment.setRunning(true);

            // Send a message :
            sendMessage(ON_PRE_EXECUTE_MESSAGE, null);
        }

        @Override
        protected final void onPostExecute(Object result) {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_POST_EXECUTE_MESSAGE, result);
        }

        @Override
        protected final void onProgressUpdate(Object... values) {

            // Send a message :
            sendMessage(ON_PROGRESS_UPDATE_MESSAGE, values);
        }

        @Override
        protected final void onCancelled() {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_CANCELLED_MESSAGE, null);
        }

        private void sendMessage(int what, Object obj) {

            Message message = new Message();
            message.what = what;
            message.obj = obj;

            Bundle data = new Bundle(1);
            data.putString(EXTRA_FRAGMENT_TAG, _fragment.getTag());
            message.setData(data);

            _fragment.handler.sendMessage(message);
        }
    }

    public interface AsyncTaskFragmentListener {

        void onPreExecute(String fragmentTag);
        void onProgressUpdate(String fragmentTag, Object... progress);
        void onCancelled(String fragmentTag);
        void onPostExecute(String fragmentTag, Object result);
    }

    private static class AsyncTaskFragmentPauseHandler extends PauseHandler {

        @Override
        final protected void processMessage(Activity activity, Message message) {

            switch (message.what) {

                case ON_PRE_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPreExecute(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
                case ON_POST_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPostExecute(message.getData().getString(EXTRA_FRAGMENT_TAG), message.obj); break; }
                case ON_PROGRESS_UPDATE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onProgressUpdate(message.getData().getString(EXTRA_FRAGMENT_TAG), ((Object[])message.obj)); break; }
                case ON_CANCELLED_MESSAGE : { ((AsyncTaskFragmentListener)activity).onCancelled(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
            }
        }
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Attributes

    private Task _task;
    private AsyncTaskFragmentListener _listener;
    private boolean _running = false;

    private static final String EXTRA_FRAGMENT_TAG = "EXTRA_FRAGMENT_TAG";
    private static final int ON_PRE_EXECUTE_MESSAGE = 0;
    private static final int ON_POST_EXECUTE_MESSAGE = 1;
    private static final int ON_PROGRESS_UPDATE_MESSAGE = 2;
    private static final int ON_CANCELLED_MESSAGE = 3;

    private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Getters

    public AsyncTaskFragmentListener getListener() { return _listener; }
    public boolean isRunning() { return _running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Setters

    public void setTask(Task task) {

        _task = task;
        _task.setFragment(this);
    }

    public void setListener(AsyncTaskFragmentListener listener) { _listener = listener; }
    private void setRunning(boolean running) { _running = running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Fragment lifecycle

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onResume() {

        super.onResume();
        handler.resume(getActivity());
    }

    @Override
    public void onPause() {

        super.onPause();
        handler.pause();
    }

    @Override
    public void onAttach(Activity activity) {

        super.onAttach(activity);
        _listener = (AsyncTaskFragmentListener) activity;
    }

    @Override
    public void onDetach() {

        super.onDetach();
        _listener = null;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Utils

    public void execute(Object... params) {

        _task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }

    public void cancel(boolean mayInterruptIfRunning) {

        _task.cancel(mayInterruptIfRunning);
    }

    public static AsyncTaskFragment getRetainedOrNewFragment(AppCompatActivity activity, String fragmentTag) {

        FragmentManager fm = activity.getSupportFragmentManager();
        AsyncTaskFragment fragment = (AsyncTaskFragment) fm.findFragmentByTag(fragmentTag);

        if (fragment == null) {

            fragment = new AsyncTaskFragment();
            fragment.setListener( (AsyncTaskFragmentListener) activity);
            fm.beginTransaction().add(fragment, fragmentTag).commit();
        }

        return fragment;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */
}

Necesitarás el PauseHandler:

import android.app.Activity;
import android.os.Handler;
import android.os.Message;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Message Handler class that supports buffering up of messages when the activity is paused i.e. in the background.
 *
 * /programming/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
 */
public abstract class PauseHandler extends Handler {

    /**
     * Message Queue Buffer
     */
    private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());

    /**
     * Flag indicating the pause state
     */
    private Activity activity;

    /**
     * Resume the handler.
     */
    public final synchronized void resume(Activity activity) {
        this.activity = activity;

        while (messageQueueBuffer.size() > 0) {
            final Message msg = messageQueueBuffer.get(0);
            messageQueueBuffer.remove(0);
            sendMessage(msg);
        }
    }

    /**
     * Pause the handler.
     */
    public final synchronized void pause() {
        activity = null;
    }

    /**
     * Store the message if we have been paused, otherwise handle it now.
     *
     * @param msg   Message to handle.
     */
    @Override
    public final synchronized void handleMessage(Message msg) {
        if (activity == null) {
            final Message msgCopy = new Message();
            msgCopy.copyFrom(msg);
            messageQueueBuffer.add(msgCopy);
        } else {
            processMessage(activity, msg);
        }
    }

    /**
     * Notification message to be processed. This will either be directly from
     * handleMessage or played back from a saved message when the activity was
     * paused.
     *
     * @param activity  Activity owning this Handler that isn't currently paused.
     * @param message   Message to be handled
     */
    protected abstract void processMessage(Activity activity, Message message);
}

Uso de la muestra:

public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {

    private final static String ASYNC_TASK_FRAGMENT_A = "ASYNC_TASK_FRAGMENT_A";
    private final static String ASYNC_TASK_FRAGMENT_B = "ASYNC_TASK_FRAGMENT_B";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        Button testButton = (Button) findViewById(R.id.test_button);
        final AsyncTaskFragment fragment = AsyncTaskFragment.getRetainedOrNewFragment(TestActivity.this, ASYNC_TASK_FRAGMENT_A);

        testButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(!fragment.isRunning()) {

                    fragment.setTask(new Task() {

                        @Override
                        protected Object doInBackground(Object... objects) {

                            // Do your async stuff

                            return null;
                        }
                    });

                    fragment.execute();
                }
            }
        });
    }

    @Override
    public void onPreExecute(String fragmentTag) {}

    @Override
    public void onProgressUpdate(String fragmentTag, Float percent) {}

    @Override
    public void onCancelled(String fragmentTag) {}

    @Override
    public void onPostExecute(String fragmentTag, Object result) {

        switch (fragmentTag) {

            case ASYNC_TASK_FRAGMENT_A: {

                // Handle ASYNC_TASK_FRAGMENT_A
                break;
            }
            case ASYNC_TASK_FRAGMENT_B: {

                // Handle ASYNC_TASK_FRAGMENT_B
                break;
            }
        }
    }
}
Tim Autin
fuente
3

Puedes usar cargadores para esto. Compruebe Doc aquí

PPD
fuente
2
Los cargadores ahora están en desuso a partir de la API 28 de Android (como le indicará el enlace).
Sumit
Los cargadores no están en desuso, solo se cambia la forma en que los
llamas
2

Para aquellos que quieran esquivar Fragmentos, pueden retener la AsyncTask ejecutándose en los cambios de orientación usando onRetainCustomNonConfigurationInstance () y algunos cables.

(Tenga en cuenta que este método es la alternativa al obsoleto onRetainNonConfigurationInstance () ).

Sin embargo, parece que esta solución no se menciona con frecuencia. Escribí un ejemplo simple para ilustrar.

¡Salud!

public class MainActivity extends AppCompatActivity {

private TextView result;
private Button run;
private AsyncTaskHolder asyncTaskHolder;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    result = (TextView) findViewById(R.id.textView_result);
    run = (Button) findViewById(R.id.button_run);
    asyncTaskHolder = getAsyncTaskHolder();
    run.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            asyncTaskHolder.execute();
        }
    });
}

private AsyncTaskHolder getAsyncTaskHolder() {
    if (this.asyncTaskHolder != null) {
        return asyncTaskHolder;
    }
    //Not deprecated. Get the same instance back.
    Object instance = getLastCustomNonConfigurationInstance();

    if (instance == null) {
        instance = new AsyncTaskHolder();
    }
    if (!(instance instanceof ActivityDependant)) {
        Log.e("", instance.getClass().getName() + " must implement ActivityDependant");
    }
    return (AsyncTaskHolder) instance;
}

@Override
//Not deprecated. Save the object containing the running task.
public Object onRetainCustomNonConfigurationInstance() {
    return asyncTaskHolder;
}

@Override
protected void onStart() {
    super.onStart();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.attach(this);
    }
}

@Override
protected void onStop() {
    super.onStop();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.detach();
    }
}

void updateUI(String value) {
    this.result.setText(value);
}

interface ActivityDependant {

    void attach(Activity activity);

    void detach();
}

class AsyncTaskHolder implements ActivityDependant {

    private Activity parentActivity;
    private boolean isRunning;
    private boolean isUpdateOnAttach;

    @Override
    public synchronized void attach(Activity activity) {
        this.parentActivity = activity;
        if (isUpdateOnAttach) {
            ((MainActivity) parentActivity).updateUI("done");
            isUpdateOnAttach = false;
        }
    }

    @Override
    public synchronized void detach() {
        this.parentActivity = null;
    }

    public synchronized void execute() {
        if (isRunning) {
            Toast.makeText(parentActivity, "Already running", Toast.LENGTH_SHORT).show();
            return;
        }
        isRunning = true;
        new AsyncTask<Void, Integer, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                for (int i = 0; i < 100; i += 10) {
                    try {
                        Thread.sleep(500);
                        publishProgress(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI(String.valueOf(values[0]));
                }
            }

            @Override
            protected synchronized void onPostExecute(Void aVoid) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI("done");
                } else {
                    isUpdateOnAttach = true;
                }
                isRunning = false;
            }
        }.execute();
    }
}
cgrenzel
fuente
0

He implementado una biblioteca que puede resolver problemas con la pausa de actividad y recreación mientras se ejecuta su tarea.

Debe implementar AsmykPleaseWaitTasky AsmykBasicPleaseWaitActivity. Su actividad y tarea en segundo plano funcionarán bien, incluso si usted rota la pantalla y cambia entre aplicaciones

mabramyan
fuente
-9

SOLUCIÓN RÁPIDA (no recomendado)

Para evitar que una actividad se destruya y se cree es declarar su actividad en el archivo de manifiesto: android: configChanges = "oriente | keyboardHidden | screenSize

  <activity
        android:name=".ui.activity.MyActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:label="@string/app_name">

Como se menciona en los documentos

La orientación de la pantalla ha cambiado: el usuario ha girado el dispositivo.

Nota: Si su aplicación se dirige al nivel 13 o superior de la API (según lo declarado por los atributos minSdkVersion y targetSdkVersion), también debe declarar la configuración "screenSize", porque también cambia cuando un dispositivo cambia entre las orientaciones vertical y horizontal.

Choletski
fuente
1
Esto es mejor evitarlo. developer.android.com/guide/topics/resources/… "Nota: manejar el cambio de configuración usted mismo puede hacer que sea mucho más difícil usar recursos alternativos, porque el sistema no los aplica automáticamente. Esta técnica debe considerarse una última recurrir cuando debe evitar reinicios debido a un cambio de configuración y no se recomienda para la mayoría de las aplicaciones ".
David