Tutoriel Android : Apprendre à utiliser les services Android

Chapitre 6

Android2ee

Mathias Seguy

Ce tutoriel est un document qui se charge de vous apprendre à créer et à utiliser les services Android.

- Chapitre 1.1 : Cycle de vie de l'activité

- Chapitre 2.1 : Les éléments graphiques fondamentaux d'Android

- Chapitre 2.2 : Composants graphiques complexes sous Android

- Chapitre 3.3 : Intent et IntentFilters

- Chapitre 4.1 : Les Ressources

- Chapitre 4.2 : SharedPreferences et PreferenceActivity

- Chapitre 4.3 : Base de données SQLite

- Chapitre 4.4 : Gestion des fichiers

- Chapitre 5 : Content Provider

- Chapitre 6 : Les Services

Pour réagir à ce tutoriel, un espace de dialogue vous est proposé sur le forum : Commentez

- Titre : Tutoriel Android : Apprendre à utiliser les services Android

- Auteur : Mathias Seguy

- Parution : 7 novembre 2016

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Mathias Seguy. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Mon application est un service

- I-A. Utilisation d'un service

- I-B. Création d'un service

- I-C. Que s'est-il passé ?

- II. Pour aller plus loin

- II-A. Service et cycle de vie

- II-A-1. Le cycle de vie

- II-A-2. La condition d'arrêt

- II-A-3. L'exemple du lecteur de musique

- II-B. Notion fondamentale sur les services

- II-B-1. Qu'est-ce qu'un service (Chet Haase définition)

- II-B-2. Service métier et service Android

- II-B-3. Les notifications et service de foreground

- II-B-4. Les IntentService

- III. Le fichier manifest.xml

- III-A. Ajouter votre service à votre manifeste

- III-B. Généralités concernant le manifeste

- IV. Remerciements

I - Mon application est un service

Un service, c'est un démon, une thread qui tourne sans IHM, bref un service. Un service est un singleton ou presque. En effet, si plusieurs processus accèdent au service, il n'y aura qu'un service. Mais si le service n'a plus d'utilisateur alors le singleton est détruit et le prochain utilisateur recrée un autre singleton. Cela paraît anodin, mais si votre service lance une thread puis est détruit et recréé alors il lancera une nouvelle thread, oubliant la précédente…

L'exemple typique d'un service est votre lecteur de musique. Quand le lecteur n'est plus au premier plan, la musique continue à être jouée, c'est un service. Si ce n'était qu'une activité dès que l'activité aurait quitté le premier plan, la musique se serait arrêtée.

Un service se lance soit manuellement par l'utilisateur, soit par un appel d'un autre processus. Puis, il reste en vie jusqu'à ce qu'il ne soit plus nécessaire. De ce fait, il doit être très peu gourmand en CPU (au risque de vider la batterie trop vite) et en mémoire (au risque de saturer l'appareil trop vite).

Je ne parlerai pas d'un service lancé dans un autre processus, sachez juste qu'il faut utiliser l'AIDL (Android Interface Definition Language) pour communiquer avec lui.

Soit votre service est interne à votre application et n'a pas vocation à être démarré par le système, soit il a pour vocation d'être démarré par système. Dans le premier cas (service instanciable par le système), la balise enable est à true dans votre manifest.xml.

De plus, soit vous souhaitez démarrer votre service et lui laisser vivre sa vie en communiquant avec lui via des Intents, soit vous souhaitez posséder un pointeur vers lui et donc pourvoir appeler ses méthodes directement. Le premier cas est le démarrage en mode Start : startService(new Intent(this, MyService.class));. Où this est votre contexte (votre activité).

Le second cas est exposé dans cet article.

I-A - Utilisation d'un service

Il faut implémenter sept choses dans votre Activity pour pouvoir utiliser un service :

- un objet Service qui n'est autre qu'un pointeur vers le service que vous souhaitez utiliser ;

- un objet ServiceConnection dont le but est de gérer la connexion et la déconnexion au service ;

- un objet BroadCastReceiver pour pouvoir écouter les modifications envoyées par le service ;

- un appel à la méthode bindService(service, serviceConnection, flags) dans votre méthode onCreate ;

- un appel à la méthode unbindService(serviceConnection) dans votre méthode onDestroy ;

- un appel à la méthode registerReceiver(broadcastReceiver, filter) dans votre méthode onResume pour pouvoir écouter les intentions envoyées par le service. Ce mécanisme permet au service d'informer l'activité d'un changement ;

- un appel à la méthode unregisterReceiver(broadcastReceiver) dans votre méthode onPause pour stopper l'écoute des modifications envoyées par le service.

Ce qui donne l'exemple élémentaire suivant :

1. public class MyServiceUser extends Activity {

2. /**

3. * The service that can be used as a normal object You can call its public methods

4. */

5. private MyService appService = null;

6. /**

7. * The broadcast receiver that aims to listen to change broadcast by the service

8. */

9. private BroadcastReceiver receiver = new BroadcastReceiver() {

10. public void onReceive(Context context, Intent intent) {

11. // Do something, call a method, you already know your service

12. // you can use its methods directly in your code

13. }

14. };

16. /**

17. * The service connection, that aims to connect to the service

18. */

19. private ServiceConnection onService = new ServiceConnection() {

20. /* (non-Javadoc)

21. * @see android.content.ServiceConnection#onServiceConnected(android.content.ComponentName,* android.os.Ibinder)

22. */

23. public void onServiceConnected(ComponentName className, IBinder rawBinder) {

24. appService = ((MyService.LocalBinder) rawBinder).getService();

25. }

26. /* (non-Javadoc)

27. * @see

28. * android.content.ServiceConnection#onServiceDisconnected(android.content.ComponentName)

29. */

30. public void onServiceDisconnected(ComponentName className) {

31. appService = null;

32. }

33. };

35. @Override

36. public void onCreate(Bundle savedInstanceState) {

37. super.onCreate(savedInstanceState);

38. setContentView(R.layout.main);

39. ...

40. //Bind your service to your activity.

41. //The parameters are: The intent to talk to the service, the service connection

42. //to use and a flag to indicates if the service has to automaticly start or not

43. //You should use BIND_AUTO_CREATE

44. bindService(new Intent(this, MyService.class),onService, Context.BIND_AUTO_CREATE);

45. }

47. @Override

48. public void onResume() {

49. super.onResume();

50. //Register your activity to listen for change send by the service

51. //You can here see that the service has declared a static public String BROADCAST_ACTION

52. //that is used to identify the Intent with unicity

53. registerReceiver(receiver, new IntentFilter(MyService.BROADCAST_ACTION));

54. }

56. @Override

57. public void onPause() {

58. super.onPause();

59. //Unregister your activity to listen for change send by the service

60. unregisterReceiver(receiver);

61. }

63. @Override

64. public void onDestroy() {

65. super.onDestroy();

66. //Unbind your service (else your activity will be destroyed but your service

67. //will still think that somebody use it

68. unbindService(onService);

69. }

71. }

I-B - Création d'un service

Lors de la création d'un service, gardez en tête que celui-ci s'exécute par défaut dans la thread principale de votre application. C'est à vous de faire en sorte de créer une thread pour que votre service l'utilise et ne court-circuite pas votre activité principale.

Pour créer un service, vous devez implémenter :

- la méthode onCreate() qui a à charge l'initialisation ;

- la méthode onDestroy qui a à charge la destruction de votre service, une classe privée qui étend Binder ;

- la méthode onBind qui renvoie votre classe privée qui étend Binder ;

- un attribut public static final qui n'est autre que votre identifiant d'intention ;

- la classe de traitement dans une thread parallèle pour effectuer les actions de votre service. Cette classe étend AsyncTask. Dans mon exemple, je préconise l'utilisation de Thread directement, voire mieux, d'un ExecutorService (pool de Thread).

1. /**

2. * @author mathias1

3. * @goals This class aims to show a service's skeleton

4. */

5. public class MyService extends Service {

6. /*****************************************************************************************/

7. /** Managing initialisation and death ****************************************************/

8. /*****************************************************************************************/

10. /*

11. * (non-Javadoc)

12. *

13. * @see android.app.Service#onCreate()

14. */

15. @Override

16. public void onCreate() {

17. super.onCreate();

18. //initialize your service here

19. }

21. /*

22. * (non-Javadoc)

23. *

24. * @see android.app.Service#onDestroy()

25. */

26. @Override

27. public void onDestroy() {

28. super.onDestroy();

29. //destroy your service here

30. }

32. /****************************************************************************************/

33. /** Managing the binder to this service *************************************************/

34. /****************************************************************************************/

35. /**

36. * The binder that glue to my service

37. */

38. private final Binder binder=new LocalBinder();

39. /**

40. * @author mSeguy

41. * @goals This class aims to define the binder to use for my service

42. */

43. public class LocalBinder extends Binder {

44. /**

45. * @return the service you want to bind to : i.e. this

46. */

47. MyService getService() {

48. return (MyService.this);

49. }

50. }

51. /* (non-Javadoc)

52. * @see android.app.Service#onBind(android.content.Intent)

53. */

54. @Override

55. public IBinder onBind(Intent intent) {

56. return binder;

57. }

59. /*****************************************************************************************/

60. /** Managing the Communication between the service and its listeners *********************/

61. /*****************************************************************************************/

62. /**

63. * The key of the Intent to communicate with the service's users

64. */

65. public static final String MY_SERVICE_INTENT = "stinfoservices.net.android.MyUniqueItentServiceKey";

66. /**

67. * The intent itself

68. */

69. private Intent broadcast = new Intent(MY_SERVICE_INTENT);

71. /**

72. * @author mSeguy

73. * @goals

74. * This class aims to make a task in a separeted thread

75. */

76. class MyTaskInAnOtherThread extends AsyncTask<Location, Void, Void> {

77. @Override

78. protected Void doInBackground(Location... locs) {

79. // Do something to update your data

80. // here is the place where your treatment is done in another thread than in the GUI

81. // thread

83. // Prevent your listener that something happens

84. sendBroadcast(broadcast);

85. return (null);

86. }

88. @Override

89. protected void onProgressUpdate(Void... unused) {

90. // not needed here

91. }

93. @Override

94. protected void onPostExecute(Void unused) {

95. // not needed here

96. }

97. }

98. }

I-C - Que s'est-il passé ?

Lors de la demande de démarrage de votre service dans votre activité via bindService(new Intent(this, MyService.class),onService, Context.BIND_AUTO_CREATE);, vous avez demandé au système de démarrer le service MyService.class.

Le système démarre donc le service puis appelle la méthode onBind sur celui-ci. Cette méthode ne fait que renvoyer le localBinder du service :

public class LocalBinder extends Binder {
MyService getService() {
return (MyService.this);
}
}

Et celui-ci est renvoyé par le système à votre activité en utilisant le deuxième paramètre que vous lui avez fourni, votre ServiceConnection. Ainsi vous retrouvez, dans votre méthode onServiceConnected, le LocalBinder que vous avez créé dans votre service.

Ce localBinder possède la méthode getService() qui renvoie un pointeur vers l'instance de sa classe englobante via MyService.this.

Il ne vous reste plus qu'à utiliser ce pointeur qui est l'adresse mémoire de l'instance de votre service MyService.

II - Pour aller plus loin

II-A - Service et cycle de vie

II-A-1 - Le cycle de vie

Comme tous les objets système natifs sur Android (Activity, Service, Application, ContentProvider), les services possèdent un cycle de vie. Ce cycle de vie est plus complexe que les autres, car les Services possèdent deux conditions de démarrage (le mode Start et le mode Bind). Ainsi, en fonction de ce mode de démarrage vous empruntez l'un ou l'autre de ces cycles de vie.

Personnellement, quand un de mes services doit démarrer en mode start et en mode bind, je centralise la méthode de traitement dans une méthode que je nomme onResume() et que j'appelle en fin de onBind() et en fin de onStartCommand().

II-A-2 - La condition d'arrêt

Quand on regarde ces deux cycles de vie, l'on se dit naïvement qu'ils sont dissociés l'un de l'autre, ce qui est absolument faux.

En fait, pour simplifier, onStart positionne un booléen (started) qui passe à true quand le service est démarré via le mode Start, et à false quand soit onStop soit stopSelf est appelé sur le service.

Toujours pour simplifier, la méthode onBind incrémente un compteur (clientCount) et la méthode onUnbind le décrémente.

La condition d'arrêt est clientCount==0 ET started==false. Ce qui fait que ces deux cycles de vie sont intimement entremêlés. Ce qui est résumé dans le diagramme officiel suivant :

II-A-3 - L'exemple du lecteur de musique

Ainsi, une question toute bête est de savoir comment on démarre un service pour diffuser la musique.

- Nous souhaitons pouvoir communiquer avec le service directement à partir de l'activité (pour ne pas avoir à mettre un pont de communication entre l'Activité et le Service basé sur l'envoi et la réception d'Intents). L'idée est que l'activité puisse appeler avant/après/lis tel morceau/pause/stop directement sur le service.

- Nous devons appeler onUnbind quand l'Activité passe dans onStop.

- Nous devons permettre à l'utilisateur de quitter l'activité pour aller lire ces mails tout en continuant à diffuser la musique.

- Nous devons être en capacité d'arrêter le service via le clic sur un bouton dédié.

Si, dans l'activité, dans la méthode onStart(), on démarre le service en mode Start (via startService). Cette méthode ne nous donne pas de pointeur vers le Service et donc on a raté notre premier point.

Si, dans l'activité, dans la méthode onStart(), on démarre le service via bindService, on récupère un pointeur vers le service, on est content. Mais dans la méthode onStop() de l'activité, on doit appeler unbindService, ce qui arrête le service et dans ce cas, l'utilisateur ne peut pas écouter sa musique en lisant ses mails.

Par contre, si, dans l'activité, dans la méthode onStart(), on démarre le service en appelant startService ET bindService, le service démarre dans ces deux modes. On a démarré le service en mode bind, ainsi l'activité possède un pointeur vers lui. Quand on passe dans la méthode onStop() de l'activité, on appelle unbindService, on perd le pointeur mais le service ne s'arrête pas, car il a été aussi démarré en mode Start. L'utilisateur peut ainsi lire ses mails et écouter la musique.

Pour arrêter le service, il suffit alors de mettre un bouton dans l'activité (genre le carré usuel d'un bouton stop) qui appelle stopService (et aussi, il faut dire au service d'arrêter de diffuser la musique). Le service s'arrêtera réellement quand l'activité passera dans onStop et appellera la méthode unbindService(). Ce n'est qu'à ce moment précis que la condition d'arrêt du service (clientCount==0 Et started==false) sera remplie.

II-B - Notion fondamentale sur les services

II-B-1 - Qu'est-ce qu'un service (Chet Haase définition)

Si vous vous intéressez un tant soit peu à Android, vous savez que Chet Haase a publié des articles fondamentaux sur Medium (https://medium.com/@chethaase) intitulés « Developing for Android ». Dans l'un de ces articles, il donne la définition suivante pour un service :

« Service (la classe Android) : un service permet à une application d'indiquer au système d'exploitation qu'il a besoin d'effectuer une opération longue en dehors du cycle UI normal de l'Activité. Il peut être auto-exécutable (par Context.startService ()), ou en cours d'exécution au nom d'un autre processus (par Context.bindService ()). Si vous n'avez pas besoin de l'un de ces comportements, vous ne devriez pas utiliser un service. »

Ainsi, Chet Haase, vous dit simplement que le système considère qu'un service est une activité sans IHM, c'est-à-dire un truc qui est aussi important pour votre utilisateur qu'une IHM en termes d'interaction avec lui, mais qui ne possède pas d'écran. C'est pour ça que le lecteur de musique est l'exemple parfait pour un service.

Voilà, tout est dit, avez-vous vraiment besoin d'un Service pour effectuer vos traitements ?

II-B-2 - Service métier et service Android

Eh oui, c'est la question de fond, mes services métiers sont-ils des services Android ? Et inversement mes services Android sont-ils des services métiers ?

Pour creuser le sujet, vous pouvez aller visionner ma conférence Devoxx France 2016, où je vous en parle longuement.

Animation en format "youtube" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://www.youtube.com/watch?v=8YgAz4mkGuQ

Mais pour faire succinct, si vous ne souhaitez pas que le système démarre votre service et que votre service n'interagit pas fortement avec votre utilisateur, alors vous n'avez pas besoin d'étendre Service pour implémenter votre service métier. Il vous suffit de faire, comme côté serveur, une classe toute simple (qui étend Object) et d'assurer l'unicité de son instance (par exemple via le design pattern du singleton).

II-B-3 - Les notifications et service de foreground

Que pensez-vous d'une application qui démarre un Service (et donc un élément qui consomme votre batterie et squatte votre RAM) sans vous le dire et qui ne vous permet pas de l'arrêter ?

Ainsi, il est assez naturel d'associer à un Service en cours d'exécution, une notification qui permet à votre utilisateur d'arrêter ledit service.

En fait, c'est déjà pensé par le système, vous avez la notion de ForegroundService qui est un service qui ne doit pas mourir même en cas de pénurie mémoire. C'est-à-dire qu'il est considéré par le système comme ultra important pour votre utilisateur. Eh bien dans ce cas, il est obligatoire d'associer une notification à votre service.

La bonne pratique souhaite simplement que dès que vous exécutez un service, même pour les services qui ne sont pas foreground, vous laissiez à votre utilisateur la possibilité de l'arrêter lui-même en affichant une notification (quand votre activité n'est plus visible par votre utilisateur).

II-B-4 - Les IntentService

Si vous êtes en train de mettre en place des Services dans votre application, relisez l'article sur les IntentService (http://mathias-seguy.developpez.com/tutoriels/android/gestion-requetes-asynchrones-intentservice/) écrit par Florian Fournier.

Ce sont des objets qui sont des services simplifiés pour la gestion de requêtes asynchrones.

Bref, ne vous battez pas avec un service multithreadé que vous coderiez à la main, utilisez un IntentService, c'est fait pour.

III - Le fichier manifest.xml

III-A - Ajouter votre service à votre manifeste

Pour que votre service soit connu par le système, il faut le rajouter à votre manifeste :

1. <manifest ... >

2. ...

3. <application ... >

4. <service android:name=".MyService" />

5. ...

6. </application>

7. </manifest>

III-B - Généralités concernant le manifeste

Le fichier manifest.xml est décrit de manière complète à la page :

http://developer.android.com/guide/topics/manifest/manifest-intro.html.

Ce fichier est le centre névralgique de votre application, et ce, pour plusieurs raisons :

- d'une part, il décrit les besoins de votre application, en termes de SDK, de compatibilité matérielle, d'API utilisées et d'utilisation de services du système ;

- d'autre part, il décrit ce qu'offre votre application au système (Activity, ContentProvider, Service…) ;

- enfin, il décrit les éléments auxquels votre application réagit au moyen des IntentFilters et d'URI ainsi que les permissions nécessaires pour les utiliser.

Ce fichier est analysé par les markets pour cibler les appareils qui sont éligibles pour le téléchargement de votre application. Il faut donc être très attentif à sa mise en place.

Pour décrire ces éléments, ce fichier possède la structure globale suivante :

1. <manifest>

3. <uses-permission />

4. <permission />

5. <permission-tree />

6. <permission-group />

7. <instrumentation />

8. <uses-sdk />

9. <uses-configuration />

10. <uses-feature />

11. <supports-screens />

12. <compatible-screens />

13. <supports-gl-texture />

15. <application>

17. <activity>

18. <intent-filter>

19. <action />

20. <category />

21. <data />

22. </intent-filter>

23. <meta-data />

24. </activity>

26. <activity-alias>

27. <intent-filter> . . . </intent-filter>

28. <meta-data />

29. </activity-alias>

31. <service>

32. <intent-filter> . . . </intent-filter>

33. <meta-data/>

34. </service>

36. <receiver>

37. <intent-filter> . . . </intent-filter>

38. <meta-data />

39. </receiver>

41. <provider>

42. <grant-uri-permission />

43. <meta-data />

44. </provider>

46. <uses-library />

48. </application>

50. </manifest>

N'hésitez pas à assouvir votre curiosité en allant sur la page précédemment citée pour connaître chacune de ces balises.

IV - Remerciements

Cet article a été publié avec l'aimable autorisation de la société Android2EE.

J'adresse ici tous mes remerciements à Claude Leloup pour ses corrections orthographiques ainsi que Laethy pour la mise au gabarit du document.