Je télécharge des données d’Internet en tâche de fond (j’utilise AsyncTask
) et j’affiche une boîte de dialogue de progression pendant le téléchargement. L'orientation change, l'activité est redémarrée, puis ma tâche asynchrone est terminée. Je souhaite fermer la boîte de dialogue de progression et commencer une nouvelle activité. Mais appeler licencierDialog génère parfois une exception (probablement parce que l'activité a été détruite et que la nouvelle activité n'a pas encore démarré).
Quel est le meilleur moyen de gérer ce type de problème (mise à jour de l'interface utilisateur à partir d'un fil de travail en arrière-plan qui fonctionne même si l'utilisateur change d'orientation)? Quelqu'un de Google a-t-il fourni une "solution officielle"?
Étape n ° 1: Faites de votre AsyncTask
une static
classe imbriquée, ou une classe entièrement séparée, mais pas une classe interne (imbriquée non statique).
Étape n ° 2: Demandez au AsyncTask
de conserver le Activity
via un membre de données, défini via le constructeur et un configurateur.
Étape n ° 3: lors de la création de AsyncTask
, fournissez le Activity
actuel au constructeur.
Étape n ° 4: Dans onRetainNonConfigurationInstance()
, renvoyez le AsyncTask
, après l'avoir détaché de l'activité d'origine, qui a maintenant disparu.
Étape 5: Dans onCreate()
, si getLastNonConfigurationInstance()
n'est pas null
, transmettez-le à votre classe AsyncTask
et appelez votre opérateur pour associer votre nouvelle activité. avec la tâche.
Étape n ° 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 suivante onCreate()
.
Voici un exemple de projet démontrant la technique.
Une autre approche consiste à abandonner le AsyncTask
et à déplacer votre travail dans un IntentService
. Ceci est particulièrement utile si le travail à effectuer peut être long et doit continuer quelle que soit l'activité de l'utilisateur (par exemple, le téléchargement d'un fichier volumineux). Vous pouvez utiliser une diffusion ordonnée Intent
pour que l'activité réponde au travail en cours (si elle est toujours au premier plan) ou pour déclencher un Notification
afin de permettre à l'utilisateur de savoir si le travail a été effectué. été fait. 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 dialogue de progression.
Heureusement pour vous, lecteur, j'ai créé un exemple extrêmement complet et très utile d'un AsyncTask avec un dialogue de progression !
Je travaille depuis une semaine pour trouver une solution à ce dilemme sans avoir à éditer le fichier manifeste. Les hypothèses de cette solution sont:
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:
Tous vos Activity
s devraient s'étendre BaseActivity
Dans onCreate()
, super.onCreate()
devrait être appelé après l'initialisation des membres auxquels votre ASyncTask
s a besoin d'accéder. En outre, remplacez getContentViewId()
pour fournir l'ID de présentation du formulaire.
Remplacez onCreateDialog()
comme d'habitude pour créer des boîtes de dialogue gérées par l'activité.
Voir le code ci-dessous pour un exemple de classe interne statique pour créer vos tâches asynchrones. 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();
Voilà! J'espère que cette solution robuste aidera quelqu'un.
BaseActivity.Java (organiser les importations vous-même)
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;
}
}
Quelqu'un de Google a-t-il fourni une "solution officielle"?
Oui.
La solution est plutôt une proposition d'architecture d'application plutôt que seulement du code.
Ils ont proposé 3 modèles de conception permettant à une application de fonctionner en synchronisation avec un serveur, quel que soit son état. (Cela fonctionnera même si l'utilisateur termine l'application, l'utilisateur change d'écran, l'application se termine, tout autre état possible dans lequel une opération de données en arrière-plan pourrait être interrompue, cela le couvre)
La proposition est expliquée dans le Android REST applications clientes discours pendant Google I/O 2010 de Virgil Dobjanschi. Il dure 1 heure, mais il est extrêmement intéressant à regarder.
La base de cette opération est d’abstraire les opérations réseau sur un Service
qui fonctionne indépendamment pour n’importe quel Activity
de l’application. Si vous travaillez avec des bases de données, l'utilisation de ContentResolver
et Cursor
vous donnera un modèle d'observateur prêt à l'emploi il est pratique de mettre à jour l’UI sans aucune logique supplémentaire, une fois que vous avez mis à jour votre base de données locale avec les données distantes extraites. Tout autre code après opération serait exécuté via un rappel transmis à la Service
(j'utilise une sous-classe ResultReceiver
pour cela).
Quoi qu'il en soit, mon explication est en fait assez vague, vous devriez absolument regarder le discours.
Bien que 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 modifications d'orientation ET les rares événements d'activité détruits 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 :
Le mérite revient entièrement à Ryan pour avoir trouvé celui-ci.
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 du périphérique. J'ai aussi une solution simple pour le vieil Android.
vous devez appeler toutes les actions d'activité à l'aide du gestionnaire d'activité. Donc, si vous êtes dans un fil de discussion, vous devez créer un Runnable et le publier à l’aide de Activitie's Handler. Sinon, votre application plantera parfois avec une exception fatale.
Voici ma solution: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
Fondamentalement, les étapes sont les suivantes:
onSaveInstanceState
pour enregistrer la tâche si elle est en cours de traitement.onCreate
je récupère la tâche si elle a été enregistrée.onPause
, je supprime le ProgressDialog
s'il est affiché.onResume
, je montre le ProgressDialog
si la tâche est en cours de traitement.