1. Le fil d'exécution de l'interface utilisateur sous Android▲
1-1. Le fil d'exécution principal▲
Android modifie l'interface utilisateur et gère les événements de saisie dans un fil d'exécution d'interface utilisateur unique. Celui-ci est appelé également le fil d'exécution principal.
Android recueille tous les événements dans une file d'attente et traite une instance de la classe Looper.
1-2. Pourquoi utiliser l'accès concurrentiel ?▲
Si le programmeur n'utilise pas d'éléments de concurrence, tout le code d'une application Android s'exécute dans le fil d'exécution principal et les instructions sont exécutées l'une après l'autre.
Si vous effectuez une opération de longue durée, par exemple l'accès aux données à partir d'Internet, l'application est bloquée jusqu'à ce que l'opération correspondante ait pris fin.
Pour assurer une bonne expérience utilisateur, toutes les opérations potentiellement lentes d'une application Android devraient être exécutées de manière asynchrone, par exemple, par des éléments de concurrence du langage Java ou du framework Android. Cela comprend toutes les opérations potentiellement lentes, comme l'accès réseau ou l'accès aux fichiers et aux bases de données et les calculs complexes.
Android impose aux applications un temps de réaction au pire des cas. Si une activité ne réagit pas à la saisie de l'utilisateur dans les cinq secondes, le système Android affiche une boîte de dialogue « L'application ne répond pas »(4). Dans cette dernière, l'utilisateur peut choisir d'arrêter l'application.
2. Les notions de base Android▲
Ce qui suit implique que vous avez déjà les bases du développement Android. Veuillez consulter le tutoriel de développement Android pour en apprendre les bases. Lisez également les tutoriels de développement Android pour plus d'informations à ce sujet.
3. L'utilisation des fils d'exécution Java sous Android▲
3-1. Utiliser le threading Java sous Android▲
Android prend en charge l'utilisation de la classe Thread pour effectuer des traitements asynchrones.
Android fournit également le paquetage java.util.concurrent pour exécuter quelque chose en arrière-plan, par exemple, en utilisant les classes ThreadPools et Executor.
Si vous avez besoin de mettre à jour l'interface utilisateur à partir d'un nouveau fil d'exécution, vous devez synchroniser avec le fil d'exécution de l'interface utilisateur.
3-2. Les inconvénients de l'utilisation des fils d'exécution Java sous Android▲
Si vous utilisez des fils d'exécution Java, vous devrez tenir compte, dans votre code, des exigences suivantes :
- synchronisation avec le fil d'exécution principal si vous postez des résultats en retour sur l'interface utilisateur ;
- aucune valeur par défaut pour annuler le fil d'exécution ;
- aucun fil d'exécution mis en commun par défaut ;
- pas de mécanisme par défaut pour le traitement des changements de configuration Android.
4. Les éléments de concurrence dans Android▲
Par rapport à la norme Java, Android fournit des éléments supplémentaires pour gérer la concurrence. Vous pouvez utiliser la classe android.os.Handler ou les classes AsyncTask. Des approches plus sophistiquées sont basées sur la classe Loader, les fragments conservés et les services.
5. La classe Handler▲
5-1. Le but de la classe Handler▲
La classe Handler peut être utilisée pour s'enregistrer à un fil d'exécution. Elle fournit un canal simple pour envoyer des données à ce fil d'exécution.
Un objet Handler s'enregistre avec le fil dans lequel il est créé. Par exemple, si vous créez une nouvelle instance de la classe Handler dans la méthode onCreate() de votre activité, l'objet Handler qui en résulte peut être utilisé pour publier des données sur le fil d'exécution principal.
Les données affichées par la classe Handler peuvent être des instances de la classe Message ou de la classe Runnable.
Un gestionnaire est particulièrement utile si vous voulez publier des données sur le fil d'exécution principal à plusieurs reprises.
5-2. Création et réutilisation des instances de gestionnaires▲
Pour utiliser un gestionnaire, vous devez créer une classe dérivée et y redéfinir la méthode handleMessage() pour traiter les messages.
Votre fil d'exécution peut envoyer des messages à l'objet Handler par la méthode sendMessage(Message) ou par la méthode sendEmptyMessage().
Pour traiter un Runnable, vous pouvez utiliser la méthode post().
Pour éviter la création d'objets, vous pouvez réutiliser l'objet Handler existant de votre activité.
//
Réutilisez
le
gestionnaire
existant
si
vous
n'en
avez
pas
//
à
redéfinir
le
traitement
des
messages
handler =
getWindow
().getDecorView
().getHandler
();
La classe View vous permet de poster des objets de type Runnable par la méthode post().
5-3. Exemple▲
Le code suivant illustre l'utilisation d'un gestionnaire par une vue.
Supposons que votre activité utilise le schéma suivant :
<?
xml
version="1.0"
encoding="utf-8"?
>
<
LinearLayout
xmlns
:
android
=
"
http://schemas.android.com/apk/res/android
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
match_parent
"
android
:
orientation
=
"
vertical
"
>
<
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
"
>
<
/
ProgressBar
>
<
TextView
android
:
id
=
"
@+id/textView1
"
android
:
layout_width
=
"
wrap_content
"
android
:
layout_height
=
"
wrap_content
"
android
:
text
=
"
"
>
<
/
TextView
>
<
Button
android
:
id
=
"
@+id/button1
"
android
:
layout_width
=
"
wrap_content
"
android
:
layout_height
=
"
wrap_content
"
android
:
onClick
=
"
startProgress
"
android
:
text
=
"
Start
Progress
"
>
<
/
Button
>
<
/
LinearLayout
>
Avec le code suivant, la ProgressBar est mise à jour dès que l'utilisateur appuie sur le Bouton :
package
de.vogella.android.handler;
import
android.app.Activity;
import
android.os.Bundle;
import
android.view.View;
import
android.widget.ProgressBar;
import
android.widget.TextView;
public
class
ProgressTestActivity extends
Activity {
private
ProgressBar progress;
private
TextView text;
@
Override
public
void
onCreate
(Bundle savedInstanceState) {
super
.onCreate
(savedInstanceState);
setContentView
(R.layout.main);
progress =
(ProgressBar) findViewById
(R.id.progressBar1);
text =
(TextView) findViewById
(R.id.textView1);
}
public
void
startProgress
(View view) {
//
faites
une
opération
longue
Runnable runnable =
new
Runnable
() {
@
Override
public
void
run
() {
for
(int
i =
0
; i <=
10
; i+
+
) {
final
int
value =
i;
doFakeWork
();
progress.post
(new
Runnable
() {
@
Override
public
void
run
() {
text.setText
("
Updating
"
);
progress.setProgress
(value);
}
}
);
}
}
}
;
new
Thread
(runnable).start
();
}
//
Simuler
une
opération
chronophage
private
void
doFakeWork
() {
try
{
Thread.sleep
(2000
);
}
catch
(InterruptedException e) {
e.printStackTrace
();
}
}
}
6. La classe AsynkTask▲
6-1. Le but de la classe AsyncTask▲
La classe AsyncTask encapsule la création d'un processus d'arrière-plan et la synchronisation avec le fil d'exécution principal. Elle prend également en compte l'état d'avancement des tâches en cours d'exécution.
6-2. L'utilisation de la classe AsyncTask▲
Pour utiliser AsyncTask, vous devez créer une classe dérivée. AsyncTask utilise les génériques et les varargs. Les paramètres sont AsyncTask<TypeOfVarArgParams, ProgressValue, ResultValue>.
Une AsyncTask est lancée par la méthode execute().
La méthode execute() appelle les méthodes doInBackground() et onPostExecute().
TypeOfVarArgParams est fourni en entrée avec la méthode doInBackground(), ProgressValue est utilisé pour les informations de progression et ResultValue doit être retourné par doInBackground() et passé en paramètre à onPostExecute().
La méthode doInBackground() contient le code de l'instruction qui devrait être accomplie dans un fil d'exécution d'arrière-plan. Cette méthode s'exécute automatiquement dans un fil séparé.
La méthode onPostExecute() se resynchronise avec le fil d'exécution de l'interface utilisateur et lui permet d'être mis à jour. Cette méthode est appelée par le framework une fois que la méthode doInBackground() prend fin.
6-3. L'exécution de plusieurs AsyncTask en parallèle▲
Android exécute des tâches AsyncTask avant Android 1.6 et encore à partir d'Android 3.0 dans la séquence par défaut.
Vous pouvez demander à Android de les exécuter en parallèle par l'utilisation de la méthode executeOnExecutor(), en spécifiant AsyncTask.THREAD_POOL_EXECUTOR comme premier paramètre.
L'extrait de code ci-après démontre cela :
//
ImageLoader
étend
AsyncTask
ImageLoader imageLoader =
new
ImageLoader
(imageView);
//
Exécution
in
parallèle
imageLoader.executeOnExecutor
(AsyncTask.THREAD_POOL_EXECUTOR, "
http://url.com/image.png
"
);
6-4. Les inconvénients de l'utilisation des tâches asynchrones▲
La tâche asynchrone ne gère pas automatiquement les modifications de configuration, c'est-à-dire si l'activité est recréée, le programmeur doit gérer cela dans son code.
Une solution commune à ce problème est de déclarer la tâche asynchrone dans un fragment conservé sans entête.
6-5. Tâche asynchrone : exemple▲
Le code suivant montre comment utiliser la classe AsyncTask pour télécharger le contenu d'une page Web.
Créez un nouveau projet Android appelé de.vogella.android.asynctask avec une activité appelée ReadWebpageAsyncTask. Ajoutez l'autorisation android.permission.INTERNET à votre fichier AndroidManifest.xml.
Créez la mise en page suivante :
<?
xml
version="1.0"
encoding="utf-8"?
>
<
LinearLayout
xmlns
:
android
=
"
http://schemas.android.com/apk/res/android
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
match_parent
"
android
:
orientation
=
"
vertical
"
>
<
Button
android
:
id
=
"
@+id/readWebpage
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
wrap_content
"
android
:
onClick
=
"
onClick
"
android
:
text
=
"
Load
Webpage
"
>
<
/
Button
>
<
TextView
android
:
id
=
"
@+id/TextView01
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
match_parent
"
android
:
text
=
"
Placeholder
"
>
<
/
TextView
>
<
/
LinearLayout
>
Modifiez votre activité comme suit :
package
de.vogella.android.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
de.vogella.android.asyntask.R;
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
onClick
(View view) {
DownloadWebPageTask task =
new
DownloadWebPageTask
();
task.execute
(new
String[] {
"
http://www.vogella.com
"
}
);
}
}
Si vous exécutez votre application et appuyez sur le bouton, le contenu de la page Web définie est lu en arrière-plan. Une fois ce processus terminé, votre TextView est actualisé.
7. Les processus d'arrière-plan et la gestion des cycles de vie▲
7-1. Conserver l'état pendant les changements de configuration▲
Un défi de l'utilisation des fils d'exécution est de prendre en compte le cycle de vie de l'application. Le système Android peut tuer votre activité ou déclencher un changement de configuration qui le redémarrera.
Vous devez également gérer les boîtes de dialogue ouvertes, car ces dernières sont toujours reliées à l'activité qui les a créées. Dans le cas où l'activité redémarre et vous accédez à un dialogue existant, vous recevez une exception Vue non attachée au gestionnaire de fenêtre(5).
Pour enregistrer un objet, vous pouvez utiliser la méthode onRetainNonConfigurationInstance(). Cette méthode vous permet d'enregistrer un objet pour le cas où l'activité sera bientôt relancée.
Pour récupérer cet objet, vous pouvez utiliser la méthode getLastNonConfigurationInstance(). Ainsi, vous pouvez enregistrer un objet, par exemple un fil d'exécution, même si l'activité est redémarrée.
getLastNonConfigurationInstance() retourne « null » si l'activité est lancée pour une première fois ou si elle a été terminée par la méthode finish().
onRetainNonConfigurationInstance() est obsolète depuis API 13, il est recommandé d'utiliser les fragments et la méthode setRetainInstance() afin de conserver les données sur les changements de configuration.
7-2. Utiliser l'objet application pour stocker des objets▲
Si plus d'un objet doit être stocké à travers des activités et des changements de configuration, vous pouvez implémenter une classe Application pour votre application Android.
Pour utiliser votre classe application, assignez le nom de la classe à l'attribut android:name de votre application.
<
application
android
:
icon
=
"
@drawable/icon
"
android
:
label
=
"
@string/app_name
"
android
:
name
=
"
MyApplicationClass
"
>
<
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
>
La classe application est créée automatiquement par l'environnement d'exécution Android et est disponible à moins que l'ensemble du processus de l'application soit terminé.
Cette classe peut être utilisée pour accéder à des objets qui devraient être des activités transversales ou disponibles pour l'ensemble du cycle de vie de l'application. Dans la méthode onCreate(), vous pouvez créer des objets et les rendre disponibles via les champs « public » ou des méthodes getter.
La méthode OnTerminate() de la classe application est utilisée seulement pour tester. Si Android met fin au processus dans lequel l'application s'exécute, toutes les ressources allouées sont libérées automatiquement.
Vous pouvez accéder à l'Application par la méthode getApplication() dans votre activité.
8. Fragments et processus d'arrière-plan▲
8-1. Conserver une instance lors des changements de configuration▲
Vous pouvez utiliser des fragments sans interface utilisateur et les conserver entre les changements de configuration par un appel à leur méthode setRetainInstance().
De cette façon, votre Thread ou AsyncTask est conservé lors des changements de configuration. Cela vous permet d'effectuer un traitement de fond sans tenir compte explicitement du cycle de vie de votre activité.
8-2. Fragments sans entête▲
Si vous effectuez un traitement de fond, vous pouvez attacher dynamiquement un fragment sans entête à votre application et appeler setRetainInstance() à true. Ce fragment est conservé lors des changements de configuration et vous pouvez y effectuer le traitement asynchrone.
9. En savoir plus sur les fragments▲
Vous pouvez utiliser ce tutoriel sur les fragments (Tutoriel Developpez « vogella - Utiliser les fragments sous Android ») pour apprendre à utiliser les fragments.
10. Chargeur▲
10-1. Le but de la classe Loader ▲
La classe Loader permet de charger des données de manière asynchrone dans une activité ou un fragment. Elle peut contrôler la source des données et fournir de nouveaux résultats au moment du changement de contenu. Elle persiste également les données entre les changements de configuration.
Si le résultat est récupéré par le chargeur après la déconnexion de l'objet de son parent (activité ou fragment), il peut mettre les données en cache.
Les chargeurs ont été introduits dans Android 3.0 et font partie de la couche de compatibilité pour les versions Android supérieures à 1.6.
10-2. Implémenter un Chargeur▲
Vous pouvez utiliser la classe abstraite AsyncTaskLoader comme base pour vos propres implémentations du chargeur.
Le LoaderManager d'une activité ou d'un fragment gère une ou plusieurs instances de chargeur. La création d'un chargeur se fait par l'appel de méthode suivant :
# démarrer un nouveau chargeur ou se reconnecter à un chargeur existant
getLoaderManager
().initLoader
(0
, null
, this
);
Le premier paramètre est un identifiant unique qui peut être utilisé par la classe de rappel pour identifier le Chargeur plus tard. Le deuxième paramètre est un ensemble qui peut être passé à la classe de rappel pour plus d'informations.
Le troisième paramètre de initLoader() est la classe qui est appelée une fois que l'initialisation a été lancée (classe de rappel). Cette classe doit implémenter l'interface LoaderManager.LoaderCallbacks. C'est une bonne pratique qu'une activité ou le fragment qui utilise un chargeur implémente l'interface LoaderManager.LoaderCallbacks.
Le chargeur n'est pas directement créé par l'appel de méthode getLoaderManager().InitLoader(), mais il doit être créé par la classe de rappel dans la méthode onCreateLoader().
Une fois que le chargeur a terminé la lecture asynchrone de données, la méthode onLoadFinished() de la classe de rappel est appelée. Ici, vous pouvez mettre à jour votre interface utilisateur.
10-3. Base de données SQLite et CursorLoader▲
Android fournit une implémentation de chargeur par défaut, la classe CursorLoader, pour gérer les connexions à la base de données SQLite.
Pour un ContentProvider basé sur une base de données SQLite, vous pouvez généralement utiliser la classe CursorLoader. Ce chargeur de curseur interroge la base de données dans un fil d'exécution d'arrière-plan afin que l'application ne soit pas bloquée.
La classe CursorLoader est la remplaçante des curseurs d'activité gérés (Activity-managed) qui sont maintenant obsolètes.
Si le Curseur devient invalide, la méthode onLoaderReset() est appelée sur la classe de rappel.
11. Exercice : Chargeur personnalisé pour préférences ▲
11-1. Implémentation▲
Dans le code suivant, vous créez une implémentation personnalisée de chargeur pour gérer les préférences. À chaque chargement, la valeur de la préférence est augmentée.
Créez un projet appelé com.vogella.android.loader.preferences avec une activité appelée MainActivity.
Créez la classe suivante comme implémentation personnalisée de AsyncTaskLoader pour la gestion des préférences partagées :
package
com.vogella.android.loader.preferences;
import
android.content.AsyncTaskLoader;
import
android.content.Context;
import
android.content.SharedPreferences;
import
android.preference.PreferenceManager;
public
class
SharedPreferencesLoader extends
AsyncTaskLoader<
SharedPreferences>
implements
SharedPreferences.OnSharedPreferenceChangeListener {
private
SharedPreferences prefs =
null
;
public
static
void
persist
(final
SharedPreferences.Editor editor) {
editor.apply
();
}
public
SharedPreferencesLoader
(Context context) {
super
(context);
}
//
Charge
les
données
de
façon
asynchrone
@
Override
public
SharedPreferences loadInBackground
() {
prefs =
PreferenceManager.getDefaultSharedPreferences
(getContext
());
prefs.registerOnSharedPreferenceChangeListener
(this
);
return
(prefs);
}
@
Override
public
void
onSharedPreferenceChanged
(SharedPreferences sharedPreferences, String key) {
//
notifier
le
chargeur
que
le
contenu
a
changé
onContentChanged
();
}
/**
*
starts
the
loading
of
the
data
*
once
result
is
ready
the
onLoadFinished
method
is
called
*
in
the
main
thread
.
It
loader
was
started
earlier
the
result
*
is
return
directly
*
method
must
be
called
from
main
thread
.
*/
@
Override
protected
void
onStartLoading
() {
if
(prefs !
=
null
) {
deliverResult
(prefs);
}
if
(takeContentChanged
() |
|
prefs =
=
null
) {
forceLoad
();
}
}
}
L'exemple de code suivant illustre l'utilisation de ce chargeur dans une activité :
package
com.vogella.android.loader.preferences;
import
android.annotation.SuppressLint;
import
android.app.Activity;
import
android.app.LoaderManager;
import
android.content.Loader;
import
android.content.SharedPreferences;
import
android.os.Bundle;
import
android.widget.TextView;
public
class
MainActivity extends
Activity implements
LoaderManager.LoaderCallbacks<
SharedPreferences>
{
private
static
final
String KEY =
"
prefs
"
;
private
TextView textView;
@
Override
public
void
onCreate
(Bundle savedInstanceState) {
super
.onCreate
(savedInstanceState);
setContentView
(R.layout.activity_main);
textView =
(TextView) findViewById
(R.id.prefs);
getLoaderManager
().initLoader
(0
, null
, this
);
}
@
Override
public
Loader<
SharedPreferences>
onCreateLoader
(int
id, Bundle args) {
return
(new
SharedPreferencesLoader
(this
));
}
@
SuppressLint
("
CommitPrefEdits
"
)
@
Override
public
void
onLoadFinished
(Loader<
SharedPreferences>
loader,
SharedPreferences prefs) {
int
value =
prefs.getInt
(KEY, 0
);
value +
=
1
;
textView.setText
(String.valueOf
(value));
//
actualiser
la
valeur
SharedPreferences.Editor editor =
prefs.edit
();
editor.putInt
(KEY, value);
SharedPreferencesLoader.persist
(editor);
}
@
Override
public
void
onLoaderReset
(Loader<
SharedPreferences>
loader) {
//
NON
utilisé
}
}
11-2. Test▲
LoaderManager appelle automatiquement onLoadFinished() dans votre activité après un changement de configuration. Exécutez l'application et assurez-vous que la valeur stockée dans les préférences partagées est augmentée à chaque changement de configuration.
12. L'utilisation des services▲
Vous pouvez également utiliser les services Android pour effectuer des tâches de fond. Voir le tutoriel les services sous Android pour plus de détails.
13. En apprendre plus sur les services▲
Vous pouvez utiliser le tutoriel les services sous Android pour apprendre à utiliser les services.
14. Exercice : le cycle de vie de l'activité et les fils d'exécution▲
L'exemple suivant téléchargera une image à partir d'Internet dans un fil d'exécution et affichera une boîte de dialogue jusqu'à ce que le téléchargement soit terminé. Nous veillerons à ce que le fil d'exécution soit conservé, même si l'activité redémarre et que la boîte de dialogue s'affiche et se ferme correctement.
Pour cet exemple, créez un nouveau projet Android appelé de.vogella.android.threadslifecycle avec l'activité appelée ThreadsLifecycleActivity. Ajoutez également l'autorisation d'utiliser l'Internet à votre fichier AndroidManifest.xml.
Votre fichier AndroidManifest.xml devrait ressembler à ce qui suit :
<?
xml
version="1.0"
encoding="utf-8"?
>
<
manifest
xmlns
:
android
=
"
http://schemas.android.com/apk/res/android
"
package
=
"
de.vogella.android.threadslifecycle
"
android
:
versionCode
=
"
1
"
android
:
versionName
=
"
1.0
"
>
<
uses-sdk
android
:
minSdkVersion
=
"
10
"
/
>
<
uses-permission
android
:
name
=
"
android.permission.INTERNET
"
>
<
/
uses-permission
>
<
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
>
Changez la mise en page main.xml comme suit :
<?
xml
version="1.0"
encoding="utf-8"?
>
<
LinearLayout
xmlns
:
android
=
"
http://schemas.android.com/apk/res/android
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
match_parent
"
android
:
orientation
=
"
vertical
"
>
<
LinearLayout
android
:
id
=
"
@+id/linearLayout1
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
wrap_content
"
>
<
Button
android
:
layout_width
=
"
wrap_content
"
android
:
layout_height
=
"
wrap_content
"
android
:
onClick
=
"
downloadPicture
"
android
:
text
=
"
Click
to
start
download
"
>
<
/
Button
>
<
Button
android
:
layout_width
=
"
wrap_content
"
android
:
layout_height
=
"
wrap_content
"
android
:
onClick
=
"
resetPicture
"
android
:
text
=
"
Reset
Picture
"
>
<
/
Button
>
<
/
LinearLayout
>
<
ImageView
android
:
id
=
"
@+id/imageView1
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
match_parent
"
android
:
src
=
"
@drawable/icon
"
>
<
/
ImageView
>
<
/
LinearLayout
>
Maintenant, adaptez votre activité. Dans cette dernière, le fil d'exécution est enregistré et la boîte de dialogue fermée si l'activité est détruite.
package
de.vogella.android.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.client.methods.HttpUriRequest;
import
org.apache.http.impl.client.DefaultHttpClient;
import
org.apache.http.util.EntityUtils;
import
android.app.Activity;
import
android.app.ProgressDialog;
import
android.content.Context;
import
android.graphics.Bitmap;
import
android.graphics.BitmapFactory;
import
android.os.Bundle;
import
android.os.Handler;
import
android.os.Message;
import
android.view.View;
import
android.widget.ImageView;
public
class
ThreadsLifecycleActivity extends
Activity {
//
Statique
pour
que
le
fil
accède
au
dernier
attribut
private
static
ProgressDialog dialog;
private
static
Bitmap downloadBitmap;
private
static
Handler handler;
private
ImageView imageView;
private
Thread downloadThread;
/**
Appelée
quand
l
'
activité
est
créée
.
*/
@
Override
public
void
onCreate
(Bundle savedInstanceState) {
super
.onCreate
(savedInstanceState);
setContentView
(R.layout.main);
//
créer
un
gestionnaire
pour
actualiser
l'IU
handler =
new
Handler
() {
@
Override
public
void
handleMessage
(Message msg) {
imageView.setImageBitmap
(downloadBitmap);
dialog.dismiss
();
}
}
;
//
obtenir
la
dernière
imageView
après
le
redémarrage
de
l'application
imageView =
(ImageView) findViewById
(R.id.imageView1);
Context context =
imageView.getContext
();
System.out.println
(context);
//
Avons-nous
déjà
téléchargé
l'image ?
if
(downloadBitmap !
=
null
) {
imageView.setImageBitmap
(downloadBitmap);
}
//
vérifier
si
le
thread
est
déjà
en
cours
d'exécution
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
();
}
//
conserver
le
thread
@
Override
public
Object onRetainNonConfigurationInstance
() {
return
downloadThread;
}
//
dissimuler
la
boîte
de
dialogue
si
l'activité
est
détruite
@
Override
protected
void
onDestroy
() {
if
(dialog !
=
null
&
&
dialog.isShowing
()) {
dialog.dismiss
();
dialog =
null
;
}
super
.onDestroy
();
}
//
Méthode
utilitaire
pour
télécharger
l'image
depuis
Internet
static
private
Bitmap downloadBitmap
(String url) throws
IOException {
HttpUriRequest request =
new
HttpGet
(url);
HttpClient httpClient =
new
DefaultHttpClient
();
HttpResponse response =
httpClient.execute
(request);
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
public
class
MyThread extends
Thread {
@
Override
public
void
run
() {
try
{
//
Simuler
une
connexion
lente
try
{
new
Thread
().sleep
(5000
);
}
catch
(InterruptedException e) {
e.printStackTrace
();
}
downloadBitmap =
downloadBitmap
("
http://www.devoxx.com/download/attachments/4751369/DV11
"
);
//
Mettre
à
jour
l'interface
utilisateur
handler.sendEmptyMessage
(0
);
}
catch
(IOException e) {
e.printStackTrace
();
}
finally
{
}
}
}
}
Exécutez votre application et appuyez sur le bouton pour démarrer un téléchargement. Vous pouvez tester le comportement correct du cycle de vie en changeant l'orientation dans l'émulateur via le raccourci Ctrl + F11.
Il faut aussi noter que Thread est une classe interne statique. Il est important d'utiliser une classe interne statique pour votre processus d'arrière-plan, car sinon la classe interne contiendrait une référence vers la classe dans laquelle elle a été créée. Comme le fil d'exécution est passé à la nouvelle instance de votre activité, cela créerait une fuite de mémoire si l'ancienne activité était encore référencée par le Thread.
15. Optimisations de mémoire▲
15-1. L'utilisation des caches▲
Une autre façon d'éviter une mauvaise performance est de mettre en cache les objets coûteux. Par exemple, si vous téléchargez des images à partir d'Internet pour les afficher dans un ListView, vous devez les garder dans un cache pour éviter de les télécharger plusieurs fois.
Un cache « Moins Utilisé Récemment »(6) garde des traces de l'usage de son contenu. Il a une taille donnée et si cette taille est dépassée, il supprime les éléments qui n'ont pas été utilisés depuis longtemps. Ce comportement est illustré dans l'image ci-dessous :
La plate-forme Android fournit la classe LruCache à partir de l'API 12 (ou dans la bibliothèque support-v4). La classe LruCache fournit une implémentation de cache Least Recently Used.
L'exemple de code suivant illustre une implémentation possible de la classe LruCache pour la mise en cache des images :
public
class
ImageCache extends
LruCache<
String, Bitmap>
{
public
ImageCache
(int
maxSize) {
super
(maxSize);
}
@
Override
protected
int
sizeOf
(String key, Bitmap value) {
return
value.getByteCount
();
}
@
Override
protected
void
entryRemoved
(boolean
evicted, String key, Bitmap oldValue, Bitmap newValue) {
oldValue.recycle
();
Pour déterminer la taille initiale de la mémoire cache, vous pouvez utiliser MemoryClass et utiliser une fraction de la quantité totale de mémoire disponible comme le montre le code suivant :
int
memClass =
((ActivityManager)activity.getSystemService
(Context.ACTIVITY_SERVICE)).getMemoryClass
();
int
cacheSize =
1024
*
1024
*
memClass /
8
;
LruCache cache =
new
LruCache<
String, Bitmap>
(cacheSize);
15-2. L'utilisation des structures de mémoire efficaces ▲
Android fournit des structures de données qui sont plus efficaces pour le mappage de valeurs vers d'autres objets. Utilisez ces objets si possible, ils évitent la création des objets comme dans le cas de l'utilisation de HashMap. La création d'objets peut être coûteuse et doit être évitée afin de réduire le nombre de fois où le garbage collector doit s'exécuter.
Le tableau 1 donne des exemples pour SparseArrays.
Tableau 1. Des structures de mémoire efficaces
Structure de mémoire | Description |
SparseArray<E> | Effectue le mappage des entiers vers des Objets, évite la création des objets Integer. |
SparseBooleanArray | Effectue le mappage des entiers vers des booléens. |
SparseIntArray | Effectue le mappage des entiers vers des entiers. |
15-3. Nettoyer l'utilisation mémoire▲
Depuis l'API 14, vous pouvez substituer la méthode onTrimMemory() dans les composants Android. Cette méthode est appelée par le système Android et vous demande de nettoyer la mémoire au cas où le système Android nécessite des ressources pour les processus de premier plan.
16. Le mode strict▲
Android vous permet de donner au système l'instruction de signaler tout processus de longue durée effectué dans le fil d'exécution de l'interface utilisateur. Voir l'utilisation de StrictMode sous Android pour plus de détails.
17. Retour d'information par boîte de dialogue▲
Si vous effectuez une opération de longue durée, c'est une bonne pratique de fournir à l'utilisateur des informations en retour sur l'opération en cours d'exécution.
Vous pouvez fournir des informations sur la progression via la barre d'action, par exemple, par l'intermédiaire d'une vue d'action. Sinon, vous pouvez utiliser dans votre mise en page, une barre de progression ProgressBar que vous réglez sur visible et mettez à jour au cours d'une opération de longue durée. Cette approche est appelée fournir un retour d'information en ligne(7)NDT : providing inline feedback en anglais dans le texte original., vu qu'elle laisse l'interface utilisateur réactive.
Pour bloquer l'interface utilisateur pendant l'opération, vous pouvez utiliser la boîte de dialogue ProgressBar qui permet d'afficher la progression. La Javadoc de ProgressBar donne un bel exemple de son utilisation.
Si possible, évitez d'utiliser la boîte de dialogue ProgressBar ou des approches similaires. Privilégiez la fourniture d'un retour d'information en ligne pour que votre interface utilisateur reste réactive.
18. Soutenez le site Web vogella.com▲
Ce tutoriel représente du contenu libre (Open Content) sous licence CC BY-NC-SA 3.0 DE. Le code source de ce tutoriel est distribué sous la licence publique Eclipse. Voir la page de la licence vogella pour plus de détails sur les conditions de réutilisation.
La rédaction et la mise à jour de ces tutoriels nécessitent beaucoup de travail. Si ce service gratuit vous a été utile, vous pouvez soutenir la cause en faisant un don ainsi qu'en signalant les fautes de frappe et de contenu.
18-1. Remerciement▲
Si cet article vous a été utile, vous pouvez faire un don à Lars Vogel sur la page de l'article original.
18-2. Questions et discussions▲
Si vous trouvez des erreurs dans ce tutoriel, s'il vous plaît informez-moi (voir en haut de la page). Veuillez noter qu'en raison du volume élevé de commentaires que je reçois, je ne peux pas répondre à des questions concernant votre application. Assurez-vous d'avoir lu la FAQ vogella, peut-être que les réponses s'y trouvent déjà.
19. Liens et littérature▲
19-1. Code source▲
19-2. Ressources concernant l'accès concurrentiel▲
19-3. Ressources Android▲
19-4. Ressources vogella▲
- Vogella Training : cours Android et Eclipse de l'équipe vogella.
- Tutoriel Android : introduction à la programmation Android.
- Tutoriel GWT : programmation en Java et compilation en JavaScript et HTML.
- Tutoriel Eclipse RCP : création d'applications natives en Java.
- Tutoriel JUnit : testez votre application.
- Tutoriel Git : mettez vos fichiers dans un système distribué de gestion de versions.
20. Remerciements Developpez▲
Vous pouvez retrouver l'article original à l'adresse Android Background Processing with Handlers and AsyncTask and Loaders - Tutorial. Nous remercions Lars Vogel qui nous a aimablement autorisé à traduire et héberger ses articles.
Nous remercions aussi Mishulyna pour sa traduction, ainsi que milkoseck pour sa relecture orthographique.
N'hésitez pas à commenter cet article ! Commentez