151 votes

Meilleure pratique: AsyncTask lors du 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 d'un autre changement de configuration, tandis que les AsyncTask est toujours en cours d'exécution, l'activité en cours est détruit et redémarré. Et comme l'instance de AsyncTask est connecté à cette activité, il échoue et provoque un "force close" de la fenêtre de message.

Donc, je suis à la recherche d'une sorte de "best-practice" pour éviter ces erreurs et prévenir les AsyncTask de défaut.

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

  • Désactiver les changements d'orientation. / Pour sûr, pas la façon dont vous devez gérer cela.
  • Laisser la tâche de survivre et de se mettre à jour avec la nouvelle instance d'activité via onRetainNonConfigurationInstance
  • Seulement l'annulation de la tâche lorsque l'activité est détruite et le redémarre lorsque l'activité est créé de nouveau.
  • La liaison de la tâche à la classe d'application au lieu de l'instance d'activité.
  • Une méthode utilisée dans les "tablettes" de projet (via onRestoreInstanceState)

Quelques exemples de code:

Android AsyncTasks au cours d'une rotation de l'écran, la Partie I et la Partie II

ShelvesActivity.java

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

135voto

Alex Lockwood Points 31578

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

Ne PAS utiliser Activity#onRetainNonConfigurationInstance() . C'est moins modulaire et ne convient pas pour Fragment-en fonction des applications.

Vous pouvez lire mon article décrivant comment gérer les modifications de configuration à l'aide de retenues Fragments. Il résout le problème de la conservation d'un AsyncTask sur une modification de rotation bien. En gros, vous avez besoin d'héberger votre AsyncTask à l'intérieur d'un Fragment, appelez - setRetainInstance(true) sur le Fragment, et le rapport de la AsyncTasks'progrès/résultats c'est Activity grâce à l'conservé Fragment.

34voto

J'ai l'habitude de résoudre ce problème en ayant mon AsyncTasks feu de diffusion Intentions de l' .onPostExecute() de rappel, afin de ne pas modifier l'Activité qui a commencé directement. Les Activités d'écouter ces émissions dynamique BroadcastReceivers et d'agir en conséquence.

De cette façon, le AsyncTasks n'avez pas de soins sur l'Activité spécifique de l'instance qui gère leur résultat. Ils ont juste "crier" quand ils sont finis, et si une Activité est autour de ce temps (est active et ciblée / est dans son état de "reprise") qui s'est intéressé aux résultats de la tâche, elle sera traitée.

Cela implique un peu plus de surcharge, depuis le runtime doit gérer l'émission, mais j'ai l'habitude de ne pas l'esprit. Je pense à l'aide de la LocalBroadcastManager au lieu de la valeur par défaut à l'échelle du système une vitesse un peu les choses.

24voto

John Bentley Points 149

Voici un autre exemple d'une AsyncTask qui utilise un Fragment pour gérer la configuration de l'exécution des changements (comme lorsque l'utilisateur fait pivoter l'écran) avec setRetainInstance(true). Une période déterminée (régulièrement mis à jour) de la barre de progression est également démontrée.

L'exemple est basé en partie sur les docs officielles, la conservation d'un Objet Lors d'un Changement de Configuration.

Dans cet exemple, le travail exigeant un thread d'arrière-plan est le simple chargement d'une image à partir de l'internet dans l'INTERFACE utilisateur.

Alex Lockwood semble être droit que quand il s'agit de la manipulation de la configuration de l'exécution des changements avec AsyncTasks à l'aide d'un "Fragment Conservé" est la meilleure pratique. onRetainNonConfigurationInstance() devient obsolète en Charpie, dans Android Studio. L'officiel docs de nous en avertir en utilisant de android:configChanges, à partir de la Manipulation de la Configuration de Modifier Vous-même, ...

La manipulation de la modification de la configuration-vous pouvez le rendre beaucoup plus difficile l'utilisation de ressources alternatives, parce que le système ne s'applique pas 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.

Il y a ensuite la question de savoir si l'on doit utiliser un AsyncTask pour le thread d'arrière-plan.

La référence officielle pour AsyncTask avertit ...

AsyncTasks devrait idéalement être utilisé pour les opérations de courte durée (quelques secondes tout au plus.) Si vous avez besoin de garder les threads en cours d'exécution pour de longues périodes de temps, il est fortement recommandé que vous utilisez les différentes Api fournies par java.util.simultanées pacakge comme Exécuteur testamentaire, ThreadPoolExecutor et FutureTask.

Alternativement, on peut utiliser un service, chargeur (à l'aide d'un CursorLoader ou AsyncTaskLoader), ou de fournisseur de contenu pour exécuter des opérations asynchrones.

Je me casse le reste de la poste à:

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

La Procédure

  1. Commencez avec une base AsyncTask intérieur de la classe d'une activité (il n'a pas besoin d'être un à l'intérieur de la classe, mais il sera probablement facile d'être). À ce stade, l'AsyncTask ne gère pas la configuration de l'exécution des changements.

    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. Ajouter une classe imbriquée RetainedFragment qui s'étend le Fragement de la classe et ne dispose pas de sa propre INTERFACE utilisateur. Ajouter setRetainInstance(true) pour l'événement onCreate de ce Fragment. Fournir 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 le ultrapériphériques de l'Activité de la classe onCreate() de la poignée de la RetainedFragment: Référence s'il existe déjà (dans le cas où l'Activité est de redémarrer); créer et ajouter si elle n'existe pas; Puis, si elle existait déjà, d'obtenir des données de la RetainedFragment et configurer votre INTERFACE utilisateur avec les 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. Initier les AsyncTask à partir de l'INTERFACE utilisateur

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

    • Ajouter une barre de progression à la disposition de l'INTERFACE utilisateur;
    • Obtenir une référence à elle dans l'Activité oncreate();
    • Rendre visible et de l'invisible au début et à la fin du processus;
    • Définir le progrès de l'INTERFACE utilisateur dans onProgressUpdate.
    • Modifier les AsyncTask 2ème paramètre Générique de Void à un type qui peut gérer les mises à jour du progrès (p. ex. Entier).
    • publishProgress à des points réguliers dans doInBackground().

Tout le code de la procédure ci-dessus

L'Activité De Mise En Page.

<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é: sous-classé AsyncTask intérieur de la classe; sous-classé RetainedFragment intérieur de la classe qui gère la configuration de l'exécution des modifications (par exemple, lorsque l'utilisateur fait pivoter l'écran); et déterminée d'une barre de progression de la 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 la bibliothèque (référencé ci-dessus avec l'explicite préfixe package com.exemple.standardapplibrary.android.Réseau) qui est la vrai 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;
}

Ajouter toutes les autorisations que votre arrière-plan la tâche nécessite de la AndroidManifest.xml ...

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

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

3voto

Yury Points 10837

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X