Escolar Documentos
Profissional Documentos
Cultura Documentos
com/android23/p8571/android/creation-de-service/
Forums Tutoriels Magazine FAQs Blogs Projets Chat Newsletter tudes Emploi Club Contacts
Accueil Conception Java .NET Dv. Web EDI Langages SGBD Office Solutions d'entreprise Applications Systmes
14:54:06, Catgories: Rcapitulatif Java, Android, Android, 6754 mots , Nicolas Druet
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 arrire plan pour une priode de temps indfinie. L'exemple le plus courant est le lecteur de musique, qui vous
permet d'couter vos mp3 sans bloquer la navigation sur internet ou consultre la liste des vos contacts. Un service peut galement
rapatrier des donnes sur internet tels que des flux RSS.
[Suite:]
Les services ont pour but de raliser des tches de fond sans aucune interaction avec l'utilisateur pour une dure indfinie. Il existe deux
type de services :
les services locaux (ou LocalService) qui s'excutent dans le mme processus que votre application
Les services distants (ou RemoteService) qui s'excutent dans un processus diffrent de celui de application
Les services s'excutent dans le Thread principal du processus parent. Ils doivent tre dclars dans le fichier AndroidManifest.xml:
<service android:name=".subpackagename.ServiceName"/>
Ils doivent tendre la classe Service dont vous devrez surcharger les mthodes suivantes en fonction de vos besoins :
Google a publi un post sur la mthode onStartCommand() apparue avec le SDK 2.0 :
http://android-developers.blogspot.com/2010/02/service-api-changes-starting-with.html
La mthode onStart() est dprcie mais doit tre redfinie pour une compatibilit avec les SDK antrieurs (si ncessaire).
Soit on appelle la mthode startService() qui invoque la mthode onCreate() puis onStart()
service.startService() | -> onCreate() - > onStartCommand() [service running]
L'appel de la mthode stopService() invoque la mthode onDestroy()
Il est possible de voir la liste des services excuts en allant dans Menu > Settings > Applications > Running Services > du tlphone:
Pour l'exemple, notre service initialise un Timer et une tche qui sera excute toutes les minutes. Nous allons crer une classe hritant
de Service et surcharger les mthodes onCreate(), onStart() et onDestroy().
@Override
protected void onCreate() {
super.onCreate();
timer = new Timer();
Log.d(this.getClass().getName(), "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(this.getClass().getName(), "onStart");
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
// Executer de votre tche
}
}, 0, 60000);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
Log.d(this.getClass().getName(), "onDestroy");
this.timer.cancel();
}
Pour le dmarrer, nous faisons appel la mthode startService(Intent) de l'Activity prenant en paramtre un Intent. Ce dernier
peut tre initialis de deux manires :
Il est possible de passer des objets complets serialisables en paramtre avec les mthodes intent.putExtra(...). Les objets pourront tre
rcuprer dans la mthode onStart(Intent intent, int startId) du service avec intent.getExtras().get(String key). Le
fonctionnement est similaire une table de Hash.
Dans un premier temps, nous allons implmenter un systme de listeners trs simple. L'interface IBackgroundServiceListener
implmente par notre Activity pour couter les mises jour du service est la suivante :
Pour accder au service depuis l'Activity nous rajoutons une variable static IBackgroundService service que nous initialiserons dans la
mthode onCreate() :
@Override
public void onCreate() {
...
service = this;
}
...
}
L'emploi d'une variable static n'est pas la meilleure solution. Nous verrons plus tard comment nous en passer. Mais ce stade, il n'est
pas possible d'accder aux mthodes du service et d'appeler un setter.
Au moment voulu, dans la mthode run() du Timer nous ferons appel la mthode prive fireDataChanged(data) pour notifier les
listeners (dans notre cas, l'Activity) de la mise jour des donnes.
Attention ! Si nous nous contentons d'implmenter la mthode dataChanged(), l'exception suivante sera leve au runtime :
android.view.ViewRoot$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
En effet, la mthode du listener dataChanged() est appele par le Timer, et donc excute dans le Thread de celui-ci. L'exception nous
indique que toute mise jour de l'interface utilisateur ne peut se faire que par le Thread responsable de la cration des View, Button,
TextView... Nous utilisons donc la mthode runOnUiThread de la classe Activity pour poster des instructions dans l'UiThread :
Il nous reste mettre jour la mthode onDestroy() du service pour vider la liste des listeners :
@Override
public void onDestroy() {
this.listeners.clear();
this.timer.cancel();
Log.d(this.getClass().getName(), "onDestroy");
}
A ce stade, nous avons vu comment dclarer notre service et y accder, dfinir une tche de fond, et mettre jour notre interface
utilisateur. Le dfaut de la solution propose est l'utilisation d'une variable static pour accder au service depuis l'Activity.
Malheureusement, la mthode startService(Intent) n'offre pas d'accs direct aux mthodes du services. Pour se faire, nous allons
avoir recours au binding.
Comme avec un RemoteService, nous allons nous connecter au service et rcuprer un Binder. A travers cet objet nous accderons aux
mthodes public du service via l'interface IBackgroundService que nous avons dfinie plus haut. L'avantage de cette solution est
d'unifier l'utilisation des LocalService et RemoteService mais surtout de rcuprer l'instance du service.
Nous redfinissons la mthode onBind(), modifions l'implmentation du listener et dportons notre tche de fond dans la mthode
_onStart() :
@Override
public void onCreate() {
timer = new Timer();
binder = new BackgroundServiceBinder(this);
_onStart();
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
_onStart();
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
this.listeners.clear();
this.timer.cancel();
}
}
Nous dfinissons le binder qui nous permettra de rcuprer l'instance de notre service :
L'avantage de l'interface IBackgroundService est de masquer les mthodes onCreate(), onBind()... et de ne proposer uniquement
addListener(), removeListener() et pourquoi pas d'autres mthodes mtiers.
Il nous reste nous connecter au service, accder aux mthodes exposes par l'interface et s'ajouter comme listener pour mettre jour
l'interface utilisateur :
bindService(intent,connection, Context.BIND_AUTO_CREATE);
La connexion au service se fait via la mthode bindService (Intent service, ServiceConnection conn, int flags) de la classe Context dont
hrite Activity. L'argument flags peut prendre soit 0 soit Context.BIND_AUTO_CREATE pour forcer le service dmarrer, s'il ne l'est pas,
au moment de la connexion.
La partie sur les LocalService est termine. Je ne suis pas personnellement convaincu de leur intret. Les classes Service,
ServiceConnection, IBinder... sont relativement contraignantes, complexifient le code et n'offrent pas vraiment d'aide la ralisation de
services. Mais si un jour votre LocalService doit se transformer en RemoteService alors vous serez prets.
Contrairement aux LocalService qui s'excutent dans le processus de l'application et plus particulirement dans le thread principal, les
RemoteService s'excutent dans un processus totalement diffrent de celui de votre application.
Afin que deux processus diffrents puissent communiquer et s'changer des donnes, Google a implment son IDL appel AIDL (Android
Interface Definition Language) pour dcrire les mthodes publiques et les donnes changes. Cette description se fait dans un fichier
.aidl qui devra tre connu des deux processus !
Pourront tre changes entre deux processus, seulement les types primitifs java (int, boolean, etc), les String, List, Map et
CharSequence. Pour des objects personnaliss, vous devrez implmenter l'interface Parcelable.
Un RemoteService peut tre sollicit par plusieurs applications diffrentes. C'est pourquoi je prfre le dporter dans un nouveau projet
Android sous Eclipse. Je conseille galement la ralisation d'une petite interface graphique avec deux boutons pour le dmarrer et
l'arrter. Si vous ne souhaitez pas crer un projet particulier, il faudra ajouter android:process=":remote" la dclaration de votre
service (<service>) dans le fichier AndroidManifest.xml.
Aprs avoir crer un projet BackgroundService (package de l'application com.android23.backgroundservice), nous crons notre service
com.android23.backgroundservice.service.BackgroundService le plus simple qui soit, avec un Timer et un objet Data:
@Override
public void onCreate() {
@Override
public void onDestroy() {
super.onDestroy();
this.binder = null;
this.timer.cancel();
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
Pour chaque RemoteService il est ncessaire de dclarer un fichier .aidl que nous plaons dans le mme package
(com.android23.backgroundservice.service).
package com.android23.backgroundservice.service;
import com.android23.backgroundservice.bo.Data;
interface IRemoteBackgroundService {
int getPid(); // Renvoie le PID du processus du service
Data getData(); // Renvoie notre objet mis jour
//void updateData(in Data data);
}
Il n'y pas de rgle concernant le nom du fichier. Ce fichier sert d'interface entre le client et le service. Nous y dclarons seulement les
mthodes public. La syntaxe ressemble fortement au Java bien que cela n'en soit pas. Les mots clefs 'in', 'out', 'inout' sont ajouts aux
paramtres des mthodes. '//' permet d'ajouter des commentaires. Enfin on peut spcifier 'oneway' devant interface.
Nous crons dans le package com.android23.backgroundservice.bo, notre objet java Data implmentant l'interface Parcelable :
Notre objet contient le temps et la temprature, deux types java simples. Le code est calqu sur l'exemple que vous trouverez sur
http://developer.android.com/guide/developing/tools/aidl.html.
L'interface Parcelable nous oblige dfinir la mthode 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 mthode readFromParcel().
L'attribut static CREATOR est obligatoire. Il implmente l'interface Parcelable.Creator. L'implmentation de cette interface nous force a
crire la mthode readFromParcel().
Pour que notre objet Data puisse voyager d'un processus l'autre, il nous reste dclarer son fichier Data.aidl :
package com.android23.backgroundservice.bo;
parcelable Data;
Le mot clef parcelable spcifie que notre objet Data implmente bien le protocole Google pour les communication IPC.
Les erreurs contenues dans les fichiers .aidl devraient disparaitre. Eclipse gnre alors l'interface Java IRemoteBackgroundService dans
le rpertoire source "gen" du projet :
Il nous reste ensuite dclarer notre BackgroundServiceBinder qui permettra au client d'accder aux mthodes dclares dans le
fichier aidl. Notre binder hrite de IRemoteBackgroundService.Stub gnr par Eclipse :
Les clients devront dclarer cette permission dans leur fichier manifest pour se connecter au service. Sans quoi une SecurityException
sera leve l'appel des mthodes startService ou bindService. Pour plus d'informations concernant les permissions, jetez un coup
d'oeil la documentation Google:
http://developer.android.com/guide/topics/manifest/manifest-intro.html#perms
http://developer.android.com/guide/topics/security/security.html
Revenons notre projet Eclipse principal. Pour raliser notre appel distant au service, nous devons y copier/coller les fichier .aidl
Data.aidl, et IRemoteBackgroundService.aidl dans le mme package que le projet BackgroundService, ainsi que votre objet java
Data! Eclipse crera automatiquement les interfaces d'accs.
Nous passons au constructeur de l'Intent le package principal de notre projet BackgroundService puis la classe du service.
IRemoteBackgroundService.Stub.asInterface() nous permet de rcuprer notre service. A noter galement que chaque appel de
mthode peut lever une RemoteException.
Pour nous dconnecter au service, nous faisons appel la mthode unbindService(ServiceConnection) qui prend en paramtre
l'object ServiceConnection que nous avons cr :
unbindService(remoteConnection);
Il est indispensable d'ajouter la permission dans le fichier AndroidManifest.xml, au mme niveau que la balise <application> sans
quoi notre Activity ne se connectera jamais :
<uses-permission android:name="com.android23.backgroundservice.BACKGROUNDSERVICE_PERMISSION"></uses-
permission>
Il n'est pas possible de dbugger la fois le service et notre application. Pour tester, notre service il sera ncessaire de dvelopper dans
notre projet BackgroundService une Activity avec 2 boutons : un pour dmarrer le service, et l'autre pour l'arrter.
Pour tester notre application, nous dployons le service en premier puis nous lanons notre application en mode debug.
Comme pour les LocalService, les RemoteService peuvent galement notifier leurs clients grce une liste RemoteCallbackList. Pour cela
nous allons apporter quelques modifications.
Pour commencer, retournons dans le projet BackgroundService, et modifions le fichier IRemoteBackgroundService.aidl en supprimant
la mthode getData() et en rajoutant les mthodes registerCallback et unregisterCallback qui permettrons aux clients de
s'enregistrer auprs du service :
package com.android23.backgroundservice.service;
import com.android23.backgroundservice.service.IRemoteBackgroundServiceCallback;
interface IRemoteBackgroundService {
int getPid();
A travers cette interface, les clients pourront s'enregistrer. Une fois enregistrs il leur faudra implmenter l'interface
IRemoteBackgroundServiceCallback que nous dclarons dans le fichier IRemoteBackgroundServiceCallback.aidl :
package com.android23.backgroundservice.service;
import com.android23.backgroundservice.bo.Data;
interface IRemoteBackgroundServiceCallback {
void dataChanged(in Data data);
}
Nous mettons jour la mthode _onStart() afin de crer un nouvel objet Data toutes les minutes et notifier les clients :
@Override
public void onDestroy() {
super.onDestroy();
this.binder = null;
this.callbacks.kill(); // Dsactive tous les lments de la liste
this.timer.cancel();
}
this.service = service;
}
@Override
public int getPid() throws RemoteException {
return Process.myPid();
}
@Override
public void registerCallback(IRemoteBackgroundServiceCallback cb) throws RemoteException {
if(cb != null){
service.getCallbacks().register(cb);
}
@Override
public void unregisterCallback(IRemoteBackgroundServiceCallback cb) throws RemoteException {
if(cb != null){
service.getCallbacks().unregister(cb);
}
}
};
Retournons maintenant du ct de notre application principale pour y copier les fichiers IRemoteBackgroundService.aidl et
IRemoteBackgroundServiceCallback.aidl dans le package com.android23.backgroundservice.service.
Puis mettons jour la manire dont nous nous connectons au service en dclarant un IRemoteBackgroundServiceCallback et en
l'inscrivant auprs du service dans la mthode onServiceConnected() :
Pour tester, nous dployons le service et puis nous lanons en mode debug notre application.
<receiver android:name=".service.BackgroundServiceReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
</application>
<uses-permission android:name="com.android23.backgroundservice.BACKGROUNDSERVICE_PERMISSION"/>
Nous avons galement prcis que notre service demande la permission BACKGROUNDSERVICE_PERMISSION pour pouvoir dmarrer tout
seul.
Dployons le service sur l'mulateur en faisant un clic droit sur le projet : Run As > Android Application. Une fois dploy, re-dmarrons
l'mulateur en cliquant sur le bouton dans la barre d'outil d'Eclipse puis sur le bouton start dans la fentre qui apparait. Une fois
l'mulateur lanc, il suffit d'aller dans Menu > Settings > Applications > Running Services > pour constater que notre
BackgroundService est bien en route.
Le lecteur MP3 d'Android est lui aussi un RemoteService. Il est donc trs simple de s'y connecter pour rcuprer la chanson en cours,
l'artiste... Pour l'exemple n'oubliez pas de lancer la lecture d'un fichier mp3.
Profitons du fait qu'Android soit open-source pour nous rendre sur Google Code et rcuprer le fichier IMediaPlaybackService.aidl.
Une fois le fichier rcupr, nous crons le package "com.android.music" dans lequel nous insrons le fichier .aidl. Eclipse gnre alors
automatiquement l'interface IMediaPlaybackService. Le service ne renvoyant que des types primitifs, seul ce fichier suffit au client.
Nous nous connectons au lecteur mp3 comme nous nous connections notre BackgroundService :
@Override
public void onServiceConnected(ComponentName componentname, IBinder ibinder) {
IMediaPlaybackService service = IMediaPlaybackService.Stub.asInterface(ibinder);
try {
Log.i("ServiceConnection", "Playing track: " + service.getTrackName());
Log.i("ServiceConnection", "By artist: " + service.getArtistName());
} catch (Exception e) {
}
}
};
Malheureusement, si Google modifie l'interface .aidl de son lecteur MP3 il faudra mettre jour le fichier IMediaPlaybackService.aidl
dans notre projet.
> Conclusion
Le post est relativement long mais regroupe tout ce que j'ai pu apprendre en parcourant le net. Je vais rflchir le publier au format
PDF dans la rubrique tutorial.
L'intrt des LocalService reste flou mes yeux. J'ai la fcheuse impression de gagner plus en complexit qu'en productivit. A contrario,
les RemoteService ont une vritable valeur ajoute. Ainsi on peut imaginer un service mto commun toutes nos applications et nos
widgets. Les possibilits sont nombreuses ds lors que l'OS gre le multi-taches :D :D :D
Si vous souhaitez que certains points soient approfondis, ou corrigs, n'hsitez pas m'en faire part.
> Ressources
http://developer.android.com/reference/android/app/Service.html
http://blog.7touchgroup.com/tag/android-services-versus-broadcast-receivers/
http://www.brighthub.com/mobile/google-android/articles/34861.aspx
http://saigeethamn.blogspot.com/2009/09/android-developer-tutorial-part-9.html
http://www.androidcompetencycenter.com/2009/06/start-service-at-boot/
http://www.alexc.me/android-music-app-service-currently-playing-song/231/
Commentaires:
sera ferme.
"Les services distants (ou RemoteService) qui s'excutent dans un processus diffrent de celui de application"
Tout a fait. Mais en aucun cas son processus parent est le processus de l'application. Sinon ca n'aurait pas grand intrt. Le
RemoteService est une autre application Android sans IHM ;)
30/05/2010 @ 22:32
Vos questions techniques : forum d'entraide Blogs - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'quipe de rdaction du club d'entraide des dveloppeurs francophones
Nous contacter - Hbergement - Participez - Copyright 2000-2011 www.developpez.com - Legal informations.