2010-01-23 20 views
33

J'ai travaillé avec AsyncTasks sur Android et je suis confronté à un problème. Prenez un exemple simple, une activité avec une asyncTask.Comportement du contexte Android AsyncTask

La tâche en arrière-plan ne fait rien de spectaculaire, elle dort juste pendant 8 secondes.

À la fin de la tâche AsyncTask dans la méthode onPostExecute(), je définis simplement un statut de visibilité de bouton à View.VISIBLE, uniquement pour vérifier mes résultats. Maintenant, 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 veille de 8 secondes). Je comprends le cycle de vie de l'activité Android et je sais que l'activité est détruite et recréée.

C'est là qu'intervient le problème. La tâche asynchrone fait référence à un bouton et contient apparemment une référence au contexte qui a démarré la tâche asynchrone en premier lieu.

Je m'attendais à ce que cet ancien contexte (depuis que l'utilisateur a provoqué un changement d'orientation) soit devenu nul et la tâche asynchrone à lancer un NPE pour la référence au bouton qu'il essaie de rendre visible. Au lieu de cela, aucun NPE n'est levé, le AsyncTask pense que la référence du bouton n'est pas nulle, le définit comme visible. Le résultat? Rien ne se passe sur l'écran!

Mise à jour: Je me suis attaqué à cela en gardant un WeakReference à l'activité et de commutation quand un changement de configuration se produit. C'est lourd.

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 d'exécuter et pendant que le AsyncTask travaille changer votre orientation des téléphones.

+0

un peu trompeur d'écrire "garder une référence faible à l'activité" et de ne pas le faire dans le code. Pire encore, plus loin vous dites "et basculer quand un changement de configuration se produit" ce qui ne veut vraiment rien dire ... Changer de quoi? – Ewoks

Répondre

23

AsyncTask n'a pas été conçu pour être réutilisé une fois une activité a été démoli et redémarré. L'objet Handler interne devient obsolète, comme vous l'avez indiqué. Dans l'exemple Shelves de Romain Guy, il annule simplement tout AsyncTask en cours d'exécution, puis redémarre les nouveaux changements post-orientation.

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

2

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

Pour cela, ajoutez ceci à votre tag <Activity> dans votre fichier manifeste:

android:configChanges="orientation|keyboardHidden" 

Et passer outre onConfigurationChanged dans votre classe d'activité:

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

Votre approche fonctionne mais avez-vous de bons contre-arguments à cette approche? Pourquoi est-ce par défaut que l'OS détruit et recrée l'activité alors? En d'autres termes, qu'est-ce que je "perds" en faisant ce que vous proposez? Btw, cela a fonctionné comme prévu cependant. Merci pour la réponse. – dnkoutso

+0

Autant que je sache, la principale chose que vous perdriez serait la possibilité d'avoir différentes dispositions d'activités en mode paysage et portrait. Il y a peut-être d'autres choses que vous "perdriez" mais je ne sais pas ce qu'elles sont. –

+1

Google recommande généralement contre cette approche, sauf si vous savez ce que vous faites. – emmby

2

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

Mais si vous avez besoin de détruire l'activité (différentes dispositions pour le portrait et le paysage) vous pouvez faire de l'AsyncTask une classe publique (lisez ici pourquoi il ne devrait pas être privé Android: AsyncTask recommendations: private class or public class?) puis créez une méthode setActivity pour définir la référence à l'activité en cours chaque fois qu'elle est détruite/créée.

Vous pouvez voir un exemple ici: Android AsyncTask in external class

3

Si vous avez seulement besoin d'un contexte et ne l'utiliser pour des trucs ui vous pouvez simplement passer le ApplicationContext à votre AsyncTask.You ont souvent besoin le contexte des ressources du système, par exemple. N'essayez pas de mettre à jour l'interface utilisateur à partir d'une asyncTask et essayez d'éviter de gérer les modifications de configuration vous-même car cela peut être compliqué. Afin de mettre à jour l'interface utilisateur, vous pouvez enregistrer un récepteur de diffusion et envoyer une diffusion.

Vous devriez également avoir AsyncTask en tant que classe publique distincte 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.