DDéévveellooppppeemmeenntt AApppplliiccaattiioonnss MMoobbiilleess
CChh55.. SSeerrvviicceess eett BBrrooaaddccaassttRReecceeiivveerr
www.zeddini.com
Objectifs Un service est une composante essentielle d'une application. Il est nécessaire lorsque votre application souhaite effectuer des opérations ou des calculs en dehors de l'interaction utilisateur. Donc comme vous l'aurez compris un service ne dispose pas d'interface graphique. Rien de mieux qu'un exemple, votre lecteur de musique vous permet d'écouter la musique tout en surfant sur internet, lisant vos mails et cette fonctionnalité n'est possible qu'à l'aide desservices. À l’instar des activités, les services sont des composants qui peuvent être démarrés depuis l’application à laquelle ils appartiennent ou à distance depuis un programme tiers. Les ressemblances s’arrêtent là, les services n’ont pas de représentation visuelle et la logique qui les gouverne diffère. Dans ce chapitre nous aborderons :
- Les services (threads, cycle de vie…) - Services Locaux (LocalService)
• Déclaration du service • •
Implémentation des listeners Implémentation d’un binder
- Services Distants (RemoteService)
• Déclaration du service • Mise en place des Callbacks • Démarrer le service au boot du téléphone • Exemple : Connexion au service MP3
Ressources : http://developer.android.com/ tout, tout, tout Cours, TD/TP, code http://www.u-picardie.fr/ferment/android http://www.vogella.com/android.html tutoriel l'Art du developpement, M. Murphy, Pearson livre simplePro Android 4, S Komatineni D MacLean, Apress livre complet Le livre écrit par Florent Garin « Android » Le livre de Mark Murphy chez Pearson Le cours de Victor Matos : http://grail.cba.csuohio.edu/~matos/notes/cis-493/Android-Syllabus.pdf Plusieurs livres : Android A Programmers Guide - McGraw Hill Professional Android Application Development - Wrox
2
5.1LES SERVICES (threads, cycle de vie…)
A l’instar des Activities, des Intents, les services font partie des briques essentielles d’Android. Ils ne disposent pas d’interface utilisateur mais fonctionnent en arrière plan pour une période de temps indéfinie. L’exemple le plus courant est le lecteur de musique, qui vous permet d’écouter vos mp3 sans bloquer la navigation sur internet ou consulter la liste des vos contacts. Un service peut également rapatrier des données sur internet tels que des flux RSS.
Les services ont pour but de réaliser des tâches de fond sans aucune interaction avec l’utilisateur pour une durée indéfinie. Il existe deux types de services :
•
•
les services locaux (ou LocalService) qui s’exécutent dans le même processus que votre application Les services distants (ou RemoteService) qui s’exécutent dans un processus différent de celui de application
Les services s’exécutent dans le Thread principal du processus parent. Ils doivent être déclarés dans le fichier AndroidManifest.xml: <serviceandroid:name=".subpackagename.ServiceName"/>
Ils doivent étendre la classe Service dont vous devrez surcharger les méthodes suivantes en fonction de vos besoins :
voidonCreate(); // initialisation des ressources voidonStart(Intentintent); // SDK<2.0 la tâche de fond démarre voidonStartCommand(Intentintent, int flags, intstartId); // SDK>2.0 la tâche de fond démarre voidonDestroy(); // libération des ressources
IBinder onBind(Intentintent); // connexion client distant Boolean onUnbind(Intentintent); // déconnexion d'un client voidonRebind(Intentintent)
Google a publié un post sur la méthode onStartCommand() apparue avec le SDK 2.0 :http://android-developers.blogspot.com/2010/02/service-api-changes- starting-with.html La méthode onStart() est dépréciée mais doit être redéfinie pour une compatibilité avec les SDK antérieurs (si nécessaire).
Dans le principe, les services Android ressemblent à n’importe quel autre système RPC comme COM, Corba ou RMI. L’idée est de définir un service, c’est-à-dire un objet exposant des méthodes sollicitées à distance depuis un autre processus. Les méthodes sont appelées exactement de la même façon que les autres objets dont le code est exécuté localement.
3
Quant au cycle de vie d’un service, Google l’illustre de la manière suivante :
Pour interagir (démarrer/arrêter…) avec un service, deux possibilités s’offrent à nous :
• Soit on appelle la méthode startService() qui invoque la
méthode onCreate() puisonStart() service.startService() | ->onCreate() – >onStartCommand() [service running] L’appel de la méthode stopService() invoque la méthode onDestroy()
4
• Soit on appelle la méthode bindService() qui appelle uniquement la
méthodeonCreate() activity.bindService() | ->onCreate() [service created]
Il est possible de voir la liste des services exécutés en allant dans Menu > Settings > Applications > Running Services > du téléphone:
Un service n'a pas de durée définie, il est là pour exécuter sa tâche et il fonctionnera tant que c'est nécessaire. Les différentes méthodes qui correspondent au cycle de vie d'un serviceseront à surcharger quand vous créerez votre service :
• OnCreate() : cette méthode est appelée à la création du service et est en
général utilisée pour initialiser ce qui sera nécessaire à votre service ;
• OnStart(Intent i) : le service démarre. Valable uniquement pour les versions
du SDK inférieur à 2.0 ;
• OnStartCommand(Intent i, int flags, intstartId) : le service démarre.
Valable uniquement pour les versions du SDK supérieur à 2.0 ;
• OnDestroy() : appelé à la fermeture du service.
5
5.2 SERVICES LOCAUX (LocalService)
5.2.1 Déclaration du service
Un service Local n’est accessible que par les Activity de l’application.
Pour l’exemple, notre service initialise un Timer et une tâche qui sera exécutée
toutes les minutes.
Nous allons créer une classe héritant de Service et surcharger les méthodes
onCreate(), onStart() et onDestroy().
6
Public class BackgroundService extends Service {
private Timer timer ;
@Override protectedvoidonCreate() { super.onCreate(); timer = new Timer(); Log.d(this.getClass().getName(), "onCreate"); }
@Override publicintonStartCommand(Intent intent, int flags,
intstartId) {
Log.d(this.getClass().getName(), "onStart"); timer.scheduleAtFixedRate(newTimerTask() { publicvoid run() { // Executer de votretâche } }, 0, 60000);
return START_NOT_STICKY; }
@Override publicvoidonDestroy() { Log.d(this.getClass().getName(), "onDestroy"); this.timer.cancel(); } }
Nous déclarons le service dans le fichier AndroidManifest.xml à l’intérieur de la balise<application> :
<serviceandroid:name=".BackgroundService"/>
Pour le démarrer, nous faisons appel à la méthode startService(Intent) de l’Activity prenant en paramètre un Intent. Ce dernier peut être initialisé de deux manières :
✓ Soit en lui passant explicitement le context de l’application et la class du
service.
Intent intent = newIntent(this,BackgroundService.class); startService(intent);
7
✓ Soit en déclarant une action dans le fichier AndroidManifest.xml…
<serviceandroid:enabled="true"android:name=".BackgroundService"> <intent-filter> <actionandroid:name=".BackgroundService.ACTION"/> </intent-filter> </service>
… que nous passons au constructeur de l’intent
Intent intent = newIntent(".BackgroundService.ACTION"); startService(intent);
Il est possible de passer des objets complets serialisables en paramètre avec les
méthodes intent.putExtra(…).
Les objets pourront être récupérer dans la méthode onStart(Intentintent, intstartId) du
service avec intent.getExtras().get(String key). Le fonctionnement est similaire à une
table de Hash.
Pour arrêter notre service :
Intent intent = newIntent(this,BackgroundService.class); stopService(intent);
5.2.3 Implémentation d’un binder • Android permet de véhiculer au travers d’interface de service, en plus des types
déjà cités, des objets « remote ». C’est un peu comme si on passait en paramètre
d’un service un autre service. Cette fonctionnalité est assez répandue dans les
systèmes RPC, c’est quelque chose qu’il est possible de faire par exemple en Java
RMI où un UnicastRemoteObject peut être échangé sur le réseau.
• Comme avec un RemoteService, nous allons nous connecter au service et récupérer
un Binder. A travers cet objet nous accéderons aux méthodes public du service via
une classe interface. L’avantage de cette solution est d’unifier l’utilisation des
LocalService et RemoteService mais surtout de récupérer l’instance du service.
8
• La connexion au service se fait via la méthode bindService (Intent service,
ServiceConnection conn, int flags) de la classe Context dont hérite Activity.
L’argument flags peut prendre soit 0soit Context.BIND_AUTO_CREATE pour
forcer le service à démarrer, s’il ne l’est pas, au moment de la connexion.
• Les classes Service, ServiceConnection, IBinder… sont relativement
contraignantes, complexifient le code et n’offrent pas vraiment d’aide à la
réalisation de services. Mais si un jour votre LocalService doit se transformer en
RemoteService alors vous serez prêts.
5.3 SERVICES DISTANTS (RemoteService)
5.3.1 Déclaration du service
Contrairement aux LocalService qui s’exécutent dans le processus de l’application et
plus particulièrement dans le thread principal, les RemoteService s’exécutent dans
un processus totalement différent de celui de votre application.
Afin que deux processus différents puissent communiquer et s’échanger des données,
Google a implémenté son IDL appelé AIDL (Android Interface DefinitionLanguage)
pour décrire les méthodes publiques et les données échangées. Cette description se fait
dans un fichier .aidlqui devra être connu des deux processus .Pourront être échangées
entre deux processus, seulement les types primitifs java (int,boolean, etc),
les String, List, Mapet CharSequence. Pour des objects personnalisés, vous devrez
implémenter l’interface Parcelable.
Le langage AIDL
Pour définir un service, la première étape est de définir l’interface de celui-ci : les méthodes distantes et les objets transitant entre le client et le serveur. La définition de cette interface ne se fait pas en Java mais dans un langage spécifiquement conçu à cet effet, l’AIDL (Android Interface DefinitionLanguage).
L’AIDL est différent de l’IDL de Corba. En fait, l’AIDL est plus simple à appréhender dans la mesure où la syntaxe de ce langage est très proche de celle de Java.
Par rapport à une interface Java standard, il y a quelques contraintes qui pèsent sur la définition des interfaces AIDL. En outre, trois mots clés : « in », « out » et «
9
inout » ont été introduits. Ces mots clés se positionnent devant les paramètres des méthodes et pour indiquer leur direction. La signification de ces mots clés est la suivante :
• « in » marque les paramètres dont la valeur est passée du client vers le serveur ; • « out » ceux dont la valeur est fixée par le serveur et retournée au client ; • « inout » enfin concerne les paramètres initialement transportés du client vers le serveur mais qui peuvent être modifiés par celui-ci ; ces modifications seront répercutées côté client.
En dehors du concept du sens des paramètres complètement étranger à Java, les interfaces AIDL se doivent de respecter certaines règles quant au choix des types des paramètres et des objets renvoyés.
Seuls les types suivants sont autorisés :
• Les types primitifs Java (boolean, byte, char, int, long, float, double) à l’exception de short – Ces types ne peuvent être que des paramètres « in » et non des « out » ni des « inout ». L’ajout du modificateur « in » est donc optionnel. Si le service souhaite transmettre en résultat de la méthode un type primitif Java, il faudra le définir en retour de la méthode.
• Le type String – En AIDL, ce type est considéré comme un primitif : il
n’apparaît qu’en paramètre « in ».
• Le type CharSequence– Comme le String, il ne peut s’agir que d’un paramètre « in
».
• Le type List – Bien qu’en Java l’interface List soit définie dans le package java.util, il n’est pas nécessaire d’importer cette interface dans le fichier AIDL. Il est possible d’utiliser la forme générique du type et de préciser la nature des objets à contenir. Évidemment, pour pouvoir être échangée entre le client et le serveur, la liste ne pourra regrouper que des objets appartenant individuellement aux types autorisés (primitifs, String, CharSequence...). Les listes peuvent être employées comme paramètre « in », « out » et « inout ».
• Le type Map– Cette structure de données qui, on le rappelle, permet d’indexer des valeurs par des clés, fonctionne en AIDL comme la List, à ce détail près que la forme générique n’est pas autorisée.
• N’importe quel objet Parcelable. • Toutes les autres interfaces générées à partir d’une définition AIDL.
Ces deux derniers items méritent une explication approfondie.
Un RemoteService peut être sollicité par plusieurs applications différentes., il faudra ajouterandroid:exported="true"android:process=":remote" à la déclaration de votre service (<service>) dans le fichierAndroidManifest.xml.
Pour chaque RemoteService il est nécessaire de déclarer un fichier .aidl que nous
plaçons dans le même package.
10
. Ce fichier sert d’interface entre le client et le service. Nous y déclarons seulement les
méthodes public. La syntaxe ressemble fortement au Java bien que cela n’en soit pas.
Les mots clefs ‘in‘, ‘out‘, ‘inout‘ sont ajoutés aux paramètres des méthodes. ‘//‘
permet d’ajouter des commentaires. Enfin on peut spécifier ‘oneway‘ devant interface.
Les objets Parcelable
Parcelableest une interface qui, à la manière de l’interface Serializable, caractérise les objets ayant la capacité à transiter entre plusieurs process
Exemple:
Nous créons dans le package tn.rnu.isi.backgroundservice.bo, notre objet java Data
implémentant l’interface Parcelable :
package tn.rnu.isi.backgroundservice.bo;
importandroid.os.Parcel; importandroid.os.Parcelable;
publicclass Data implementsParcelable { publicstaticenum Weather {
NUAGEUX, PLUVIEUX, ENSOLEILLE, BROUILLARD
}
privateinttemperature; private Weather today;
publicstaticfinalParcelable.Creator<Data>CREATOR =
newParcelable.Creator<Data>() {
public Data createFromParcel(Parcel in) {
returnnew Data(in);
}
public Data[] newArray(int size) { returnnew Data[size];
}
};
public Data(int temperature, Weather today) {
super(); this.temperature = temperature; this.today = today;
}
11
Public int getTemperature() {
returntemperature;
}
public Weather getToday() {
returntoday;
}
private Data(Parcel in) { readFromParcel(in);
}
Public void readFromParcel(Parcel in) { temperature = in.readInt(); today = (Weather) in.readSerializable();
}
Public int describeContents() {
return 0;
}
Public void writeToParcel(Parcel arg0, int arg1) {
arg0.writeInt(temperature); arg0.writeSerializable(today);
}
}
Notre objet contient le temps et la température, deux types java simples.
L’interface Parcelable nous oblige à définir la méthode writeToParcel() dans laquelle
nous ajoutons à l’objet Parcel tous les attributs de notre objet Data. Un Parcel peut
être vue comme l’unité de communication entre deux processus. Attention, l’ordre
dans lequel nous ajoutons les attributs à une importance. C’est ordre doit être respecté
dans la méthodereadFromParcel().
L’attribut static CREATOR est obligatoire. Il implémente
l’interface Parcelable.Creator. L’implémentation de cette interface nous force a
écrire la méthode readFromParcel().
Pour que notre objet Data puisse voyager d’un processus à l’autre, il nous reste à
déclarer son fichier Data.aidl :
package tn.rnu.isi.backgroundservice.bo;
parcelable Data;
12
Le mot clef parcelable spécifie que notre objet Data implémente bien le protocole
Google pour les communications IPC (Inter-Process Communication).
Les erreurs contenues dans les fichiers .aidl devraient disparaitre.
Déconnecter du service
Pour nous déconnecter au service, nous faisons appel à la
méthodeunbindService(ServiceConnection) qui prend en
paramêtrel’object ServiceConnectionque nous avons créé :
unbindService(remoteConnection);
Permission
Il est indispensable d’ajouter la permission dans le fichier AndroidManifest.xml, au
même niveau que la balise <application> sans quoi notre Activity ne se connectera
jamais :
<uses-permission
android:name="tn.rnu.isi.backgroundservice.BACKGROUNDSERVICE_PERMISSI
ON"></uses-permission>
……
<service android:exported="true" android:enabled="true" android:process=":remote"
android:name=".service.BackgroundService"
android:permission="tn.rnu.isi.backgroundservice.BACKGROUNDSERVICE_PERM
ISSION"/>
Exemple voici un objet Invoice (facture) implémentant Parcelable:
package tn.rnu.isis.service;
import android.os.Parcel; import android.os.Parcelable;
public final class Invoice implements Parcelable {
private int number; private int vendorCode; private int totalAmount;
13
private String comment; public static final Parcelable.Creator<Invoice> CREATOR = new
Parcelable.Creator<Invoice>() {
@Override public Invoice createFromParcel(Parcel in) { Invoice invoice = new Invoice(); invoice.number = in.readInt(); invoice.vendorCode = in.readInt(); invoice.totalAmount = in.readInt(); invoice.comment = in.readString(); return invoice;
}
@Override public Invoice[] newArray(int size) { return new Invoice[size];
}
};
public Invoice() { }
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(number); dest.writeInt(vendorCode); dest.writeInt(totalAmount); dest.writeString(comment);
} public int getNumber() {
return number;
} public void setNumber(int number) { this.number = number;
} public int getVendorCode() {
return vendorCode;
} public void setVendorCode(int vendorCode) { this.vendorCode = vendorCode;
}
public int getTotalAmount() {
return totalAmount;
}
14
public void setTotalAmount(int totalAmount) { this.totalAmount = totalAmount;
}
public String getComment() { return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
À la base, l’objet Invoice est un banal Bean modélisant un concept métier, en l’occurrence une facture. Cet objet a quatre attributs : number, vendorCode, totalAmount et comment ainsi que leurs getters et setters.
Les méthodes de Parcelable à implémenter sont au nombre de deux : • intdescribeContents() – Cette méthode dont l’explication fournie dans la javadoc est quelque peu succincte se doit de retourner un masque de bits informant d’éventuels types particuliers manipulés par cet objet Parcelable. Dans la pratique, cette méthode sert rarement ; généralement, on se contente donc de retourner 0.
• writeToParcel(Parceldest, int flags) – Le rôle de cette méthode est d’écrire l’état de l’objet dans ce qu’Android nomme une parcelle. Un objet Parcel est un conteneur qui peut être échangé avec un objet de type Ibinderqui est l’interface de base des objets invocables à distance comme les services.
• L’implémentation de cette méthode consiste donc à écrire un à un sur la Parcel passée en paramètre les attributs de l’objet. La parcelle offre pour cela un ensemble de méthodes writeXXX pour chacun des types des attributs, un peu sous le même modèle que l’ObjectOutputStreamde Java.
la
ce
de
est
flag
valeur
Ensuite, la parcelle sera transmise à l’autre bout du canal IPC pour que l’objet puisse être recréé. Le deuxième paramètre de la méthode est un flag de type int. Si constante Parcelable.PARCELABLE_WRITE_RETURN_VALUE, cela signifie que l’opération courante de sérialisation prend part à la construction d’une valeur de retour d’une méthode. L’objet sauvegardé sur la parcelle est donc soit retourné directement par une méthode remote soit par un paramètre « out » ou « inout ». Dans l’exemple de la facture, et assez fréquemment d’ailleurs, ce flag peut être ignoré. Mais on pourrait toutefois imaginer que la sérialisation d’un objet lorsqu’il intervient en tant que valeur de retour donne lieu à un traitement différent que lorsqu’il apparaît comme paramètre « in ».
égale
la
à
Une fois l’interface Parcelableimplémentée par l’objet, il faut créer la « factory »
15
sous forme d’un attribut static nommé CREATOR qui implémentera l’interface Parcelable.Creator<T>, T étant le type générique, la classe Invoice dans l’exemple.
Cela ne pose pas de difficultés particulières, l’interface ne possèdant que deux
méthodes :
• T[] newArray(int size) – Cette méthode renvoie un tableau typé vide (les
entrées valent toutes null) de la taille fixée en paramètre.
• T createFromParcel(Parcel in) – Cette méthode est le miroir de la méthode writeToParcel. Son objectif est d’instancier l’objet Parcelable et de lire la valeur de ses attributs depuis la parcelle. Il est capital que la lecture se fasse exactement dans le même ordre que l’écriture.
On peut créer soit même un fichier AIDL par clique droit (sur le package
contenant l’objet parcelable), New -> File :
Le contenu du fichier Invoice.aidl
packagetn.rnu.isi.android.service; parcelable Invoice;
16
Définition du service
Maintenant que tous les objets prenant part aux échanges entre les clients et le service ont été construits, on est fin prêt à écrire le fichier d’interface AIDL du service :
package tn.rnu.isis.service; import tn.rnu.isis.service.Invoice; interface ISampleService { String echo(in String message); int max(int a, int b); void duplicate(int index, inout long[] data); Invoice getInvoice(int number); }
Ce fichier doit porter le nom du service, ISampleService.aidl et doit être
placé dans le répertoire correspondant à son package.
La présence de ce fichier est détectée automatiquement par le plug-in Eclipse qui génère alors les Stub et Proxy, contenus dans le fichier source ISampleService.java, qui serviront de point de départ à l’implémentation du service. Si l’application est développée avec un autre IDE qu’Eclipse, il est obligatoire d’exécuter la commande aidl pour générer ce fichier.
Vocabulaire RPC Les termes employés dans ce chapitre sont souvent empruntés au vocabulaire des systèmes objets distribués comme Corba, RMI, SOAP... Les mots stub et proxy apparaissent aussi dans ce chapitre. La distinction entre ces deux termes n’est pas souvent très claire dans la littérature informatique où ils sont parfois employés indifféremment pour représenter la même notion. Le proxy, mandataire en français, est un objet agissant à la place d’un autre. Dans le contexte d’un système d’objets distribués tel que celui des services Android, il s’agit de l’objet qui sera instancié côté client, qui offrira les mêmes méthodes que celles de l’interface du service. Cet objet sera chargé de gérer la communication interprocessus, marshalling et unmarshalling des objets, avec le service. Il donnera ainsi l’impression au consommateur du service que celui-ci se comporte comme un simple objet instancié localement. Le stub (souche) est la classe embryon qui sera étendue pour fournir l’implémentation complète du service. Cette classe propose également quelques méthodes statiques notamment« asInterface » utilisée sur le client pour obtenir le service (en réalité le proxy !) sous la forme de son interface.
17
5.3.2 Implémentation du service
L’implémentation du service se fait en étendant la classe interne Stub générée. Dans l’exemple, il s’agit de ISampleService.Stub et le nom choisi pour cette implémentation est SampleServiceImpl dont voici la source :
package tn.rnu.isis.service.impl;
import tn.rnu.isis.service.ISampleService.Stub; import tn.rnu.isis.service.Invoice; import android.os.RemoteException;
public class SampleServiceImpl extends Stub {
@Override public void duplicate(int index, long[] data) throws
RemoteException {
long valueToCopy = data[index]; for (int i = 0; i < data.length; i++)
data[i] = valueToCopy;
// data est un paramètre inout. Les modifications
apportées
}
// se verront sur le client.
@Override public String echo(String message) throws RemoteException {
return message;
}
@Override public Invoice getInvoice(int number) throws RemoteException {
Invoice invoice = new Invoice(); invoice.setNumber(number); invoice.setComment("impayée !"); invoice.setTotalAmount(15000); invoice.setVendorCode(12345); return invoice;
// dans une véritable application on effectuerait probablement // une recherche dans un content provider ou une base de données // sur un serveur distant.
}
@Override public int max(int a, int b) throws RemoteException {
return Math.max(a, b);
}
}
18
La tâche d’implémentation semble relativement triviale, néanmoins on peut émettre quelques recommandations. Premièrement, le service devra être thread safe. En effet, à la manière d’un serveur d’application, Android gère un pool de threads chargés de l’exécution, à la demande des clients, des méthodes « remote ». Par contre, contrairement justement aux serveurs d’applications Java EE, il n’y a pas ici d’équivalent au pool d’EJB, autrement dit plusieurs threads pourront potentiellement exécuter des méthodes en même temps sur la même instance du service.
En outre, l’autre conséquence de ces traitements en parallèle sur la même
instance d’objet est que le service ne peut gérer d’état.
5.3.3 Publication du service
Le service est défini et implémenté mais il n’est pour l’instant atteignable par aucun client. En réalité, le service n’est pour l’instant qu’un IBinderet pas encore un vrai service. L’ultime étape est d’autoriser un client à en récupérer une instance. Dans le monde Corba, les services doivent s’enregistrer auprès d’un annuaire. Ainsi, lorsqu’un client interroge cet annuaire en passant un nom, il récupère une instance du service correspondant, ou plutôt son proxy, et peut ensuite l’interroger.
Dans Android, c’est un peu la même histoire. Le service sera accessible non pas en fournissant un nom mais un objet Intent. En outre, le service peut déclarer plusieurs IntentFilters ; de ce fait il pourra être sollicité par des Intents différents. Néanmoins, avant de paramétrer les IntentFilters, la publication exige d’implémenter à nouveau une interface, il s’agit de « android.app.Service » :
package tn.rnu.isis.service.impl;
import android.app.Service; import android.content.Intent; import android.os.IBinder;
public class SampleService extends Service {
private final SampleServiceImpl service = new
SampleServiceImpl();
@Override public IBinder onBind(Intent intent) {
return service;
}
}
Il n’y a qu’une méthode à implémenter : la méthode onBindqui renvoie l’implémentation du service en fonction de l’Intent. Cela peut sembler étrange, on pourrait se demander à quoi peut bien servir cette méthode et l’intérêt de passer l’Intent. En fait la réponse tient dans le fait qu’un service Android peut regrouper plusieurs objets «
19
remote ». Pour faire une comparaison, c’est un peu comme les web services SOAP qui peuvent définir plusieurs Ports.
Dans l’exemple, le service n’encapsule qu’une seule interface AIDL, l’implémenta-
tion est donc des plus simples et il n’est même pas utile d’examiner l’Intent.
Après cela il faut déclarer le service dans le manifeste : <service android:name=".impl.SampleService" android:exported="true" > <intent-filter> <action android:name="tn.rnu.isis.service.ISampleService" /> </intent-filter> </service>
Le filter est important, sans cela le service ne pourra pas être appelé depuis une
autre application mais uniquement en interne.
Juste avant de passer à l’implémentation du client, il faut penser à packager un jar contenant les classes dont celui-ci aura besoin. Il s’agit uniquement, dans l’exemple, des classes Invoice et ISampleService. Les classes du package tn.rnu.isi.android.service.impl ne sont pas à mettre dans le jar car ces classes (SampleService et SampleServiceImpl) sont utilisées côté service exclusivement.
5.3.4 Côté client
Le projet Android (SampleServiceClient) consommateur du service ne devra pas manquer d’importer en tant que librairie le fameux jar contenant le Stub et les classes de type Parcelable.
20
Pour obtenir une référence vers le service, il faut utiliser la méthode bindSer-
vice(Intent, ServiceConnection, int).
Le premier paramètre est l’Intentdont l’action doit correspondre à celle de l’IntentFilter, comme pour les Activity. Dans l’exemple, c’est le nom qualifié de l’interface du service ISampleService.
Le deuxième paramètre est une implémentation de l’interface ServiceConnection. Cette classe sera le callback de l’opération de binding. Ainsi la méthode onService- Connected(ComponentName, IBinder) sera invoquée par le système à la connexion et la méthode onServiceDisconnectedà la déconnexion. Le rôle premier de cette classe est de capter le service, fourni sous forme d’IBinder. Ensuite, il reste à appeler la méthode ISampleService.Stub.asInterface(binder) qui se chargera de caster le binder dans le type de l’interface du service.
Le troisième et dernier paramètre est un flag d’option sur le traitement. La valeur Context.BIND_AUTO_CREATE employée indique que le service devra être lancé si ce n’est pas le cas.
Appel du service :
Nous pouvons appeler le service soit à partir de la même application, soit à partir d’une autre application sauf qu’à partir d’une application cliente, il faut ajouter le jar contenant le uniquement, classes Invoice et ISampleService
Un exemple complet d’activité cliente du service, pourrait être: package tn.rnu.isi.clientsampleservice;
import tn.rnu.isis.service.ISampleService; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.view.View; import android.widget.Button; import android.widget.TextView;
public class ServiceClientActivity extends Activity {
private ISampleService service;
/** Called when the activity is first created. */
21
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_service_client); ServiceConnection connection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName
className,
ISampleService.Stub.asInterface(binder); }
IBinder binder) {
service =
@Override public void onServiceDisconnected(ComponentName
className) {
service = null;
}
}; bindService(new Intent(ISampleService.class.getName()),
connection,
final Button sendBtn = (Button)
Context.BIND_AUTO_CREATE);
findViewById(R.id.sendCmdBtn);
final TextView result = (TextView)
findViewById(R.id.result);
sendBtn.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
try {
//result.setText("le plus grand entre 5 et 10 est : " //+ service.max(5, 10));
result.setText("la Facture N° : "
+ service.getInvoice(5).getNumber() +" est " + service.getInvoice(5).getComment());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
L’activité comporte un bouton et une zone de texte. Lorsque l’utilisateur clique sur le bouton, la méthode du service max qui compare deux entiers est appelée. La valeur retournée par le service est ensuite affichée dans la zone de
22
texte.
Avant de lancer le client, il faut bien entendu avoir déployé l’application contenant le
service. Le résultat obtenu est alors le suivant :
Figure 5.4 — Le résultat du service est affiché par l’activité
Conclusion
Les RemoteService ont une véritable valeur ajoutée. Ainsi on peut imaginer un service météo commun à toutes nos applications et nos widgets. Les possibilités sont nombreuses dès lors que l’OS gère le multi-taches
23
5.4 LE BUS DE MESSAGES (BroadcastReceiver)
Un broadcast receiver est un composant permettant de communiquer avec des composants déjà lancés entre elles. Un broadcastReceiver sert à executer une fonction lors d’une réception d’un broadcast.
Un Broadcast est un signal qui est envoyé à toutes les applications sur chaque changement d’état de celui ci. Ca peut être un signal de batterie faible, de gps activé, de wifi disponible.
Un Broadcast est un élément en sommeil du système androïde. Seule une intention (pour lequel elle est enregistrée) peut mettre en action. Le travail de Broadcast est de passer d'une notification à l'utilisateur, dans le cas où un événement particulier survient. L'utilisation d'un BroadcastReceiver, les applications peuvent s'inscrire à un événement particulier. Une fois que l'événement se produit, le système notifie
à toutes les applications enregistrées.
Par exemple, un broadcastReceiver déclenche notification batterie faible que vous voyez sur l'écran de votre mobile.
24
D'autres cas : notifications de nouveaux amis, nouvel RSS, nouveau message, … sur votre application Facebook.
En fait, vous voyez broadcastReceiver travailler tout le temps. Notifications comme les messages entrants, un message WiFi Activé / Désactivé etc sont toutes les annonces en temps réel de ce qui se passe dans le système Android et les applications.
Mise en œuvre du BroadcastReceiver
Lors de l'implémentation d'un récepteur de diffusion que vous avez à faire en deux étapes:
• Vous devez créer une sous-classe d'd'Android BroadcastReceiver
• Vous devez mettre en œuvre la méthode OnReceive ()
25
public class MyIntentReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) {
...
}
}
Android appelle la méthode OnReceive () sur tous les Broascasts enregistrés chaque fois qu'un événement se produit. Dites que vous voulez être averti lorsque le niveau de batterie est faible. Dans ce cas, vous pouvez enregistrer votre receiver à l'événement Intent.ACTION_BATTERY_LOW . Maintenant, dès que le niveau de la batterie descend en dessous d'un seuil défini votre OnReceive () méthode sera appelée.
Voici les deux arguments de la OnReceive () méthode:
• Context: Il est utilisé pour accéder à des informations supplémentaires
ou pour démarrer les services ou activités.
• Intent: L'intention sert à enregistrer le receiver.
Enregistrement de BroadcastReceiver
Il existe deux manières d'enregistrer un broadcast ; une est statique et l'autre
dynamique.
1. statique: Utilisez tag <receiver> dans votre fichier
manifest. (AndroidManifest.xml)
2. Dynamique: Utiliser la méthode Context.registerReceiver () pour enregistrer
dynamiquement une instance.
Donc, Deux façons d’associer des broadcasts:
• Dans le manifest à l’intérieur de la balise Application avec intentFilter.
<receiver android:name=".MyBroadReceiver" > <intent-filter> <action android:name="tn.rnu.isi.broadcastreceiverdemo" > </action> </intent-filter> </receiver>
• Directement dans le code 26
MyBroadReceiver myVarBrodcastReceiver = new MyBroadReceiver() ; registerReceiver(myVarBrodcastReceiver, new IntentFilter("tn.rnu.isi.broadcastreceiverdemo")) ;
Dans le 1er cas, la fonction onReceive sera automatiquement appellé sans instance.
Dans le 2ème cas, il faut appeler l’instance, soit dans l’activité, soit dans un service. Il est donc possible de définir un broadcastReceiver en classe interne pour avoir accès aux attributs et méthode de l’activité.
Pour envoyer un broadcast, il nous suffit tout simplement de définir un message dans un intent et d’envoyer cet intent avec la fonction :
sendBroadcast(new Intent("tn.rnu.isi.broadcastreceiverdemo"));
Exemple de broadcast receiver qui s’exécute lors du démarrage du téléphone.
<receiver android:name="service.OnBootReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" > </action> </intent-filter> </receiver>
public class OnBootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) {
...
}
}
Différence entre l'intention de l'activité et l'intention de BroadcastReceiver
Vous devez vous rappeler que intentions de BroadcastReceiver sont différents des intentions utilisés pour démarrer une activité ou d'un service. L'objectif utilisé pour démarrer une activité apporte des modifications à une opération
27
l'utilisateur interagit avec, si l'utilisateur est au courant du processus. Toutefois, en cas d'intention de la BroadcastReceiver, l'opération s'exécute en tâche de fond, et est donc invisible pour l'utilisateur.
Un Broascast est lancé avec un intent mais contrairement à startActivity, un broadcast peut activer plusieurs broadcastReceiver. Le principe reste le même que l’intent implicite c’est à dire qu’il faut déclarer un filtre sur le broascast receiver pour le déclarer comme candidat.
Événements du système que vous pouvez vous inscrire pour
Un grand nombre d'événements système sont définis comme des champs statiques finales de l' intention classe. De plus tout au long de l'API, il ya beaucoup plus de classes qui offrent des événements spécifiques. Quelques exemples sont BluetoothDevice ou TextToSpeech.Engine et presque toutes les classes Manager comme