Meilleure pratique: AsyncTask pendant le changement d'orientation

151

AsyncTask est une bonne chose pour exécuter des tâches complexes dans un autre thread.

Mais lorsqu'il y a un changement d'orientation ou un autre changement de configuration alors que le AsyncTaskest toujours en cours d'exécution, le courant Activityest détruit et redémarré. Et comme l'instance de AsyncTaskest connectée à cette activité, elle échoue et provoque une fenêtre de message "Forcer la fermeture".

Donc, je recherche une sorte de "meilleure pratique" pour éviter ces erreurs et empêcher AsyncTask d'échouer.

Ce que j'ai vu jusqu'à présent, c'est:

  • Désactivez les changements d'orientation (ce n'est certainement pas la façon dont vous devez gérer cela.)
  • Laisser la tâche survivre et la mettre à jour avec la nouvelle instance d'activité via onRetainNonConfigurationInstance
  • Il suffit d'annuler la tâche lorsque le Activityest détruit et de le redémarrer lorsque le Activityest de nouveau créé.
  • Lier la tâche à la classe d'application au lieu de l'instance d'activité.
  • Une méthode utilisée dans le projet "tablettes" (via onRestoreInstanceState)

Quelques exemples de code:

Android AsyncTasks lors d'une rotation d'écran, partie I et partie II

ShelvesActivity.java

Pouvez-vous m'aider à trouver la meilleure approche qui résout le problème au mieux et qui est également facile à mettre en œuvre? Le code lui-même est également important car je ne sais pas comment résoudre ce problème correctement.

croasser
la source
Il y a un doublon, vérifiez ce stackoverflow.com/questions/4584015/… .
TeaCupApp
Ceci est du blog de Mark Murphy ... AsyncTask et ScreenRotation pourraient aider ... lien
Gopal
Bien que ce soit un ancien post, mais cette OMI, est une approche beaucoup plus facile (et meilleure?).
DroidDev
Je me demande simplement pourquoi la documentation ne parle pas de situations aussi triviales.
Sreekanth Karumanaghat

Réponses:

140

Ne pas utiliser android:configChangespour résoudre ce problème. C'est une très mauvaise pratique.

Ne pas utiliser Activity#onRetainNonConfigurationInstance()non plus . Ceci est moins modulaire et pas bien adapté aux Fragmentapplications basées.

Vous pouvez lire mon article décrivant comment gérer les modifications de configuration à l'aide de Fragments. Cela résout bien le problème de la conservation d' AsyncTaskun changement de rotation. Vous devez essentiellement héberger votre AsyncTaskintérieur Fragment, appeler setRetainInstance(true)le Fragment, et rapporter la AsyncTaskprogression / les résultats de celui-ci Activityvia le fichier conservé Fragment.

Alex Lockwood
la source
26
Bonne idée, mais tout le monde n'utilise pas des fragments. Il y a beaucoup de code hérité écrit bien avant que les fragments ne soient une option.
Scott Biggs
14
Les fragments @ScottBiggs sont disponibles via la bibliothèque de support jusqu'à Android 1.6. Et pourriez-vous donner un exemple de code hérité qui est toujours activement utilisé et qui aurait du mal à utiliser la bibliothèque de support Fragments? Parce que honnêtement, je ne pense pas que ce soit un problème.
Alex Lockwood
4
@tactoth Je n'ai pas ressenti le besoin d'aborder ces problèmes dans ma réponse, car 99,9% des gens ne l'utilisent plus TabActivity. Pour être honnête, je ne sais pas pourquoi nous en parlons même ... tout le monde convient que Fragmentc'est la voie à suivre. :)
Alex Lockwood
2
Que faire si la AsyncTask doit être appelée à partir d'un fragment imbriqué?
Eduardo Naveda
3
@AlexLockwood - "tout le monde convient que les fragments sont la voie à suivre.". Les développeurs de Squared ne seraient pas d'accord!
JBeckton
36

Je résous généralement cela en demandant à mes AsyncTasks de déclencher des intentions de diffusion dans le rappel .onPostExecute (), afin qu'ils ne modifient pas l'activité qui les a démarrés directement. Les activités écoutent ces émissions avec des récepteurs de diffusion dynamiques et agissent en conséquence.

De cette façon, les AsyncTasks n'ont pas à se soucier de l'instance Activity spécifique qui gère leur résultat. Ils "crient" juste quand ils ont terminé, et si une activité est à peu près à ce moment (est active et focalisée / est dans son état de reprise) qui s'intéresse aux résultats de la tâche, alors elle sera gérée.

Cela implique un peu plus de temps système, car le runtime doit gérer la diffusion, mais cela ne me dérange généralement pas. Je pense que l'utilisation de LocalBroadcastManager au lieu du système par défaut accélère un peu les choses.

Zsombor Erdődy-Nagy
la source
6
si vous pouvez ajouter un exemple à la réponse, ce serait plus utile
Sankar V
1
Je pense que c'est la solution qui offre moins de couplage entre activités et fragments
Roger Garzon Nieto
7
Cela peut faire partie d'une solution, mais il ne semble pas que cela résoudrait le problème de la recréation d'AsyncTask après le changement d'orientation.
miguel
4
Que faire si vous êtes malchanceux et qu'aucune activité n'est présente pendant la diffusion? (c'est-à-dire que vous êtes à mi-rotation)
Sam
24

Voici un autre exemple d'AsyncTask qui utilise un Fragmentpour gérer les changements de configuration d'exécution (comme lorsque l'utilisateur fait pivoter l'écran) avec setRetainInstance(true). Une barre de progression déterminée (régulièrement mise à jour) est également présentée.

L'exemple est en partie basé sur les documents officiels, Conservation d'un objet lors d'un changement de configuration .

Dans cet exemple, le travail nécessitant un fil d'arrière-plan est le simple chargement d'une image depuis Internet dans l'interface utilisateur.

Alex Lockwood semble avoir raison de dire que lorsqu'il s'agit de gérer les changements de configuration d'exécution avec AsyncTasks, l'utilisation d'un «fragment conservé» est la meilleure pratique. onRetainNonConfigurationInstance()devient obsolète dans Lint, dans Android Studio. La documentation officielle nous avertit de ne pas utiliser android:configChanges, de Gérer vous-même le changement de configuration , ...

La gestion du changement de configuration vous-même peut rendre l'utilisation de ressources alternatives beaucoup plus difficile, car le système ne les applique pas automatiquement pour vous. Cette technique doit être considérée comme un dernier recours lorsque vous devez éviter les redémarrages dus à un changement de configuration et n'est pas recommandée pour la plupart des applications.

Ensuite, il y a la question de savoir si l'on doit utiliser une AsyncTask pour le thread d'arrière-plan.

La référence officielle d'AsyncTask prévient ...

AsyncTasks devrait idéalement être utilisé pour des opérations courtes (quelques secondes au maximum). Executor, ThreadPoolExecutor et FutureTask.

Vous pouvez également utiliser un service, un chargeur (en utilisant un CursorLoader ou AsyncTaskLoader) ou un fournisseur de contenu pour effectuer des opérations asynchrones.

Je divise le reste du message en:

  • La procédure; et
  • Tout le code pour la procédure ci-dessus.

La procédure

  1. Commencez avec une AsyncTask de base comme classe interne d'une activité (il n'est pas nécessaire que ce soit une classe interne, mais ce sera probablement pratique). À ce stade, AsyncTask ne gère pas les modifications de configuration d'exécution.

    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. Ajoutez une classe imbriquée RetainedFragment qui étend la classe Fragement et n'a pas sa propre interface utilisateur. Ajoutez setRetainInstance (true) à l'événement onCreate de ce fragment. Fournissez des procédures pour définir et obtenir vos données.

    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. Dans la classe d'activité la plus externe, onCreate () gère le RetainedFragment: référencez-le s'il existe déjà (au cas où l'activité redémarre); créez-le et ajoutez-le s'il n'existe pas; Ensuite, s'il existait déjà, récupérez les données de RetainedFragment et définissez votre interface utilisateur avec ces données.

    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. Lancer l'AsyncTask depuis l'interface utilisateur

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. Ajoutez et codez une barre de progression déterminée:

    • Ajoutez une barre de progression à la disposition de l'interface utilisateur;
    • Obtenez une référence à celui-ci dans l'activité oncreate ();
    • Rendez-le visible et invisible au début et à la fin du processus;
    • Définissez la progression à signaler à l'interface utilisateur dans onProgressUpdate.
    • Modifiez le paramètre AsyncTask 2nd Generic de Void en un type qui peut gérer les mises à jour de progression (par exemple Integer).
    • publishProgress à des points réguliers dans doInBackground ().

Tout le code pour la procédure ci-dessus

Disposition des activités.

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

L'activité avec: classe interne AsyncTask sous-classée; classe interne sous-classée RetainedFragment qui gère les changements de configuration d'exécution (par exemple, lorsque l'utilisateur fait pivoter l'écran); et une mise à jour de la barre de progression déterminée à intervalles réguliers. ...

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);
    }
}

Dans cet exemple, la fonction de bibliothèque (référencée ci-dessus avec le préfixe de package explicite com.example.standardapplibrary.android.Network) qui fonctionne vraiment ...

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;
}

Ajoutez toutes les autorisations requises par votre tâche d'arrière-plan à AndroidManifest.xml ...

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

Ajoutez votre activité à 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
la source
Génial. Vous devriez écrire un blog à ce sujet.
Akh
2
@AKh. Voulez-vous suggérer que ma réponse prend trop de place sur Stackoverflow?
John Bentley
1
Je pense qu'il veut juste dire que vous avez une réponse géniale et que vous devriez écrire un blog! =) @JohnBentley
Sandy D.
@ SandyD.yesterday Merci pour l'interprétation positive. J'espère qu'elle ou il l'a voulu.
John Bentley
J'ai aussi pensé que c'était une réponse géniale et je l'ai interprétée comme ça aussi. Des réponses très complètes comme celle-ci sont super !!
LeonardoSibela
3

Récemment, j'ai trouvé une bonne solution ici . Il est basé sur l'enregistrement d'un objet de tâche via RetainConfiguration. De mon point de vue, la solution est très élégante et quant à moi j'ai commencé à l'utiliser. Vous avez juste besoin d'imbriquer votre asynctask de la basetask et c'est tout.

Yury
la source
Merci beaucoup pour cette réponse intéressante. C'est une bonne solution en plus de celles mentionnées dans la question connexe.
caw
5
Malheureusement, cette solution utilise des méthodes obsolètes.
Damien le
3

Sur la base de la réponse @Alex Lockwood et des réponses @William & @quickdraw mcgraw sur cet article: Comment gérer les messages du gestionnaire lorsque l'activité / le fragment est suspendu , j'ai écrit une solution générique.

De cette façon, la rotation est gérée, et si l'activité passe en arrière-plan pendant l'exécution de la tâche asynchrone, l'activité recevra les rappels (onPreExecute, onProgressUpdate, onPostExecute et onCancelled) une fois reprise, donc aucune IllegalStateException ne sera levée (voir Comment gérer le gestionnaire messages lorsque l'activité / le fragment est en pause ).

Ce serait super d'avoir le même mais avec des types d'arguments génériques, comme une AsyncTask (par exemple: AsyncTaskFragment <Params, Progress, Result>), mais je n'ai pas réussi à le faire rapidement et n'ai pas le temps pour le moment. Si quelqu'un veut faire l'amélioration, n'hésitez pas!

Le code:

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
    /* ------------------------------------------------------------------------------------------ */
}

Vous aurez besoin du 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);
}

Exemple d'utilisation:

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
la source
3

Vous pouvez utiliser des chargeurs pour cela. Vérifier Doc ici

PPD
la source
2
Les chargeurs sont désormais obsolètes à partir de l'API Android 28 (comme le lien vous le dira).
Sumit
Les chargeurs ne sont pas obsolètes, ce n'est que la façon dont vous les appelez qui a changé
EdgeDev
2

Pour ceux qui souhaitent esquiver les fragments, vous pouvez conserver l'AsyncTask en cours d'exécution sur les changements d'orientation à l'aide de onRetainCustomNonConfigurationInstance () et du câblage.

(Notez que cette méthode est l'alternative à la méthode obsolète onRetainNonConfigurationInstance () ).

On dirait que cette solution n'est pas fréquemment mentionnée. J'ai écrit un exemple simple en cours d'exécution pour illustrer.

À votre santé!

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
la source
0

J'ai implémenté une bibliothèque qui peut résoudre les problèmes de pause d'activité et de récréation pendant l'exécution de votre tâche.

Vous devez implémenter AsmykPleaseWaitTasket AsmykBasicPleaseWaitActivity. Votre activité et votre tâche en arrière-plan fonctionneront bien même si vous faites pivoter l'écran et basculez entre les applications

mabramyan
la source
-9

SOLUTION DE CONTOURNEMENT RAPIDE (non recommandé)

Pour éviter qu'une activité se détruise et se crée elle-même, il faut déclarer votre activité dans le fichier manifeste: android: configChanges = "orientation | keyboardHidden | screenSize

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

Comme il est mentionné dans la documentation

L'orientation de l'écran a changé - l'utilisateur a fait pivoter l'appareil.

Remarque: Si votre application cible le niveau d'API 13 ou supérieur (comme déclaré par les attributs minSdkVersion et targetSdkVersion), vous devez également déclarer la configuration «screenSize», car elle change également lorsqu'un appareil bascule entre les orientations portrait et paysage.

Choletski
la source
1
Il vaut mieux éviter cela. developer.android.com/guide/topics/resources/… "Remarque: la gestion du changement de configuration vous-même peut rendre l'utilisation de ressources alternatives beaucoup plus difficile, car le système ne les applique pas automatiquement pour vous. Cette technique doit être considérée comme une dernière recours lorsque vous devez éviter les redémarrages en raison d'un changement de configuration et n'est pas recommandé pour la plupart des applications. "
David