33 votes

Comportement du contexte AsyncTask d'Android

J'ai travaillé avec AsyncTasks dans Android et je suis confronté à un problème.

Prenons un exemple simple, une activité avec une AsyncTask. La tâche en arrière-plan ne fait rien de spectaculaire, elle se contente de dormir pendant 8 secondes.

À la fin de l'AsyncTask, dans la méthode onPostExecute(), je ne fais que définir l'état de visibilité d'un bouton sur View.VISIBLE, uniquement pour vérifier mes résultats.

Cela fonctionne très bien jusqu'à ce que l'utilisateur décide de changer l'orientation de son téléphone pendant que l'AsyncTask fonctionne (dans la fenêtre de sommeil de 8 secondes).

Je comprends le cycle de vie des activités Android et je sais que l'activité est détruite et recréée.

C'est là que le problème se pose. L'AsyncTask fait référence à un bouton et détient apparemment une référence au contexte qui a lancé l'AsyncTask en premier lieu.

Je m'attendrais à ce que cet ancien contexte (puisque l'utilisateur a provoqué un changement d'orientation) devienne nul et que l'AsyncTask lance un NPE pour la référence au bouton qu'il essaie de rendre visible.

Au lieu de cela, aucun NPE n'est déclenché, l'AsyncTask pense que la référence du bouton n'est pas nulle, et la rend visible. Le résultat ? Rien ne se passe à l'écran !

Mise à jour : J'ai abordé ce problème en gardant un WeakReference à l'activité et à la commutation lorsqu'un changement de configuration se produit. Cette méthode est lourde.

Voici le code :

public class Main extends Activity {

    private Button mButton = null;
    private Button mTestButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mButton = (Button) findViewById(R.id.btnStart);
        mButton.setOnClickListener(new OnClickListener () {
            @Override
            public void onClick(View v) {
                new taskDoSomething().execute(0l);
            }
        });
        mTestButton = (Button) findViewById(R.id.btnTest);   
    }

    private class TaskDoSomething extends AsyncTask<Long, Integer, Integer> 
    {
        @Override
        protected Integer doInBackground(Long... params) {
            Log.i("LOGGER", "Starting...");
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 0;
        }

        @Override
        protected void onPostExecute(Integer result) {
            Log.i("LOGGER", "...Done");
            mTestButton.setVisibility(View.VISIBLE);
        }
    }
}

Essayez de l'exécuter et pendant que l'AsyncTask fonctionne, changez l'orientation de votre téléphone.

23voto

jasonhudgins Points 1732

AsyncTask n'est pas conçu pour être réutilisé une fois qu'une activité a été démantelée et redémarrée. L'objet Handler interne devient obsolète, comme vous l'avez dit. Dans l'exemple Shelves de Romain Guy, il annule simplement toutes les AsyncTask en cours d'exécution, puis en relance de nouvelles après le changement d'orientation.

Il est possible de transmettre votre fil à la nouvelle activité, mais cela ajoute beaucoup de plomberie. Il n'y a pas de méthode généralement reconnue pour le faire, mais vous pouvez lire ma méthode ici : http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html

3voto

Ralph Mueller Points 97

Si vous n'avez besoin que d'un contexte et que vous ne l'utiliserez pas pour l'interface utilisateur, vous pouvez simplement passer l'ApplicationContext à votre AsyncTask, mais vous avez souvent besoin du contexte pour les ressources système, par exemple.

N'essayez pas de mettre à jour l'interface utilisateur à partir d'une tâche AsyncTask et évitez de gérer vous-même les changements de configuration, car cela peut devenir compliqué. Pour mettre à jour l'interface utilisateur, vous pouvez enregistrer un récepteur de diffusion et envoyer une diffusion.

Vous devriez également avoir l'AsyncTask comme une classe publique séparée de l'activité comme mentionné ci-dessus, cela rend les tests beaucoup plus faciles. Malheureusement, la programmation Android renforce souvent les mauvaises pratiques et les exemples officiels n'aident pas.

2voto

mbaird Points 31293

C'est le genre de chose qui m'amène à toujours empêcher que mon activité soit détruite/recréée lors d'un changement d'orientation.

Pour ce faire, ajoutez ceci à votre <Activity> dans votre fichier manifeste :

android:configChanges="orientation|keyboardHidden" 

Et surchargez onConfigurationChanged dans votre classe d'activité :

@Override
public void onConfigurationChanged(final Configuration newConfig)
{
    // Ignore orientation change to keep activity from restarting
    super.onConfigurationChanged(newConfig);
}

2voto

NeTeInStEiN Points 7331

Pour éviter cela, vous pouvez utiliser la réponse donnée ici : http://stackoverflow.com/a/2124731/327011

Mais si vous devez détruire l'activité (différentes mises en page pour le portrait et le paysage), vous pouvez faire de l'AsyncTask une classe publique (lisez ici pourquoi elle ne devrait pas être privée). Android : Recommandations pour AsyncTask : classe privée ou classe publique ? ) et créez ensuite une méthode setActivity pour définir la référence à l'activité actuelle chaque fois qu'elle est détruite/créée.

Vous pouvez voir un exemple ici : Android AsyncTask dans une classe externe

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