Tâche de fond, boîte de dialog de progression, changement d’orientation – existe-t-il une solution 100% opérationnelle?

Je télécharge des données d’Internet dans le thread d’arrière-plan (j’utilise AsyncTask ) et affiche une boîte de dialog de progression pendant le téléchargement. Les changements d’orientation, l’activité est redémarrée, puis ma tâche AsyncTask est terminée. Je souhaite supprimer la boîte de dialog de progression et lancer une nouvelle activité. Cependant, appeler exceptionDialog lance parfois une exception (probablement parce que l’activité a été détruite et qu’une nouvelle activité n’a pas encore été lancée).

Quelle est la meilleure façon de gérer ce type de problème (mise à jour de l’interface utilisateur à partir d’un thread d’arrière-plan qui fonctionne même si l’utilisateur modifie l’orientation)? Est-ce que quelqu’un de Google a fourni une “solution officielle”?

Etape n ° 1: Faites de votre AsyncTask une classe nestede static ou une classe entièrement séparée, mais pas une classe interne (non statique).

Etape n ° 2: Demander à AsyncTask conserver l’ Activity via un membre de données, défini via le constructeur et un setter.

Étape # 3: lors de la création de la AsyncTask , fournissez l’ Activity en cours au constructeur.

Etape n ° 4: Dans onRetainNonConfigurationInstance() , renvoyez le AsyncTask , après l’avoir détaché de l’activité d’origine.

Étape 5: Dans onCreate() , si getLastNonConfigurationInstance() n’est pas null , AsyncTask classe AsyncTask et appelez votre installateur pour associer votre nouvelle activité à la tâche.

Étape # 6: Ne faites pas référence au membre de données d’activité de doInBackground() .

Si vous suivez la recette ci-dessus, tout fonctionnera. onProgressUpdate() et onPostExecute() sont suspendus entre le début de onRetainNonConfigurationInstance() et la fin de la suite onCreate() .

Voici un exemple de projet démontrant la technique.

Une autre approche consiste à abandonner AsyncTask et à déplacer votre travail dans IntentService . Ceci est particulièrement utile si le travail à effectuer peut être long et doit se poursuivre indépendamment de ce que l’utilisateur fait en termes d’activités (par exemple, télécharger un fichier volumineux). Vous pouvez utiliser une Intent diffusion ordonnée pour que l’activité réponde au travail en cours (si elle se trouve toujours au premier plan) ou émettre une Notification pour informer l’utilisateur si le travail a été effectué. Voici un article de blog avec plus sur ce modèle.

La réponse acceptée a été très utile, mais elle n’a pas de boîte de dialog de progression.

Heureusement pour vous, lecteur, j’ai créé un exemple extrêmement complet et fonctionnel d’une AsyncTask avec une boîte de dialog de progression !

  1. La rotation fonctionne et le dialog survit.
  2. Vous pouvez annuler la tâche et la boîte de dialog en appuyant sur le bouton Retour (si vous voulez ce comportement).
  3. Il utilise des fragments.
  4. La disposition du fragment sous l’activité change correctement lorsque l’appareil tourne.

J’ai peiné pendant une semaine pour trouver une solution à ce dilemme sans avoir à modifier le fichier manifeste. Les hypothèses pour cette solution sont:

  1. Vous devez toujours utiliser une boîte de dialog de progression
  2. Une seule tâche est effectuée à la fois
  3. La tâche doit persister lorsque vous faites pivoter le téléphone et que la boîte de dialog de progression est automatiquement supprimée.

la mise en oeuvre

Vous devrez copier les deux fichiers situés au bas de cet article dans votre espace de travail. Assurez-vous simplement que:

  1. Toutes vos Activity doivent étendre BaseActivity

  2. Dans onCreate() , super.onCreate() devrait être appelé après l’initialisation des membres auxquels votre ASyncTask . En outre, substituez getContentViewId() pour fournir l’ID de présentation du formulaire.

  3. Remplacez onCreateDialog() comme d’habitude pour créer des boîtes de dialog gérées par l’activité.

  4. Voir le code ci-dessous pour un exemple de classe interne statique pour créer vos AsyncTasks. Vous pouvez stocker votre résultat dans mResult pour y accéder ultérieurement.


 final static class MyTask extends SuperAsyncTask { public OpenDatabaseTask(BaseActivity activity) { super(activity, MY_DIALOG_ID); // change your dialog ID here... // and your dialog will be managed automatically! } @Override protected Void doInBackground(Void... params) { // your task code return null; } @Override public boolean onAfterExecute() { // your after execute code } } 

Et enfin, pour lancer votre nouvelle tâche:

 mCurrentTask = new MyTask(this); ((MyTask) mCurrentTask).execute(); 

C’est tout! J’espère que cette solution robuste aidera quelqu’un.

BaseActivity.java (organise les importations vous-même)

 protected abstract int getContentViewId(); public abstract class BaseActivity extends Activity { protected SuperAsyncTask mCurrentTask; public HashMap mDialogMap = new HashMap(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getContentViewId()); mCurrentTask = (SuperAsyncTask) getLastNonConfigurationInstance(); if (mCurrentTask != null) { mCurrentTask.attach(this); if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null && mDialogMap.get((Integer) mCurrentTask.dialogId)) { mCurrentTask.postExecution(); } } } @Override protected void onPrepareDialog(int id, Dialog dialog) { super.onPrepareDialog(id, dialog); mDialogMap.put(id, true); } @Override public Object onRetainNonConfigurationInstance() { if (mCurrentTask != null) { mCurrentTask.detach(); if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null && mDialogMap.get((Integer) mCurrentTask.dialogId)) { return mCurrentTask; } } return super.onRetainNonConfigurationInstance(); } public void cleanupTask() { if (mCurrentTask != null) { mCurrentTask = null; System.gc(); } } } 

SuperAsyncTask.java

 public abstract class SuperAsyncTask extends AsyncTask { protected BaseActivity mActivity = null; protected Result mResult; public int dialogId = -1; protected abstract void onAfterExecute(); public SuperAsyncTask(BaseActivity activity, int dialogId) { super(); this.dialogId = dialogId; attach(activity); } @Override protected void onPreExecute() { super.onPreExecute(); mActivity.showDialog(dialogId); // go polymorphism! } protected void onPostExecute(Result result) { super.onPostExecute(result); mResult = result; if (mActivity != null && mActivity.mDialogMap.get((Integer) dialogId) != null && mActivity.mDialogMap.get((Integer) dialogId)) { postExecution(); } }; public void attach(BaseActivity activity) { this.mActivity = activity; } public void detach() { this.mActivity = null; } public synchronized boolean postExecution() { Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId); if (dialogExists != null || dialogExists) { onAfterExecute(); cleanUp(); } public boolean cleanUp() { mActivity.removeDialog(dialogId); mActivity.mDialogMap.remove((Integer) dialogId); mActivity.cleanupTask(); detach(); return true; } } 

Est-ce que quelqu’un de Google a fourni une “solution officielle”?

Oui.

La solution est plutôt une proposition d’architecture d’application plutôt que du code .

Ils ont proposé 3 modèles de conception permettant à une application de fonctionner en synchronisation avec un serveur, quel que soit l’état de l’application (elle fonctionnera même si l’utilisateur a terminé l’application, l’utilisateur change d’écran, une opération de données d’arrière-plan pourrait être interrompue, cela le couvre)

La proposition est expliquée dans le discours des applications client Android REST lors de Google I / O 2010 de Virgil Dobjanschi. Il dure 1 heure, mais cela vaut la peine de le regarder.

La base de cette parsing consiste à extraire les opérations réseau vers un Service qui fonctionne indépendamment de toute Activity de l’application. Si vous travaillez avec des bases de données, l’utilisation de ContentResolver et Cursor vous donnera un modèle Observer ContentResolver l’emploi, pratique pour mettre à jour l’interface utilisateur sans aucune logique supplémentaire, une fois que vous aurez mis à jour votre firebase database locale. Tout autre code après opération serait exécuté via un rappel transmis au Service (j’utilise une sous-classe ResultReceiver pour cela).

En tout cas, mon explication est en fait assez vague, vous devriez regarder le discours.

Bien que la réponse de Mark (CommonsWare) fonctionne bien pour les changements d’orientation, elle échoue si l’activité est détruite directement (comme dans le cas d’un appel téléphonique).

Vous pouvez gérer les changements d’orientation ET les rares événements détruits en utilisant un object Application pour référencer votre ASyncTask.

Il y a une excellente explication du problème et de la solution ici :

Le crédit va complètement à Ryan pour avoir compris cela.

Après 4 ans, Google a résolu le problème en appelant simplement setRetainInstance (true) dans Activity onCreate. Cela préservera votre instance d’activité pendant la rotation de l’appareil. J’ai aussi une solution simple pour Android plus ancien.

Vous devez appeler toutes les actions d’activité à l’aide du gestionnaire d’activité. Donc, si vous êtes dans un thread, vous devriez créer un Runnable et publié en utilisant Activitie’s Handler. Sinon, votre application tombera parfois avec une exception fatale.

Voici ma solution: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

Fondamentalement, les étapes sont les suivantes:

  1. J’utilise onSaveInstanceState pour enregistrer la tâche si elle est encore en cours de traitement.
  2. Dans onCreate j’obtiens la tâche si elle a été enregistrée.
  3. Dans onPause je onPause le ProgressDialog s’il est affiché.
  4. Dans onResume je montre le ProgressDialog si la tâche est encore en cours de traitement.