270 votes

Comment faire communiquer un service Android avec une activité

Je suis en train d'écrire ma première application Android et j'essaie de m'y retrouver dans la communication entre les services et les activités. J'ai un service qui fonctionnera en arrière-plan et fera de l'enregistrement basé sur le temps et le GPS. J'ai une activité qui sera utilisée pour démarrer et arrêter le service.

Tout d'abord, je dois être capable de déterminer si le service est en cours d'exécution lorsque l'activité est lancée. Il y a d'autres questions ici à ce sujet, donc je pense que je peux trouver une solution (mais n'hésitez pas à me donner des conseils).

Mon vrai problème : si l'activité est en cours d'exécution et que le service est lancé, j'ai besoin d'un moyen pour le service d'envoyer des messages à l'activité. De simples chaînes de caractères et des entiers à ce stade - des messages d'état principalement. Les messages ne seront pas envoyés régulièrement, donc je ne pense pas que l'interrogation du service soit une bonne solution s'il existe un autre moyen. Je ne veux cette communication que lorsque l'activité a été lancée par l'utilisateur - je ne veux pas lancer l'activité à partir du service. En d'autres termes, si vous lancez l'activité et que le service est en cours d'exécution, vous verrez des messages d'état dans l'interface utilisateur de l'activité lorsque quelque chose d'intéressant se produit. Si vous ne lancez pas l'activité, vous ne verrez pas ces messages (ils ne sont pas si intéressants).

Il semble que je devrais pouvoir déterminer si le service est en cours d'exécution et, le cas échéant, ajouter l'activité en tant qu'auditeur. Puis supprimer l'activité en tant qu'auditeur lorsque l'activité se met en pause ou s'arrête. Est-ce vraiment possible ? La seule façon que j'ai trouvé pour le faire est de faire en sorte que l'activité implémente Parcelable et construise un fichier AIDL pour pouvoir le passer à travers l'interface distante du service. Cela semble exagéré, et je n'ai aucune idée de la façon dont l'activité devrait implémenter writeToParcel() / readFromParcel().

Existe-t-il une méthode plus simple ou plus efficace ? Merci pour toute aide.

EDIT :

Pour ceux qui s'y intéressent plus tard, il existe un exemple de code de Google pour gérer cela via AIDL dans le répertoire samples : /apis/app/RemoteService.java

282voto

MaximumGoat Points 1463

L'auteur de la question a probablement dépassé ce stade depuis longtemps, mais au cas où quelqu'un d'autre chercherait...

Il y a une autre façon de procéder, qui me semble être la plus simple.

Ajouter un BroadcastReceiver à votre activité. Enregistrez-le pour recevoir une intention personnalisée dans onResume et le désenregistrer dans onPause . Puis envoyez cette intention à partir de votre service lorsque vous souhaitez envoyer vos mises à jour de statut ou autres.

Assurez-vous que vous ne seriez pas mécontent si une autre application écoutait votre Intent (quelqu'un pourrait-il faire quelque chose de malveillant ?), mais au-delà de ça, tout devrait bien se passer.

Un échantillon de code a été demandé :

Dans mon service, j'ai ceci :

// Do stuff that alters the content of my local SQLite Database
sendBroadcast(new Intent(RefreshTask.REFRESH_DATA_INTENT));

( RefreshTask.REFRESH_DATA_INTENT est juste une chaîne constante).

Dans mon activité d'écoute, je définis mon BroadcastReceiver :

private class DataUpdateReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(RefreshTask.REFRESH_DATA_INTENT)) {
          // Do stuff - maybe update my view based on the changed DB contents
        }
    }
}

Je déclare mon récepteur premier de la classe :

private DataUpdateReceiver dataUpdateReceiver;

Je passe outre onResume pour ajouter ceci :

if (dataUpdateReceiver == null) dataUpdateReceiver = new DataUpdateReceiver();
IntentFilter intentFilter = new IntentFilter(RefreshTask.REFRESH_DATA_INTENT);
registerReceiver(dataUpdateReceiver, intentFilter);

Et je passe outre onPause à ajouter :

if (dataUpdateReceiver != null) unregisterReceiver(dataUpdateReceiver);

Maintenant mon activité écoute mon service pour dire "Hey, va te mettre à jour". Je pourrais passer des données dans le Intent au lieu de mettre à jour les tables de la base de données et de revenir en arrière pour trouver les changements dans mon activité, mais puisque je veux que les changements persistent de toute façon, il est logique de transmettre les données via la base de données.

5 votes

Comment ? Ce que vous décrivez est exactement ce dont j'ai besoin. Mais plus que cela, j'ai besoin d'un exemple de code complet. Merci d'avance. Pour info, la dernière fois que j'ai essayé d'écrire un widget, la partie messagerie m'a pris 2 mois. Riez si vous voulez, mais vous, les experts, devez publier davantage car, à mon avis, Android est plus compliqué qu'iOS !

1 votes

Aaaaaaa, The Busy Coder's Guide to Advanced Android Development contient un excellent chapitre sur les widgets. C'est la seule ressource qui m'a permis de m'en sortir. CommonsWare, l'auteur, a 115k de réputation sur StackOverflow, et je recommande vivement le livre. commonsware.com/AdvAndroid

66 votes

Broadcasts sont fantastiques. En outre, si vous ne voulez pas que votre diffusion aille au-delà de votre propre processus, envisagez d'utiliser une LocalBroadcast : developer.Android.com/reference/Android/support/v4/content/

103voto

MrSnowflake Points 2599

Il existe trois façons évidentes de communiquer avec les services :

  1. Utilisation des intentions
  2. Utilisation de l'AIDL
  3. Utilisation de l'objet de service lui-même (comme singleton)

Dans votre cas, je choisirais l'option 3. Créez une référence statique au service lui-même et remplissez-la dans onCreate() :

void onCreate(Intent i) {
  sInstance = this;
}

Créer une fonction statique MyService getInstance() qui renvoie l'image statique sInstance .

Ensuite, dans Activity.onCreate() vous démarrez le service, attendez de manière asynchrone que le service soit effectivement lancé (vous pouvez demander à votre service d'informer votre application qu'il est prêt en envoyant une intention à l'activité) et obtenez son instance. Une fois l'instance obtenue, enregistrez votre objet d'écoute de service auprès du service et vous êtes prêt. NOTE : lorsque vous éditez des vues à l'intérieur de l'activité, vous devez les modifier dans le thread de l'interface utilisateur, le service va probablement exécuter son propre thread, vous devez donc appeler Activity.runOnUiThread() .

La dernière chose que vous devez faire est de supprimer la référence à l'objet écouteur dans le fichier Activity.onPause() sinon une instance de votre contexte d'activité va fuir, ce qui n'est pas bon.

NOTE : Cette méthode n'est utile que si votre application/activité/tâche est le seul processus qui accède à votre service. Si ce n'est pas le cas, vous devez utiliser l'option 1. ou 2.

2 votes

Comment puis-je busy-wait l'activité ? Pouvez-vous nous expliquer ?

1 votes

Donc, après onCreate ou après onStartCommand, dans votre classe de service, définissez une variable statique publique, appelez-la isConnected ou quelque chose comme true. Ensuite, dans votre activité, faites ceci Intent intent = new Intent(act, YourService.class) ; startService(intent) ; while(!YourService.isConnected) { sleep(300) ; } Après cette boucle, votre service est en cours d'exécution et vous pouvez communiquer avec lui D'après mon expérience, il y a définitivement quelques limitations à l'interaction avec un service comme celui-ci.

0 votes

Pourquoi il ne peut être démarré que dans Activity.onCreate() ?

41voto

Jack Gao Points 671

Utilisez LocalBroadcastManager pour enregistrer un récepteur pour écouter une diffusion envoyée par un service local dans votre application, la référence va ici :

http://developer.Android.com/reference/Android/support/v4/content/LocalBroadcastManager.html

3 votes

Oui, une très belle discussion sur le localbroadcast ici lien

5 votes

LocalBroadcastManager est maintenant déprécié en faveur de LiveData ou d'autres outils de patronage des observateurs : developer.Android.com/reference/androidx/localbroadcastmanager/

22voto

Kjetil Points 156

L'utilisation d'un messager est un autre moyen simple de communiquer entre un service et une activité.

Dans l'activité, créez un Handler avec un Messenger correspondant. Cela permettra de gérer les messages de votre service.

class ResponseHandler extends Handler {
    @Override public void handleMessage(Message message) {
            Toast.makeText(this, "message from service",
                    Toast.LENGTH_SHORT).show();
    }
}
Messenger messenger = new Messenger(new ResponseHandler());

Le messager peut être transmis au service en l'attachant à un message :

Message message = Message.obtain(null, MyService.ADD_RESPONSE_HANDLER);
message.replyTo = messenger;
try {
    myService.send(message);
catch (RemoteException e) {
    e.printStackTrace();
}

Un exemple complet se trouve dans les démonstrations de l'API : MessengerService et MessengerServiceActivity . Consultez l'exemple complet pour savoir comment fonctionne MyService.

1 votes

Très apprécié ! Fonctionne parfaitement. Juste un détail dans le code : myService.send(message); devrait être messenger.send(message); à la place.

21voto

Madhur Ahuja Points 11471

Je suis surpris que personne n'ait fait référence à la bibliothèque du bus d'Otto.

http://square.github.io/otto/

Je l'ai utilisé dans mes applications Android et il fonctionne parfaitement.

8 votes

Sinon, il y a le site de greenrobot EventBus bibliothèque.

16 votes

Mais rappelez-vous que EventBus et otto sont toujours dans un seul processus. Lorsque vous utilisez un service qui se trouve dans son propre processus (démarré avec l'activation de l'attribut android:process=":my_service" ils ne sont pas en mesure de communiquer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X