Chapitre 4 Le modèle de composants : les services 1
Les services "A Service is an application component that can perform long-running operations in the background and does not provide a user interface." Un service continue son exécution même si l'utilisateur change d'application Il peut être lié à plusieurs applications Il y a deux types de service : "started" (= démarré ou unbounded = non lié), lancés par startservice(). Un tel service, après avoir été lancé, est exécuté indéfiniment tant qu'il n'a pas fini son travail (même si le composant qui l'a lancé est détruit) "bound" (= liés), lancés par bindservice(). Un tel service interagit avec d'autres composants. Il est détruit lorsque tous les composants qui lui sont associés ont annulé leur association avec lui sauf s'il est aussi started donc... Un service peut à la fois être démarré par le composant d'une application est lié à d'autres source : 2 http://developer.android.com/guide/components/services.h tml
Service vs. thread Un service est en arrière plan des actions utilisateur, pas des threads (*). C'est à dire qu'il n'y a pas de création de thread, ni de processus lorsqu'un service est lancé et il s'exécute dans la thread qui lui a donné naissance On doit donc créer une thread pour ne pas être bloquant Et donc à quoi sert un service?! Avoir un service plutôt qu'une thread dans une application augmente la priorité de l'application et a donc ainsi moins de chance d'être détruite par l'environnement. Voir à http://developer.android.com/guide/components/processesand-threads.html On peut lier un service à d'autres composants de l'application, voire à d'autres applications : cf. la suite de cet exposé Il est géré par l'environnement d'exécution et par exemple, en cas d'arrêt de l'application, si le service est demandé, il peut être relancé automatiquement par l'environnement d'exécution (*) http://stackoverflow.com/questions/3520719/thread-orservices 3
Cycle de vie d'un service L'automate d'états-transitions d'un service est linéaire donc simple! Il y a évidemment deux sortes d'automates : un pour les services non liés, l'autre pour les services liés Les méthodes onstartcommand(), onbind(), onunbind() ne sont appelées que dans un des cas Les méthodes oncreate(), ondestroy() sont toujours appelées 4
Création d'un service : précisions Les méthodes startservice() et bindservice() sont des méthodes de la classe (abstraite) Context qu'on trouve donc dans les classes dérivées Service, Activity pas ContentProvider ni BroadcastReceiver startservice(), signé public abstract ComponentName startservice (Intent is) permet de lancer un service "started" et de le créer si ce n'est déjà fait. La méthode onstartcommand() du service est chaque fois exécutée mais la méthode oncreate() du service n'est exécutée qu'une seule fois à la création du service bindservice(), signée public abstract boolean bindservice (Intent is, ServiceConnection conn, int flags) permet de lier l'application au service et de le créer si ce n'est déjà fait Remarque : startservice() a un seul argument (l'intent) alors que bindservice() en a 3 (dont l'intent) 5
Arrêter un service Un service non lié (i.e. lancé par startservice()) peut être arrêté : si le service lui-même lance sa méthode stopself() en lançant la méthode stopservice(intent IDuService) de la classe Context sur ce service. IDuService est un Intent décrivant le service à arrêter. Si ce service est lié à aucun autre composant, il sera arrêté quel que soit le nombre d'appels startservice() qui ont été exécutés le service a fini son travail Un service lié (i.e. lancé par bindservice()) est exécuté tant qu'au moins un composant avec lequel il est lié est vivant (= non détruit). Ce service ne peut être arrêté que lorsque tous les composants liés à lui sont détruits 6
Déclarer un service Un service doit être déclaré dans l'androidmanifest.xml comme fils de la balise application <manifest... >... <application... > <service android:name=".exampleservice" />... </application> </manifest> Remarque : si le service n'est pas déclaré dans le manifest, il n'y a (évidemment) pas d'erreur à la compilation mais il n'y aussi pas d'erreur à l'exécution : rien ne se passe! 7
onstartcommand() d'un service "started" La méthode int onstartcommand(intent intent, int flags, int startid) que lance le service a pour arguments : intent est l'intent qui a été passé à startservice(), flags est un indicateur précisant comment le lancement précédent de ce service s'est terminé, startid est un id précisant cette requête L'entier retourné (qui peut être START_STICKY ou START_NOT_STICKY) est utile pour gérer la création du service s'il a été détruit lors de son exécution précédente alors qu'il n'avait pas fini de s'exécuter Rappel : en général, un service est lancé dans la thread principale de l'application qui l'accueille. Donc il est conseillé de lancer un service dans une thread créée par le programmeur : on peut utiliser Handler ou AsyncTask 8
Démos sur les services locaux Une démo, OK! Demo Ch4ServiceLocalHorlogeProjet 9
Service bloquant, non bloquant Comme indiqué, si la méthode int onstartcommand(intent intent, int flags, int startid) est codée sans création de thread, l'application risque d'être bloquée Demo Ch4ServiceLocalHorlogeProjet, clic sur le bouton "Lance un service bloquant" donne : Il faut donc écrire cette méthode plutôt comme : public int onstartcommand(intent intent, int flags, int startid) { new Thread(new Runnable() { public void run() { while (lecompteur < 100000) { try { lecompteur++; Thread.sleep(1000); } catch (InterruptedException e) { e.printstacktrace(); } } } }).start(); return Service.START_STICKY; } Démo : clic sur "Lance un service de comptage" 10
Récupérer les travaux d'un service "started" L'architecture est généralement : on lance le service started celui ci informe un BroadcastReceiver lorsque le travail est fait Le BroadcastReceiver informe que le travail a été fait Bibliographie : http://www.vogella.com/articles/androidservices/article.html 11
Lancer un service started : le code C'est aussi simple que lancer une activité! Context lecontexte = getapplicationcontext(); Intent i = new Intent(leContexte, ClasseDuService.class); lecontexte.startservice(i); On utilise la méthode startservice() qui a un seul argument, l'intent (comme startactivity()) Remarque : En fait la méthode startservice() utilisée ici est celle de la classe Context alors que la méthode startactivity() utilisée pour lancer les activités est celle de la classe Activity, pas de la classe Context (n'est pas Jean-marc!) 12
Service "lié" local ou distant Définition 1 : Un composant Android peut être lié à un service s'exécutant dans la même application. Un tel service est dit local (local service) Définition 2 : Un composant Android peut être lié à un service s'exécutant dans une autre application (un autre process). Un tel service est dit distant (remote service) Pour être lié à un service local, l'utilisation de Binder suffit Pour être lié à un service distant, il faut utiliser les Messenger ou AIDL Seuls une activité, un service et un content provider peuvent être des composants liés à un service, pas un broadcast receiver Voir à http://developer.android.com/guide/components/boundservices.html 13
Service "lié" local : le Binder Le service auquel on doit être lié, doit retourner ce qui permettra d'être lié à lui : un Binder Ce Binder (spécifié par l'interface IBinder) sera retourné par la méthode public IBinder onbind(intent intent) du service La classe Service (classe mère de tout service) possède cette méthode onbind() abstraite... qu'il faut donc toujours implémenter dans un service! 14
La liaison Binder, ServiceConnection L'association à un service est lancé par bindservice(). C'est un processus asynchrone bindservice() n'est pas bloquant et ne retourne pas un IBinder mais un boolean valant true si l'association a été faite bindservice() avertit le service, qui lance sa méthode onbind() qui retourne alors le Binder ce Binder est passé au gestionnaire de connexion (un ServiceConnection) qui lance sa méthode onserviceconnected() avec ce Binder comme argument En général cette méthode onserviceconnected() initialise des champs de la classe englobante => la classe qui implémente ServiceConnection et interne à la classe qui veut être liée au service 15
Service "lié" local : l'enchaînement des appels La suite des appels lors du bind est donc : On a évidemment une suite d'appels similaire pour le onunbind(intent i) : seules les méthodes changent 16
Conclusion sur l'enchaînement des appels La méthode bindservice() (de la classe Context) est en fait signée public abstract boolean bindservice (Intent intent, ServiceConnection conn, int flags) L'appel bindservice() fait par le client est donc bindservice(intent, leserviceconnection, drapeau); Dans intent il y a le service auquel on veut être lié. L'environnement construit une instance de ce service, puis lance la méthode onbind() sur cette instance qui retourne un objet lebinder L'objet leserviceconnection (2ième argument de bindservice()) lance alors sa méthode onserviceconnected() avec l'objet lebinder ci dessus comme second argument Dans l'objet lebinder, il y a ce qu'il faut pour utiliser le service (c'est le service qui l'a créé!) Et voilà comment le client et le serveur sont liés 17
Le ServiceConnection Donc on doit aussi construire un gestionnaire de service implémentant l'interface android.content.serviceconnection et donc donner un corps aux deux méthodes void onserviceconnected(componentname name, IBinder service) et void onservicedisconnected(componentname name) spécifié dans l'interface Ce ServiceConnection est passé comme argument de bindservice() 18
Démo de service lié local Voir projet ServiceLocalHorlogeProjet, bouton "Lance un service de comptage" Cliquer sur bouton "change d'activité" Dans la nouvelle activité, cliquer le bouton de "liaison au service local" puis le bouton "valeur du service local" 19
Services liés distants On veut désormais atteindre un service d'une application à partir d'une autre application On est sur le même smartphone mais on parle de service... distant! Bon il est vrai que ce sont deux processus distincts (mais deux DVM) Un première solution utilise les messagers (Messager, Message,...) Une seconde solution utilise une technologie générale d'accès aux technologies distantes (cf. CORBA,...) : c'est AIDL 20
Services liés distants : les messagers Dans cette technologie, on utilise les notions de messagers (Messenger), messages (Message), thread gestionnaire d'envoi de message (Handler) Et évidemment de service (Service), d'objet pour la liaison (IBinder), de gestionnaire de connexion (ServiceConnection) 21
AIDL (Android Interface Definition Language) La seconde solution pour interroger un service "distant" est d'utiliser une technologie générale d'accès aux technologies distantes (cf. CORBA,...) Il faut donc d'abord spécifier dans un langage générique ce qui est peut être proposé (par un serveur), c'est à dire ce qu'on peut utilisé (par un client). Ce type de langage est un IDL (Interface Definition Langage) Pour Android ce langage de spécification est AIDL 22
Spécification AIDL On veut construire une application qui lance un compteur puis interroger cette application Les spécifications en AIDL sont : package android.jmf.servicedistantaidlspecification; interface IServiceDeCompteur { long getcompteur(); } C'est le fichier IServiceDeCompteur.aidl à ranger sous src dans le paquetage android.jmf.servicedistantaidlspecification Remarque : les fichier aidl ont un nom de la forme XXX.aidl, sont mis dans un paquetage p.sp et commence par la ligne package p.sp; 23
Spécification AIDL : traduction en Java Automatiquement, l'environnement de dévelopement construit la traduction de ce fichier en Java. Evidemment une interface : package android.jmf.servicedistantaidlspecification; public interface IServiceDeCompteur extends android.os.iinterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.binder implements android.jmf.servicedistantaidlspecification.iservicedecompteur {... Remarque 1 : C'est le fichier IServiceDeCompteur.java rangé sous gen dans le paquetage android.jmf.servicedistantaidlspecification Remarque 2 : En général, c'est le fichier XXX.java rangé sous gen dans dans le paquetage p.sp Remarque 3 : ce fichier est automatiquement généré (cf. première ligne) et il ne faut pas le changer. Même si l'indentation déplait! 24
L'interface générée Remarque 4 : l'interface possède une classe interne (statique abstraite) Stub qui dérive de Binder (et qui contient du code!) : si, c'est possible! Donc le Binder est en grande partie créé. Cela explique l'utilisation de l'architecture AIDL 25
Le service distant (1/2) Il est similaire à un service local : il dérive de android.app.service on doit construire le Binder qui sera retourné par onbind() évidemment la classe IServiceDeCompteur.Stub nous aide. Il suffit de dériver de cette classe et d'implémenter les méthodes utiles pour le client D'où le service distant : public class ServiceDeCompteurAIDL extends Service { private long mlecompteurnonstatic = 0; private final IServiceDeCompteur.Stub mbinder = new IServiceDeCompteur.Stub() { public long getcompteur() { return mlecompteurnonstatic; } }; @Override public IBinder onbind(intent arg0) { // TODO Auto-generated method stub return mbinder; }... // Suite diapositive suivante 26
Le service distant (2/2) Son service de compteur est similaire au service local précédent : @Override public int onstartcommand(intent intent, int flags, int startid) { new Thread(new Runnable() { public void run() { while (mlecompteurnonstatic < 100000) { try { mlecompteurnonstatic++; Thread.sleep(1000); } catch (InterruptedException e) { e.printstacktrace(); } } } }).start(); return Service.START_STICKY; } 27
Déclaration du service distant dans son AndroidManifest.xml Evidemment, il faut déclarer le service dans l'androidmanifest.xml La déclaration doit être : <service android:name=".servicedecompteuraidl" > <intent-filter> <action android:name="android.jmf.servicedistantaidlspecification.iservicedecompteur" /> </intent-filter> </service> Il faut indiquer que ce service pourra être lancé par des Intent (implicites) qui demande l'action android.jmf.servicedistantaidlspecification.iservicedec ompteur. Ce sera le cas d'une application externe (un client) qui ne connaît pas exactement la classe du service (sinon il le dirait et utiliserait un Intent explicite) mais seulement son interface android.jmf.servicedistantaidlspecification.iservicedec ompteur (associé à l'action de même nom) 28
L'application cliente L'application cliente est une autre application qui va demander ce service de comptage Il lui faut les spécifications de ce service c'est à dire le fichier IServiceDeCompteur.aidl à copier dans son paquetage android.jmf.servicedistantaidlspecification L'environnement de développement construit le fichier IServiceDeCompteur.java qu'il va ranger sous gen dans le paquetage android.jmf.servicedistantaidlspecification On a alors : 29
L'application cliente : le code Pour être lié au service distant, il suffit d'écrire (comme d'habitude) : On a besoin d'un ServiceConnection qui est : IServiceDeCompteur étant l'interface android.jmf.servicedistantaidlspecification.iservicedecompte ur Context lecontexte = getapplicationcontext(); Intent i = new Intent(IServiceDeCompteur.class.getName()); lecontexte.bindservice(i, mconnection, Context.BIND_AUTO_CREATE); private IServiceDeCompteur miservicedecompteur; private ServiceConnection mconnection = new ServiceConnection() { public void onserviceconnected(componentname classname, IBinder service) { miservicedecompteur = IServiceDeCompteur.Stub.asInterface(service); } public void onservicedisconnected(componentname classname) { miservicedecompteur = null; } }; 30
Service distant : une démo Ayant repéré le service distant par miservicedecompteur, on peut récupérer les valeurs du compteur par : Démo : miservicedecompteur.getcompteur(); On lance le service distant : projet ServiceDistantAIDLServeurProjet. Au lancement de cette applilcation, le serveur est mis en route (voir les Log) On lance le client : projet ServiceDistantAIDLClientProjet. Faire l'association puis le relevé du compteur 31
Bibliographie sur les services http://developer.android.com/reference/android/ap p/service.html sur AIDL : http://developer.android.com/guide/components/aid l.html http://www.youtube.com/watch?v=fdk2phu0o9c http://saigeethamn.blogspot.fr/2009/09/androiddeveloper-tutorial-part-9.html 32
Fin 33