Threads et Handlers dans Android
Android permet d’utiliser les Threads de Java. Cependant, un thread en arrière-plan doit respecter certaines règles, notamment ne pas modifier directement des éléments de l’interface utilisateur (UI) en premier plan pour éviter des conflits d’accès. Pour cela, il doit passer par la classe Handler ou utiliser les AsyncTask.
Exemple avec Handler
Créez un projet nommé fr.efrei.android.moi.handler avec l’activité ProgressTestActivity.
Layout XML (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"> <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:indeterminate="false" android:max="10" android:padding="4dip"/> <Button android:text="Start Progress" android:onClick="startProgress" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
Code Java pour ProgressTestActivity
public class ProgressTestActivity extends Activity { private Handler handler; private ProgressBar progress; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); progress = (ProgressBar) findViewById(R.id.progressBar1); handler = new Handler(); } public void startProgress(View view) { Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i <= 10; i++) { final int value = i; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } handler.post(new Runnable() { @Override public void run() { progress.setProgress(value); } }); } } }; new Thread(runnable).start(); } }
Explication
Ce code crée un thread pour simuler une tâche longue (ici, une boucle de 0 à 10 avec un délai de 2 secondes). Le Handler permet de mettre à jour la barre de progression dans le thread principal (UI) sans bloquer l’interface.
Exemple avec AsyncTask
Utilisons une tâche asynchrone pour télécharger le contenu d’une page web. Créez un projet nommé fr.efrei.android.moi.asynctask avec l’activité ReadWebpageAsyncTask.
Permissions et Layout XML
Ajoutez la permission android.permission.INTERNET dans le fichier AndroidManifest.xml.
Voici le layout XML (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="match_parent" android:layout_height="match_parent"> <Button android:layout_height="wrap_content" android:layout_width="match_parent" android:id="@+id/readWebpage" android:onClick="readWebpage" android:text="Load Webpage"/> <TextView android:id="@+id/TextView01" android:layout_width="match_parent" android:layout_height="match_parent" android:text="Example Text"/> </LinearLayout>
Code Java pour ReadWebpageAsyncTask
package fr.efrei.android.moi.asynctask; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.TextView; public class ReadWebpageAsyncTask extends Activity { private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); textView = (TextView) findViewById(R.id.TextView01); } private class DownloadWebPageTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... urls) { String response = ""; for (String url : urls) { DefaultHttpClient client = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(url); try { HttpResponse execute = client.execute(httpGet); InputStream content = execute.getEntity().getContent(); BufferedReader buffer = new BufferedReader(new InputStreamReader(content)); String s = ""; while ((s = buffer.readLine()) != null) { response += s; } } catch (Exception e) { e.printStackTrace(); } } return response; } @Override protected void onPostExecute(String result) { textView.setText(result); } } public void readWebpage(View view) { DownloadWebPageTask task = new DownloadWebPageTask(); task.execute(new String[] { "https://example.com" }); } }
Cycle de vie d’une Activity et d’un Thread
Voici un exemple où un thread télécharge une image tout en affichant une boîte de dialogue jusqu’à la fin du téléchargement.
Manifest XML
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.efrei.android.moi.threadslifecycle" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="10" /> <uses-permission android:name="android.permission.INTERNET" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".ThreadsLifecycleActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Layout XML (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"> <LinearLayout android:layout_height="wrap_content" android:layout_width="match_parent" android:id="@+id/linearLayout1"> <Button android:onClick="downloadPicture" android:layout_height="wrap_content" android:text="Click to start download" android:layout_width="wrap_content"/> <Button android:onClick="resetPicture" android:layout_height="wrap_content" android:text="Reset Picture" android:layout_width="wrap_content"/> </LinearLayout> <ImageView android:src="@drawable/icon" android:id="@+id/imageView1" android:layout_height="match_parent" android:layout_width="match_parent"/> </LinearLayout>
Code Java pour ThreadsLifecycleActivity
package fr.efrei.android.moi.threadslifecycle; import java.io.IOException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import android.app.Activity; import android.app.ProgressDialog; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.widget.ImageView; public class ThreadsLifecycleActivity extends Activity { private static ProgressDialog dialog; private static ImageView imageView; private static Bitmap downloadBitmap; private static Handler handler; private Thread downloadThread; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); handler = new Handler(); imageView = (ImageView) findViewById(R.id.imageView1); if (downloadBitmap != null) { imageView.setImageBitmap(downloadBitmap); } downloadThread = (Thread) getLastNonConfigurationInstance(); if (downloadThread != null && downloadThread.isAlive()) { dialog = ProgressDialog.show(this, "Download", "downloading"); } } public void resetPicture(View view) { if (downloadBitmap != null) { downloadBitmap = null; } imageView.setImageResource(R.drawable.icon); } public void downloadPicture(View view) { dialog = ProgressDialog.show(this, "Download", "downloading"); downloadThread = new MyThread(); downloadThread.start(); } @Override public Object onRetainNonConfigurationInstance() { return downloadThread; } @Override protected void onDestroy() { if (dialog != null && dialog.isShowing()) { dialog.dismiss(); dialog = null; } super.onDestroy(); } private static Bitmap downloadBitmap(String url) throws IOException { HttpGet httpGet = new HttpGet(url); HttpClient httpClient = new DefaultHttpClient(); HttpResponse response = httpClient.execute(httpGet); StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); if (statusCode == 200) { HttpEntity entity = response.getEntity(); byte[] bytes = EntityUtils.toByteArray(entity); Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); return bitmap; } else { throw new IOException("Download failed, HTTP response code " + statusCode + " - " + statusLine.getReasonPhrase()); } } static private class MyThread extends Thread { @Override public void run() { try { Thread.sleep(5000); downloadBitmap = downloadBitmap("https://example.com/image.jpg"); handler.post(new MyRunnable()); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } static private class MyRunnable implements Runnable { public void run() { imageView.setImageBitmap(downloadBitmap); dialog.dismiss(); } } }
FAQ
1. Pourquoi utiliser un Handler dans un thread Android ?
Un Handler permet de communiquer avec le thread principal (UI) depuis un thread secondaire. Cela évite les erreurs liées à l’accès simultané à des ressources partagées.
2. Qu’est-ce qu’une AsyncTask et comment fonctionne-t-elle ?
Une AsyncTask est une classe Android qui exécute une tâche en arrière-plan et permet de mettre à jour l’interface utilisateur après son exécution. Elle est composée de trois étapes : doInBackground (travail en arrière-plan), onProgressUpdate (mise à jour intermédiaire) et onPostExecute (résultat final).
3. Comment gérer le cycle de vie d’un thread dans une Activity Android ?
Pour conserver un thread après une rotation d’écran, utilisez la méthode onRetainNonConfigurationInstance(). Cela permet de récupérer le thread dans onCreate après une interruption due à une configuration.