233 votes

Tâche d'arrière-plan, dialogue de progression, changement d'orientation - existe-t-il une solution qui fonctionne à 100% ?

Je télécharge des données depuis l'internet en arrière-plan (j'utilise la fonction AsyncTask ) et afficher un dialogue de progression pendant le téléchargement. L'orientation change, l'activité est redémarrée et mon AsyncTask est terminée - je veux rejeter le dialogue de progression et démarrer une nouvelle activité. Mais appeler dismissDialog lève parfois une exception (probablement parce que l'activité a été détruite et que la 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 fil d'arrière-plan qui fonctionne même si l'utilisateur change d'orientation) ? Quelqu'un de Google a-t-il fourni une "solution officielle" ?

335voto

CommonsWare Points 402670

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

Étape n° 2 : Demandez au AsyncTask s'accrocher à la Activity par l'intermédiaire d'un membre de données, fixé par le constructeur et un setter.

Étape 3 : Lors de la création du AsyncTask , fournir le courant Activity au constructeur.

Étape 4 : Dans onRetainNonConfigurationInstance() retourner le AsyncTask après l'avoir détachée de l'activité originale, qui est en train de disparaître.

Étape 5 : Dans onCreate() si getLastNonConfigurationInstance() n'est pas null et l'ajouter à votre AsyncTask et appelez votre setter pour associer votre nouvelle activité à la tâche.

Étape 6 : Ne pas faire référence au membre des données d'activité de doInBackground() .

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

Voici un exemple de projet démontrant la technique.

Une autre approche consiste à laisser tomber le AsyncTask et déplacer votre travail dans un 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, le téléchargement d'un gros fichier). Vous pouvez utiliser une diffusion ordonnée Intent pour faire en sorte que l'activité réponde au travail en cours (si elle est toujours au premier plan) ou soulève un Notification pour faire savoir à l'utilisateur si le travail a été effectué. Voici un article de blog pour en savoir plus sur ce modèle.

13voto

Timmmm Points 9909

La réponse acceptée a été très utile, mais il n'y a pas de dialogue de progression.

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

  1. La rotation fonctionne, et le dialogue survit.
  2. Vous pouvez annuler la tâche et le dialogue en appuyant sur le bouton "retour" (si vous souhaitez ce comportement).
  3. Il utilise des fragments.
  4. La disposition du fragment sous l'activité change correctement lorsque le dispositif pivote.
  5. Le code source complet peut être téléchargé et un APK précompilé afin que vous puissiez voir si le comportement est celui que vous souhaitez.

9voto

Oleg Vaskevich Points 4216

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

  1. Vous devez toujours utiliser un dialogue de progression
  2. Une seule tâche est exécutée à la fois
  3. Il faut que la tâche persiste lorsque le téléphone est tourné et que la boîte de dialogue de progression soit automatiquement fermée.

Mise en œuvre

Vous devrez copier les deux fichiers qui se trouvent au bas de cet article dans votre espace de travail. Assurez-vous que :

  1. Tous vos Activity doivent s'étendre BaseActivity

  2. Sur onCreate() , super.onCreate() doit être appelé après l'initialisation de tous les membres qui doivent être accédés par l'utilisateur. ASyncTask s. De plus, il faut remplacer getContentViewId() pour fournir l'identifiant de la mise en page du formulaire.

  3. Remplacer onCreateDialog() comme d'habitude pour créer des boîtes de dialogue 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 plus tard.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    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 ça ! J'espère que cette solution robuste pourra aider quelqu'un.

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

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

    @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<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    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;
    }
}

2voto

Scott Biggs Points 958

Si la réponse de Mark (CommonsWare) fonctionne effectivement 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 de destruction d'activité en utilisant un objet Application pour référencer votre ASyncTask.

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

Tout le mérite revient à Ryan pour avoir trouvé cette solution.

0voto

xpepermint Points 5543

vous devez appeler toutes les actions de l'activité en utilisant le gestionnaire d'activité. Ainsi, si vous êtes dans un thread, vous devez créer un Runnable et l'appeler en utilisant le Handler de l'activité. Sinon, votre application se plantera parfois avec une exception fatale.

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