Meilleure pratique: AsyncTask pendant le changement d'orientation

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

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

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

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

  • désactive les changements d'orientation.(Pour sûr pas la façon dont vous devriez 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 Activity est détruit et de la redémarrer lorsque le Activity est créé à nouveau.
  • lier la tâche à la classe d'application au lieu de l'instance d'activité.
  • une méthode utilisée dans le projet" étagères " (via onRestoreInstanceState)

Quelques exemples de code:

AsyncTasks Android pendant une rotation d'écran, Partie I et Partie II

ShelvesActivity.java

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

141
demandé sur Willi Mentzel 2011-08-20 04:18:27

9 réponses

Do PAS utiliser android:configChanges pour répondre à cette question. C'est une très mauvaise pratique.

Faites Pas utilisez Activity#onRetainNonConfigurationInstance() non plus. Ceci est moins modulaire et pas bien adapté pour les applications basées sur Fragment.

Vous pouvez lire mon article décrire comment gérer les changements de configuration en utilisant les Fragmentconservés. cela résout bien le problème de conserver un AsyncTask à travers un changement de rotation. Vous devez essentiellement héberger votre AsyncTask dans un Fragment, appeler setRetainInstance(true) sur le Fragment, et rapportez la progression/les résultats du AsyncTask à son Activity à travers le Fragment conservé.

126
répondu Alex Lockwood 2016-04-21 21:18:27

Je résous Habituellement cela en ayant mes intentions de diffusion AsyncTasks dans le .onPostExecute () callback, de sorte qu'ils ne modifient pas l'activité qui les a démarrés directement. Les activités écoutent ces émissions avec des émetteurs de radiodiffusion dynamiques et agissent en conséquence.

De cette façon, les AsyncTasks n'ont pas à se soucier de l'instance D'activité spécifique qui gère leur résultat. Ils "crient" juste quand ils ont terminé, et si une activité est à ce moment-là (est active et concentrée / est en c'est l'état repris) qui est intéressé par les résultats de la tâche, alors il sera géré.

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

30
répondu Zsombor Erdődy-Nagy 2012-11-05 09:01:31

Voici un autre exemple D'AsyncTask qui utilise un Fragment pour gérer les modifications 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 démontrée.

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

Dans cet exemple, le travail nécessitant un thread d'arrière-plan est le simple chargement d'une image depuis internet dans le INTERFACE.

Alex Lockwood semble avoir raison de dire que lorsqu'il s'agit de gérer les changements de configuration d'exécution avec AsyncTasks en utilisant un "Fragment conservé" est la meilleure pratique. onRetainNonConfigurationInstance() est obsolète dans Lint, dans Android Studio. Les documents officiels nous avertissent en utilisant android:configChanges, à partir de gérer vous-même la Configuration ,...

Gérer vous-même le changement de configuration peut rendre beaucoup plus difficile l'utilisation de ressources alternatives, car le système ne le fait pas appliquer automatiquement pour vous. Cette technique doit être considérée comme un dernier recours lorsque vous devez éviter les redémarrages en raison d'un changement de configuration 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 pour AsyncTask avertit ...

Les AsyncTasks devraient idéalement être utilisés pour des opérations courtes (quelques secondes au maximum. Si vous avez besoin de pour maintenir les threads en cours d'exécution pendant de longues périodes, il est fortement recommandé d'utiliser les différentes API fournies par java.util.pacakge simultané tels que Executor, ThreadPoolExecutor et FutureTask.

Vous pouvez également utiliser un service, un chargeur (à l'aide D'un CursorLoader ou D'un asynctaskloader) ou un fournisseur de contenu pour effectuer des opérations asynchrones.

Je casse le reste du message en:

  • La Procédure; et
  • Tout le code pour ce qui précède procédure.

La Procédure

  1. Commencez par une AsyncTask de base en tant que classe interne d'une activité (elle n'a pas besoin d'être une classe interne mais elle sera probablement pratique). A 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. Fournir 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 et ajoutez-le s'il n'existe pas; puis, s'il existait déjà, récupérez les données du 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. Initialise L'AsyncTask à partir de L'interface utilisateur

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

    • ajoute une barre de progression à la disposition de L'interface utilisateur;
    • obtenir une référence dans L'activité oncreate ();
    • le rendre 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 générique AsyncTask 2nd De Void à un type capable de gérer les mises à jour de progression (par exemple Integer).
    • publishProgress aux points réguliers de doInBackground ().

Tous le code pour la procédure ci-dessus

Disposition De L'Activité.

<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 RetainedFragment sous-classée qui gère les changements de configuration d'exécution (par exemple lorsque l'utilisateur fait pivoter l'écran); et une barre de progression déterminée mise à jour à 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 paquet explicite com.exemple.standardapplibrary.Android.Réseau) qui fait réel travail ...

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 que votre tâche d'arrière-plan nécessite à L'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>
24
répondu John Bentley 2014-08-11 11:25:50

Récemment, j'ai trouvé une bonne solution ici. Il est basé sur l'enregistrement d'un objet task via On 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.

3
répondu Yury 2012-07-05 08:12:21

Basé sur la réponse de @ Alex Lockwood et sur les réponses de @William & @ Quickdraw mcgraw sur ce post: Comment gérer les messages du gestionnaire lorsque l'activité / fragment est en pause , 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 & onCancelled) une fois reprise, donc aucune exception IllegalStateException ne sera levée (voir Comment Gérer les messages du gestionnaire lorsque l'activité / fragment est en pause).

Ce serait génial d'avoir la même chose mais avec des types d'arguments génériques, comme un AsyncTask (par exemple: Asynctaskfragment), 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.
 *
 * https://stackoverflow.com/questions/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;
            }
        }
    }
}
3
répondu Tim Autin 2017-05-23 10:31:13

Pour ceux qui veulent esquiver les Fragments, vous pouvez conserver la AsyncTask en cours d'exécution sur les changements d'orientation en utilisant onRetainCustomNonConfigurationInstance() et du câblage.

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

Semble que cette solution n'est pas fréquemment mentionnée cependant. J'ai écrit un exemple simple pour illustrer.

Cheers!

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();
    }
}
2
répondu cgrenzel 2017-05-24 22:52:05

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

2
répondu PPD 2017-09-13 19:30:44

J'ai implémenté library {[4] } qui peut résoudre les problèmes de pause d'activité et de loisirs pendant l'exécution de votre tâche.

Vous devez implémenter AsmykPleaseWaitTask et AsmykBasicPleaseWaitActivity. Votre tâche d'activité et d'arrière-plan fonctionnera bien même si vous faites pivoter l'écran et basculer entre les applications

1
répondu mabramyan 2017-05-31 16:55:06

Solution de contournement rapide (non recommandé)

Pour éviter une activité à détruire et à créer elle-même, il faut déclarer votre activité dans un 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 docs

L'orientation de L'écran a changé - l'utilisateur a tourné à l'appareil.

Remarque: Si votre application cible le niveau D'API 13 ou supérieur (tel que déclaré par le minSdkVersion et targetSdkVersion attributs), alors vous devriez déclarez également la configuration "screenSize", car elle change également lorsqu'un appareil bascule entre les orientations portrait et paysage.

-9
répondu Choletski 2017-04-19 12:13:39