Concurrence sur Android avec service

Dans ce tutoriel, nous allons explorer les Un service composant et sa super-classe, le IntentService. Vous apprendrez quand et comment utiliser ce composant pour créer d'excellentes solutions d'accès simultané pour des opérations en arrière-plan de longue durée. Nous examinerons également rapidement IPC (Inter Process Communication) pour apprendre à communiquer avec des services s'exécutant sur différents processus..

Pour suivre ce tutoriel, vous devez comprendre un peu la concurrence sur Android. Si vous ne connaissez pas grand chose à ce sujet, vous pouvez lire d’abord certains de nos autres articles sur le sujet..

1. Le composant de service

le Un service Le composant est une partie très importante du framework de concurrence d'accès d'Android. Il répond à la nécessité d'effectuer une opération de longue durée au sein d'une application ou fournit certaines fonctionnalités à d'autres applications. Dans ce tutoriel, nous allons nous concentrer exclusivement sur Un servicela capacité de tâche de longue durée de, et comment utiliser ce pouvoir pour améliorer la concurrence.

Qu'est-ce qu'un service?

UNE Un service est un composant simple qui est instancié par le système pour effectuer un travail de longue durée qui ne dépend pas nécessairement de l'interaction de l'utilisateur. Il peut être indépendant du cycle de vie de l'activité et peut également s'exécuter sur un processus complètement différent.

Avant de plonger dans une discussion sur ce qu’un Un service représente, il est important de souligner que même si les services sont couramment utilisés pour des opérations d'arrière-plan de longue durée et pour exécuter des tâches sur différents processus, une Un service ne représente pas Fil ou un processus. Il ne s'exécutera que dans un thread en arrière-plan ou sur un processus différent s'il est explicitement demandé de le faire..

UNE Un service a deux caractéristiques principales:

  • Fonction permettant à l'application d'indiquer au système quelque chose qu'elle souhaite faire en arrière-plan.
  • Fonction permettant à une application d'exposer certaines de ses fonctionnalités à d'autres applications.

Services et fils

Il y a beaucoup de confusion à propos des services et des threads. Lorsqu'un Un service est déclaré, il ne contient pas Fil. En fait, par défaut, il s'exécute directement sur le thread principal et tout travail effectué sur celui-ci peut potentiellement geler une application. (Sauf si c'est un IntentService, une Un service sous-classe déjà fournie avec un thread de travail configuré.)

Alors, comment les services offrent-ils une solution de simultanéité? Bien, un Un service ne contient pas de thread par défaut, mais il peut être facilement configuré pour fonctionner avec son propre thread ou avec un pool de threads. Nous verrons plus à ce sujet ci-dessous.

Sans tenir compte de l’absence de thread intégré, un Un service est une excellente solution pour les problèmes de concurrence dans certaines situations. Les principales raisons de choisir un Un service sur d'autres solutions de concurrence telles que AsyncTask ou le cadre HaMeR sont:

  • UNE Un service peut être indépendant du cycle de vie de l'activité.
  • UNE Un service convient aux longues opérations.
  • Les services ne dépendent pas de l'interaction de l'utilisateur.
  • Lorsqu'il s'exécute sur différents processus, Android peut essayer de maintenir les services actifs même lorsque le système manque de ressources..
  • UNE Un service peut être redémarré pour reprendre ses travaux.

Types de service

Il y a deux types de Un service, commencé et lié.

UNE service commencé est lancé via Context.startService (). En règle générale, il exécute une seule opération. Il s'exécute indéfiniment jusqu'à la fin de l'opération, puis s'arrête. En règle générale, il ne renvoie aucun résultat à l'interface utilisateur.

le service lié est lancé via Context.bindService (), et il permet une communication à double sens entre le client et Un service. Il peut également se connecter avec plusieurs clients. Il se détruit lorsqu'il n'y a pas de client connecté.

Pour choisir entre ces deux types, le  Un service doit implémenter des callbacks: onStartCommand () pour fonctionner comme un service démarré, et onBind () pour fonctionner en tant que service lié. UNE Un service peut choisir de mettre en œuvre un seul de ces types, mais il peut également adopter les deux en même temps sans aucun problème. 

2. Mise en œuvre du service

Pour utiliser un service, étendez la Un service classe et substituer ses méthodes de rappel, selon le type de Un service . Comme mentionné précédemment, pour les services démarrés, le onStartCommand () méthode doit être mise en œuvre et pour les services liés, le onBind () méthode. En fait, le onBind ()La méthode doit être déclarée pour l'un ou l'autre type de service, mais elle peut renvoyer null pour les services démarrés..

classe publique CustomService étend le service @Override public int onStartCommand (intention, int flags, int startId) // Exécuter vos opérations // le service ne sera pas terminé automatiquement retournera Service.START_NOT_STICKY;  @Nullable @Override public IBinder onBind (Intentive) // Crée une connexion avec un client // à l'aide d'une interface implémentée sur IBinder return null; 
  • onStartCommand (): lancé par Context.startService (). Ceci est généralement appelé à partir d'une activité. Une fois appelé, le service peut s'exécuter indéfiniment et c'est à vous de l'arrêter, en appelant stopSelf () ou aire d'autoroute().
  • onBind (): appelé quand un composant veut se connecter au service. Appelé sur le système par Context.bindService (). Il retourne un IBinder qui fournit une interface pour communiquer avec le client.

Le cycle de vie du service est également important à prendre en compte. le onCreate ()  et onDestroy () des méthodes doivent être mises en œuvre pour initialiser et arrêter toutes les ressources ou opérations du service.

Déclaration d'un service sur manifeste

le Un service composant doit être déclaré sur le manifeste avec le  élément. Dans cette déclaration, il est également possible, mais non obligatoire, de définir un processus différent pour Un service courir dans.

 

2.2. Travailler avec les services démarrés

Pour lancer un service démarré, vous devez appeler Context.startService () méthode. le Intention doit être créé avec le Le contexte et le Un service classe. Toute information ou donnée pertinente doit également être transmise dans cette Intention.

Intention serviceIntent = new Intent (this, CustomService.class); // Transmission des données à traiter sur le paquet de services data = new Bundle (); data.putInt ("OperationType", 99); data.putString ("DownloadURL", "http://mydownloadurl.com"); serviceIntent.putExtras (data); // Démarrage du service startService (serviceIntent);

Dans ton Un service classe, la méthode qui devrait vous préoccuper est la onStartCommand (). C'est sur cette méthode que vous devez appeler toute opération que vous souhaitez exécuter sur le service démarré. Vous allez traiter le Intention capturer les informations envoyées par le client. le startId représente un identifiant unique, créé automatiquement pour cette requête spécifique et le drapeaux peut également contenir des informations supplémentaires à ce sujet.

 @Override public int onStartCommand (Intention, Int, int flags, int startId) Bundle data = intent.getExtras (); if (data! = null) int operation = data.getInt (KEY_OPERATION); // Vérifie quelle opération effectuer et envoie un message si (opération == OP_DOWNLOAD) // effectue un téléchargement return START_STICKY; 

le onStartCommand () renvoie une constante int qui contrôle le comportement:

  • Service.START_STICKY: Le service est redémarré s'il est terminé.
  • Service.START_NOT_STICKY: Le service n'est pas redémarré.
  • Service.START_REDELIVER_INTENT: Le service est redémarré après un crash et les intentions, puis le traitement sera redélivré.

Comme mentionné précédemment, un service démarré doit être arrêté, sinon il fonctionnera indéfiniment. Cela peut être fait soit par le Un service appel stopSelf () sur lui-même ou par un appel client aire d'autoroute() dessus.

void someOperation () // effectue une opération longue // et arrête le service à la fin stopSelf (); 

Liaison aux services

Les composants peuvent créer des connexions avec des services, établissant une communication bidirectionnelle avec eux. Le client doit appeler Context.bindService (), en passant un Intention, une ServiceConnection interface et un drapeau comme paramètres. UNE Un service peut être lié à plusieurs clients et il sera détruit s'il n'a plus de clients connectés.

void bindWithService () Intentity Intent = new Intent (this, PlayerService.class); // liaison avec le service bindService (intent, mConnection, Context.BIND_AUTO_CREATE); 

Il est possible d'envoyer Message objets aux services. Pour ce faire, vous devrez créer un Messager côté client dans un ServiceConnection.onServiceConnected mise en œuvre de l'interface et l'utiliser pour envoyer Message objets à la Un service.

private ServiceConnection mConnection = new ServiceConnection () @Override public void onServiceConnected (ComponentName nomClasse, service IBinder) // utilise l'IBinder reçu pour créer un messager mServiceMessenger = new Messenger (service); mBound = true;  @Override public void onServiceDisconnected (ComponentName arg0) mBound = false; mServiceMessenger = null; ;

Il est également possible de passer une réponse Messager au Un service pour que le client reçoive des messages. Attention cependant, car le client peut ne plus être là pour recevoir le message du service. Vous pouvez aussi utiliser BroadcastReceiver ou toute autre solution de diffusion.

 gestionnaire privé mResponseHandler = nouveau gestionnaire () @Override public void handleMessage (Message msg) // gestion de la réponse de Service; Message msgReply = Message.obtain (); msgReply.replyTo = new Messenger (mResponseHandler); try mServiceMessenger.send (msgReply);  catch (RemoteException e) e.printStackTrace (); 

Il est important de dissocier le service lorsque le client est détruit..

@Override protected void onDestroy () super.onDestroy (); // se déconnecter du service if (mBound) unbindService (mConnection); mBound = false; 

Sur le Un service côté, vous devez implémenter le Service.onBind () méthode, fournissant un IBinder fourni à partir d'un Messager. Cela va relayer une réponse Gestionnaire pour gérer le Message objets reçus du client.

 IncomingHandler (PlayerService playerService) mPlayerService = new WeakReference <> (playerService);  @Override public void handleMessage (Message msg) // handle messages public IBinder onBind (Intentive) // passe un classeur à l'aide du messager créé. Return mMessenger.getBinder ();  final Messenger mMessenger = new Messenger (nouveau IncomingHandler (this));

3 Utilisation simultanée des services

Enfin, il est temps de parler de la façon de résoudre les problèmes de simultanéité à l'aide de services. Comme mentionné précédemment, une norme Un service ne contient pas de fils supplémentaires et il fonctionnera sur le principal Fil par défaut. Pour surmonter ce problème, vous devez ajouter un ouvrier Fil, un pool de threads ou exécuter le Un service sur un processus différent. Vous pouvez également utiliser une sous-classe de Un service appelé IntentService qui contient déjà un Fil.

Exécution d'un service sur un thread de travail

Pour faire le Un service exécuter sur un fond Fil vous pouvez simplement créer un extra Fil et exécuter le travail là-bas. Cependant, Android nous offre une meilleure solution. Une des façons de tirer le meilleur parti du système consiste à implémenter le cadre HaMeR dans le Un service, par exemple en bouclant un Fil avec une file de messages pouvant traiter des messages indéfiniment.

Il est important de comprendre que cette implémentation traitera les tâches de manière séquentielle. Si vous devez recevoir et traiter plusieurs tâches en même temps, vous devez utiliser un pool de threads. L'utilisation de pools de threads n'entre pas dans le cadre de ce didacticiel et nous n'en parlerons pas aujourd'hui. 

Pour utiliser HaMeR, vous devez fournir le Un service avec un Looper, une Gestionnaire et un HandlerThread.

 Looper privé mServiceLooper; serviceHandler privé mServiceHandler; // Gestionnaire pour recevoir des messages de la classe finale privée du client ServiceHandler extend Handler ServiceHandler (Looper Looper) super (Looper);  @Override public void handleMessage (Message msg) super.handleMessage (msg); // gérer les messages // arrêter le service à l'aide de startId stopSelf (msg.arg1);  @Override public void onCreate () HandlerThread thread = new HandlerThread ("ServiceThread", Process.THREAD_PRIORITY_BACKGROUND); thread.start (); mServiceLooper = thread.getLooper (); mServiceHandler = new ServiceHandler (mServiceLooper);  

Si le cadre HaMeR vous est inconnu, lisez nos tutoriels sur l'accès simultané à HaMer pour Android.

Le service d'intention

S'il n'y a pas besoin de Un service pour rester en vie pendant longtemps, vous pouvez utiliser IntentService, une Un service sous-classe prête à exécuter des tâches sur des threads d'arrière-plan. Intérieurement, IntentService est un Un service avec une implémentation très similaire à celle proposée ci-dessus. 

Pour utiliser cette classe, tout ce que vous avez à faire est de l’étendre et de mettre en œuvre la onHandleIntent (), une méthode hook qui sera appelée chaque fois qu'un client appelle startService () sur ce Un service. Il est important de garder à l'esprit que le IntentService s'arrêtera dès que son travail sera terminé.

Classe publique MyIntentService étend IntentService public MyIntentService () super ("MyIntentService");  @Override protected void onHandleIntent (Intention Intention) // gérer les intentions envoyées par startService

IPC (communication inter-processus)

UNE Un service peut fonctionner sur un tout autre Processus, indépendamment de toutes les tâches qui se produisent sur le processus principal. Un processus a sa propre allocation de mémoire, son groupe de threads et ses priorités de traitement. Cette approche peut être très utile lorsque vous devez travailler indépendamment du processus principal..

La communication entre différents processus s'appelle IPC (Inter Process Communication). Dans un Un service il y a deux façons principales de faire IPC: utiliser un Messager ou mettre en œuvre un AIDL interface. 

Nous avons appris à envoyer et recevoir des messages entre services. Tout ce que vous avez à faire est d’utiliser créer un Messager en utilisant le IBinder instance reçue pendant le processus de connexion et l'utiliser pour envoyer une réponse Messager Retour à la Un service.

 gestionnaire privé mResponseHandler = nouveau gestionnaire () @Override public void handleMessage (Message msg) // gestion de la réponse de Service; private ServiceConnection mConnection = new ServiceConnection () @Override public void onServiceConnected (ComponentName nomClasse, service IBinder) // utilise l'IBinder reçu pour créer un messager mServiceMessenger = new Messenger (service); Message msgReply = Message.obtain (); msgReply.replyTo = new Messenger (mResponseHandler); try mServiceMessenger.send (msgReply);  catch (RemoteException e) e.printStackTrace (); 

le AIDL L’interface est une solution très puissante qui permet d’appeler directement Un service méthodes en cours d'exécution sur différents processus et il est approprié d'utiliser lorsque votre Un service est vraiment complexe. toutefois, AIDL est compliqué à mettre en œuvre et est rarement utilisé, son utilisation ne sera donc pas abordée dans ce tutoriel..

4. Conclusion

Les services peuvent être simples ou complexes. Cela dépend des besoins de votre application. J'ai essayé de couvrir autant de terrain que possible sur ce tutoriel, mais je me suis concentré uniquement sur l'utilisation de services à des fins de concurrence et il y a plus de possibilités pour ce composant. Si vous souhaitez en savoir plus, consultez la documentation et les guides Android. 

À bientôt!