Android : la programmation concurrente
Présentation
Il est nécessaire d’utiliser la programmation concurrente dans les applications Android afin de n’avoir aucune lenteur au niveau de l’interface graphique. Il est donc nécessaire d’attribuer une tâche entière à la gestion de l’interface graphique et de ses actions rapides et des tâches parallèles dédiés aux traitements longs.
Il existe trois mécanismes permettant de répondre à ce besoin :
-
Le thread : permet de créer une nouvelle tâche ne pouvant communiquer directement avec l’interface graphique.
-
La tâche asynchrone : permet de créer une nouvelle tâche de manière structurée et pouvant communiquer directement avec l’interface graphique.
-
Le handler : permet à plusieurs tâches parallèles de communiquer via un canal commun et standardisé vers l’interface graphique.
Problématique
1. Présentation
Lorsqu’un traitement long doit être exécuté sur une application, celui-ci doit être traité dans un nouveau thread. Effectivement, si un traitement long est exécuté sur le thread gérant l’interface graphique alors elle ne pourra être mise à jour qu’au moment où le traitement sera terminé. Ce qui aura pour effet de rendre l’utilisation de l’application désagréable.
2. Démonstration
Cette démonstration présente une application avec une mauvaise gestion de son traitement long. Lorsque l’on clique sur le bouton "traitement long" l’application se fige pendant 60 secondes.
La démonstration est réalisée dans un nouveau projet dont l’activité principale se nomme MainActivity.
Activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="fr.acos.threadwithkotlin.MainActivity"> ...
Les threads
1. Présentation
Les threads permettent d’exécuter plusieurs instructions en même temps. Cela permet d’avoir des instructions qui gèrent l’interface graphique et des instructions qui s’occupent des traitements longs en parallèle dans le principal but de ne pas bloquer l’interface graphique.
2. Syntaxe
Voici la syntaxe du constructeur de la classe Thread :
fun thread(
start: Boolean = true,
isDaemon: Boolean = false,
contextClassLoader: ClassLoader? = null,
name: String? = null,
priority: Int = -1,
block: () -> Unit
): Thread
Description des différents paramètres :
Paramètre |
Description |
start |
Si la valeur de ce paramètre est égale à true, alors le thread démarre immédiatement après sa création. |
isDaemon |
Un daemon thread en Java est un thread qui n’empêche pas la JVM de se terminer. |
contextClassLoader |
Permet de charger des classes et des ressources dans le thread. |
name |
Nom du thread. |
priority |
Priorité du thread. |
block |
Bloc d’instructions à exécuter dans le thread. C’est le seul paramètre qui est obligatoire. |
3. Usage
Exemple de création d’un thread
Thread{traitementLong()}.start()
Dans cet exemple, un nouveau thread va être créé et lancé pour exécuter le traitement long de la fonction traitementLong().
4. Démonstration
Cette démonstration permet de voir que le traitement long ne bloque pas l’utilisation de l’IHM.
La démonstration est réalisée dans un nouveau projet dont l’activité principale se nomme MainActivity.
Activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" ...
Les tâches asynchrones
1. Présentation
La classe AsyncTask permet de mettre en place des tâches asynchrones. Les tâches asynchrones permettent de faciliter l’utilisation des threads. Pour créer une tâche asynchrone, il est nécessaire de définir une classe héritant de la classe AsyncTask et de redéfinir les fonctions suivantes :
Fonctions |
Description |
doInBackground() |
Cette fonction contient le contenu du traitement long. Les instructions définies dans cette fonction sont exécutées dans un nouveau thread. |
onPreExecute() |
Cette fonction s’exécute sur le thread UI. Elle s’exécute avant doInBackground(). |
onPostExecute() |
Cette fonction s’exécute sur le thread UI. Elle s’exécute après doInBackground(). |
onProgressUpdate() |
Cette fonction s’exécute sur le thread UI. Elle s’exécute pendant le traitement long donc en même temps que doInBackground(). Cette fonction s’exécute lorsque la fonction publishProgress() est appelée depuis doInBackground(). |
Pour exécuter la tâche asynchrone, il faut créer une instance de la classe héritant de AsyncTask et utiliser la fonction execute().
2. Démonstration
Cette démonstration permet de voir comment mettre en place des tâches asynchrones.
La démonstration est réalisée dans un nouveau projet dont l’activité principale se nomme MainActivity.
Activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="fr.acos.asynctaskwithkotlin.MainActivity">
<TextView
android:id="@+id/tv_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
...
Les Handlers
1. Présentation
Un Handler permet :
-
à différents threads de communiquer entre eux,
-
de réceptionner les messages envoyés par des threads et ensuite d’exécuter une action sur l’IHM par exemple.
La démonstration suivante ne présente pas toutes les possibilités offertes par les handlers.
2. Démonstration
Dans cette démonstration, trois traitements longs vont pouvoir être lancés. Ces traitements longs envoient des messages au handler créé sur le thread UI pour mettre à jour l’IHM. Concrètement, il y a trois diffuseurs de messages et un récepteur de messages.
La démonstration est réalisée dans un nouveau projet dont l’activité principale se nomme MainActivity.
Activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="fr.acos.handlerwithkotlin.MainActivity">
<TextView
android:id="@+id/tv_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_traitement_long1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickTraitementLong1" ...
Les coroutines
Kotlin a mis au point un concept de tâches concurrentes coopératives appelé coroutine, permettant de simplifier l’écriture des threads et des tâches asynchrones et d’optimiser la mémoire grâce à un système de fonctions concurrentes suspendues et reprises. Ce concept est encore expérimental aujourd’hui et ne sera pas traité dans le livre.