AsynsTask & ProgressDialog

 

[Mise à jour 26/01/2013] Cet article vous fournira une solution technique pour implémenter correctement un AsyncTask pour alimenter un ListView en résolvant les problèmes cités dans cet article : Implémenter correctement un AsyncTask pour alimenter un ListView

 

Lorsque l’on commence à travailler avec des AsyncTask, on est parfois amené à publier la progression du travail en cours de la tâche à l’utilisateur. Cela peut se faire, par exemple, à l’aide de ProgressBar ou ProgressDialog.


Le genre de code que l’on trouve sur la toile est généralement incomplet. En voici un exemple que j’ai trouvé et adapté avec un ProgressDialog

Main.java

package com.blogdebenoit.asynctask;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;

public class Main extends Activity {

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

		// attach a listener on the button to launch the asynctask
		findViewById(R.id.launcher).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				new ProgressAsynsTask(Main.this).execute();
			}
		});
	}

	/**
	 * Asynctask publishing its progress in a {@link ProgressDialog}
	 *
	 * @author Benoit
	 */
	class ProgressAsynsTask extends AsyncTask {

		/**
		 * context attached to this {@link AsyncTask}
		 */
		Context context = null;

		/**
		 * progress of the work
		 */
		int progress = 0;

		/**
		 * {@link ProgressDialog} displaying progress
		 */
		ProgressDialog progressDialog;

		/**
		 * attach a context to this activity while creating this
		 * {@link AsyncTask}
		 *
		 * @param context
		 */
		ProgressAsynsTask(Context context) {
			attach(context);
		}

		/**
		 * Attach context and create a {@link ProgressDialog}
		 *
		 * @param context
		 */
		public void attach(Context context) {
			this.context = context;
			// build a progress dialog
			progressDialog = new ProgressDialog(context);
			progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			progressDialog.setMessage(getString(R.string.loading));
			progressDialog.setOnCancelListener(new OnCancelListener() {
				@Override
				public void onCancel(DialogInterface dialog) {
					ProgressAsynsTask.this.cancel(true);
				}
			});
			progressDialog.show();
		}

		@Override
		protected Void doInBackground(Void... unused) {
			for (int i = 0; i < 100; i++) {
				SystemClock.sleep(50);
				progress = i;
				publishProgress(i);
			}

			return (null);
		}

		@Override
		protected void onProgressUpdate(Integer... progression) {
			progressDialog.setProgress(progression[0]);
		}

		@Override
		protected void onPostExecute(Void unused) {
			progressDialog.dismiss();
		}
	}
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:gravity="center">

	<Button
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="@string/launch"
		android:id="@+id/launcher" />

</LinearLayout>
 

Ce code a l’air de fonctionner correctement 😀 :

Mais dès que l’on tourne l’écran, là c’est le drame 😥 !!!

Mais que s’est-il donc passé. Tout simplement que l’activité a été détruite durant l’exécution de l’AsyncTask.

Et oui ! Même si l’activité a été détruite pour être ensuite recréée, l’AsyncTask a continuer tranquillement son travail et arrivé à la fin, elle a demandé à l’activité détruite de ne plus afficher le ProgressDialog. D’où le crash ! De plus lorsque l’on a tourné l’écran la ProgressDialog a disparu. Or ce n’est pas ce que nous voulons. Nous voulons toujours voir l’avancement de travail de l’AsyncTask.

L’une des premières solution que j’ai pu lire proposait de bloquer l’orientation de l’écran. Pas bieeeeeen 👿 !!! Cette solution n’est pas suffisante. En effet, Android peut décider de lui même et à tout moment de détruire une activité lorsque cette dernière n’est plus au premier plan (réception d’un appel, lancement par l’utilisateur d’une autre application, …) et on se retrouverai dans le même cas de figure qu’avant avec un joli plantage.

La seconde qui avait l’air séduisante était d’enregistrer l’Asynctask en utilisant les méthodes onRetainNonConfigurationInstance et getLastNonConfigurationInstance. Cependant on est confronté à un problème majeur, ces méthodes ne sont appelé que lors de changement de configuration. Cela ne prend pas en compte le cas ou l’application est détruire alors qu’elle était en arrière plan (réception d’un appel, lancement par l’utilisateur d’une autre application, …).

Pour une solution plus complète (et bien plus complexe) je vous conseille la lecture de cet article en deux parties. Je trouve tout de même étonnant que cela ne soit pas plus simplement géré sous Android :

Android AsyncTasks during a screen rotation, Part I
Android AsyncTasks during a screen rotation, Part II

Publicités

Étiquettes : , ,

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :