web-dev-qa-db-fra.com

Comment faire en sorte que le service Android communique avec Activity

J'écris ma première application Android et j'essaie de comprendre la communication entre services et activités. J'ai un service qui s'exécutera en arrière-plan et effectuera des enregistrements GPS et temporels. J'aurai une activité qui sera utilisée pour démarrer et arrêter le service. 

Donc, tout d’abord, je dois savoir si le service est en cours d’exécution lorsque l’activité est lancée. Il y a quelques autres questions à ce sujet, alors je pense pouvoir comprendre (mais n'hésitez pas à donner des conseils).

Mon vrai problème: si l'activité est en cours d'exécution et que le service est démarré, j'ai besoin d'un moyen pour que le service envoie des messages à l'activité. Chaînes simples et entiers à ce stade - messages d'état principalement. Les messages ne seront pas diffusés régulièrement, donc je ne pense pas que le sondage auprès du service soit un bon moyen d’aller s’il existe un autre moyen. Je souhaite cette communication uniquement lorsque l'activité a été démarrée par l'utilisateur. Je ne souhaite pas démarrer l'activité à partir du service. En d'autres termes, si vous démarrez l'activité et que le service est en cours d'exécution, des messages d'état s'affichent dans l'interface utilisateur de l'activité lorsqu'un événement intéressant se produit. Si vous ne démarrez pas l'activité, vous ne verrez pas ces messages (ils ne sont pas si intéressants).

Il semble que je devrais être en mesure de déterminer si le service est en cours d'exécution et, le cas échéant, d'ajouter l'activité en tant qu'auditeur. Supprimez ensuite l'activité en tant qu'écouteur lorsque l'activité est suspendue ou arrêtée. Est-ce vraiment possible? La seule façon pour moi de le faire est de faire en sorte que Activity implémente Parcelable et crée un fichier AIDL afin que je puisse le transmettre via l'interface distante du Service. Cela semble toutefois exagéré et je ne sais pas du tout comment l’Activité devrait implémenter writeToParcel ()/readFromParcel ().

Y a-t-il un moyen plus facile ou meilleur? Merci pour toute aide.

MODIFIER:  

Pour les personnes intéressées ultérieurement, il existe un exemple de code de Google permettant de le gérer via AIDL dans le répertoire des exemples: /apis/app/RemoteService.Java

225
Scott Saunders

Il existe trois moyens évidents de communiquer avec les services:

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

Dans votre cas, j'irais avec l'option 3. Faites une référence statique au service lui-même et remplissez-le dans onCreate (): 

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

Créez une fonction statique MyService getInstance(), qui renvoie la variable sInstance statique. 

Ensuite, dans Activity.onCreate(), vous démarrez le service, attendez de manière asynchrone que le service soit réellement démarré (vous pouvez demander à votre service de notifier votre application qu'elle est prête en envoyant une intention à l'activité.) Et obtenir son instance. Lorsque vous avez l'instance, enregistrez votre objet écouteur de service sur votre service et vous êtes défini. REMARQUE: lors de la modification de vues dans l'activité, vous devez les modifier dans le fil de l'interface utilisateur. Le service exécutera probablement son propre fil de discussion. Vous devez donc appeler Activity.runOnUiThread().

La dernière chose à faire est de supprimer la référence à votre objet écouteur dans Activity.onPause(), sinon une instance de votre contexte d'activité risque de fuir, ce qui n'est pas bien.

REMARQUE: cette méthode n'est utile que lorsque votre application/activité/tâche est le seul processus permettant d'accéder à votre service. Si ce n'est pas le cas, vous devez utiliser l'option 1. ou 2.

82
MrSnowflake

Le demandeur a probablement dépassé ce point depuis longtemps, mais au cas où quelqu'un d'autre le rechercherait ...

Il existe une autre façon de gérer cela, qui, à mon avis, pourrait être la plus simple.

Ajoutez un BroadcastReceiver à votre activité. Enregistrez-le pour recevoir une intention personnalisée dans onResume et annulez-le dans onPause. Envoyez ensuite cette intention de votre service lorsque vous souhaitez envoyer vos mises à jour de statut ou ce que vous avez.

Assurez-vous de ne pas être mécontent si une autre application écoute votre intention (quelqu'un peut-il faire quelque chose de malveillant?), Mais au-delà de cela, tout devrait bien se passer.

Un exemple de code a été demandé:

A mon service, j'ai:

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

(RefreshTask.REFRESH_DATA_INTENT est simplement 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 destinataire au sommet de la classe: 

private DataUpdateReceiver dataUpdateReceiver;

Je remplace onResume pour ajouter: 

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

Et je remplace onPause pour ajouter: 

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

Maintenant, mon activité consiste à écouter mes services pour leur dire "Hey, va te mettre à jour." Je pourrais transmettre des données dans Intent au lieu de mettre à jour les tables de la base de données, puis de rechercher les modifications dans mon activité, mais comme je veux que les modifications persistent, il est logique de transmettre les données via db.

242
MaximumGoat

Utilisez LocalBroadcastManager pour enregistrer un récepteur afin d'écouter une émission envoyée par le service local de votre application. La référence se trouve ici:

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

37
Jack Gao

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

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

J'utilise cela dans mes applications Android et cela fonctionne de manière transparente.

18
Madhur Ahuja

Utiliser un Messenger est un autre moyen simple de communiquer entre un service et une activité.

Dans l'activité, créez un gestionnaire avec un Messenger correspondant. Cela gérera 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());

Messenger 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();
}

Vous trouverez un exemple complet dans les démonstrations de l’API: MessengerService et MessengerServiceActivity . Reportez-vous à l'exemple complet pour connaître le fonctionnement de MyService.

18
Kjetil

L'autre méthode non mentionnée dans les autres commentaires consiste à lier l'activité au service à l'aide de bindService () et à obtenir une instance du service dans le rappel ServiceConnection. Comme décrit ici http://developer.Android.com/guide/components/bound-services.html

7
miguel

Une autre solution consiste à utiliser des observateurs avec une classe de modèle fictive via l'activité et le service lui-même, en implémentant une variation de modèle MVC. Je ne sais pas si c'est le meilleur moyen d'y parvenir, mais c'est la méthode qui a fonctionné pour moi. Si vous avez besoin d'un exemple, demandez-le et je posterai quelque chose.

2
ReDirEct__

Pour donner suite à la réponse de @MrSnowflake avec un exemple de code . Il s’agit du XABBER maintenant open source Application class . La classe Application centralise et coordonne les interfaces Listeners et ManagerInterfaces, etc. Les gestionnaires de toutes sortes sont chargés dynamiquement. Activity´s démarré dans Xabber indiquera le type de Listener qu’ils sont. Et quand un Service le démarre, il est signalé à la classe Application telle que démarrée. Maintenant, pour envoyer un message à un Activity, tout ce que vous avez à faire est de faire en sorte que votre Activity devienne un listener du type dont vous avez besoin. Dans OnStart()OnPause() register/unreg. Service peut demander à la classe Application ce que listener doit juste parler et s'il y en a une, l'activité est prête à recevoir. 

En parcourant la classe Application, vous verrez qu'il y a un butin supplémentaire, alors.

2
Erik

Vous pouvez également utiliser LiveData qui fonctionne comme un EventBus.

class MyService : LifecycleService() {
    companion object {
        var BUS = MutableLiveData<Object>()
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)

        val testItem : Object

        // expose your data
        if (BUS.hasActiveObservers()) {
            BUS.postValue(testItem)
        }

        return START_NOT_STICKY
    }
}

Ajoutez ensuite un observateur de votre Activity.

MyService.BUS.observe(this, Observer {
    it?.let {
        // Do what you need to do here
    }
})

Vous pouvez lire plus de ce blog.

1
zeenosaur

La reliure est un autre moyen de communiquer

Créer un rappel

public interface MyCallBack{

   public void getResult(String result);

}

Côté activité:

  1. Implémenter l'interface dans l'activité
  2. Fournir l'implémentation pour la méthode
  3. Lier l'activité au service
  4. Enregistrez et annulez le rappel lorsque le service est lié et non lié à Activity.

    public class YourActivity extends AppCompatActivity implements MyCallBack{
    
          private Intent myIntent;
          private GPSService gpsService;
          private boolean bound = false;
    
          @Override
          public void getResult(String result){
           // show in textView textView.setText(result);
          }
    
          @Override
          protected void onStart()
          {
              super.onStart();
              bindService();
          }
    
          @Override
          protected void onStop() {
              super.onStop();
              unbindService();
          }
    
          private ServiceConnection serviceConnection = new ServiceConnection() {
    
                @Override
                public void onServiceConnected(ComponentName className, IBinder service) {
    
                      GPSService.GPSBinder binder = (GPSService.GPSBinder) service;
                      gpsService= binder.getService();
                      bound = true;
                      gpsService.registerCallBack(YourActivity.this); // register
    
               }
    
               @Override
               public void onServiceDisconnected(ComponentName arg0) {
                      bound = false;
               }
          };
    
          private void bindService() {
    
               bindService(notifyMeIntent, serviceConnection, Context.BIND_AUTO_CREATE);
          }
    
          private void unbindService(){
               if (bound) {
                     gpsService.registerCallBack(null); // unregister            
                     unbindService(serviceConnection);
                     bound = false;
                }
          }
    
          // Call this method somewhere to start Your GPSService
          private void startService(){
               myIntent = new Intent(this, GPSService.class);
               startService(myIntent );
          }
    
     }
    

Côté service:

  1. Initialiser le rappel
  2. Appeler la méthode de rappel chaque fois que nécessaire

     public class GPSService extends Service{
    
         private MyCallBack myCallback;
         private IBinder serviceBinder = new GPSBinder();
    
         public void registerCallBack(MyCallBack myCallback){
              this.myCallback= myCallback;
         }
    
         public class GPSBinder extends Binder{
    
             public GPSService getService(){
                  return GPSService.this;
             }
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent){
             return serviceBinder;
        }
     }
    
0
Rohit Singh

Comme mentionné par Madhur, vous pouvez utiliser un bus pour la communication.

Si vous utilisez un bus, vous avez quelques options:

Otto event Bus library (obsolète en faveur de RxJava)

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

EventBus de Green Robot

http://greenrobot.org/eventbus/

NYBus (RxBus, implémenté à l'aide de RxJava. Très similaire à EventBus)

https://github.com/MindorksOpenSource/NYBus

0
Mahpooya

Outre LocalBroadcastManager, Event Bus et Messenger déjà répondu à cette question, nous pouvons utiliser Intention en attente pour communiquer à partir du service.

Comme mentionné ici dans mon blog

La communication entre le service et l'activité peut être effectuée à l'aide de PendingIntent.For que nous pouvons utiliser createPendingResult (). createPendingResult () crée un nouveau fichier Objet PendingIntent que vous pouvez remettre au service à utiliser et à envoyer renvoyer les données à votre activité dans le rappel onActivityResult (int, int, Intent). Depuis un PendingIntent est colis, et peut donc être mis dans une intention supplémentaire, votre activité peut passer cela PendingIntent au service. Le service, à son tour, peut appeler send () méthode sur le PendingIntent pour notifier l’activité via onActivityResult d'un événement.

Activité

public class PendingIntentActivity extends AppCompatActivity
{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

PendingIntent pendingResult = createPendingResult(
100, new Intent(), 0);
Intent intent = new Intent(getApplicationContext(), PendingIntentService.class);
intent.putExtra("pendingIntent", pendingResult);
startService(intent);

}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 100 && resultCode==200) {
Toast.makeText(this,data.getStringExtra("name"),Toast.LENGTH_LONG).show();
}
super.onActivityResult(requestCode, resultCode, data);
}
}

Un service

public class PendingIntentService extends Service {

    private static final String[] items= { "lorem", "ipsum", "dolor",
            "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi",
            "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam",
            "vel", "erat", "placerat", "ante", "porttitor", "sodales",
            "pellentesque", "augue", "purus" };
    private PendingIntent data;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        data = intent.getParcelableExtra("pendingIntent");

        new LoadWordsThread().start();
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    class LoadWordsThread extends Thread {
        @Override
        public void run() {
            for (String item : items) {
                if (!isInterrupted()) {

                    Intent result = new Intent();
                    result.putExtra("name", item);
                    try {
                        data.send(PendingIntentService.this,200,result);
                    } catch (PendingIntent.CanceledException e) {

                        e.printStackTrace();
                    }
                    SystemClock.sleep(400);

                }
            }
        }
    }
}
0
Android Developer

Ma méthode:

Classe pour gérer l'envoi et la réception de messages depuis/vers le service/l'activité:

import Android.os.Bundle;
import Android.os.Handler;
import Android.os.IBinder;
import Android.os.Message;
import Android.os.Messenger;
import Android.os.RemoteException;
import Android.util.Log;

import Java.util.ArrayList;
import Java.util.List;

public class MessageManager {

    public interface IOnHandleMessage{
        // Messages
        int MSG_HANDSHAKE = 0x1;

        void onHandleMessage(Message msg);
    }

    private static final String LOGCAT = MessageManager.class.getSimpleName();

    private Messenger mMsgSender;
    private Messenger mMsgReceiver;
    private List<Message> mMessages;

    public MessageManager(IOnHandleMessage callback, IBinder target){
        mMsgReceiver = new Messenger(new MessageHandler(callback, MessageHandler.TYPE_ACTIVITY));
        mMsgSender = new Messenger(target);
        mMessages = new ArrayList<>();
    }

    public MessageManager(IOnHandleMessage callback){
        mMsgReceiver = new Messenger(new MessageHandler(callback, MessageHandler.TYPE_SERVICE));
        mMsgSender = null;
        mMessages = new ArrayList<>();
    }

    /* START Getter & Setter Methods */
    public Messenger getMsgSender() {
        return mMsgSender;
    }

    public void setMsgSender(Messenger sender) {
        this.mMsgSender = sender;
    }

    public Messenger getMsgReceiver() {
        return mMsgReceiver;
    }

    public void setMsgReceiver(Messenger receiver) {
        this.mMsgReceiver = receiver;
    }

    public List<Message> getLastMessages() {
        return mMessages;
    }

    public void addMessage(Message message) {
        this.mMessages.add(message);
    }
    /* END Getter & Setter Methods */

    /* START Public Methods */
    public void sendMessage(int what, int arg1, int arg2, Bundle msgData){
        if(mMsgSender != null && mMsgReceiver != null) {
            try {
                Message msg = Message.obtain(null, what, arg1, arg2);
                msg.replyTo = mMsgReceiver;
                if(msgData != null){
                    msg.setData(msgData);
                }
                mMsgSender.send(msg);
            } catch (RemoteException rE) {
                onException(rE);
            }
        }
    }

    public void sendHandshake(){
        if(mMsgSender != null && mMsgReceiver != null){
            sendMessage(IOnHandleMessage.MSG_HANDSHAKE, 0, 0, null);
        }
    }
    /* END Public Methods */

    /* START Private Methods */
    private void onException(Exception e){
        Log.e(LOGCAT, e.getMessage());
        e.printStackTrace();
    }
    /* END Private Methods */

    /** START Private Classes **/
    private class MessageHandler extends Handler {

        // Types
        final static int TYPE_SERVICE = 0x1;
        final static int TYPE_ACTIVITY = 0x2;

        private IOnHandleMessage mCallback;
        private int mType;

        public MessageHandler(IOnHandleMessage callback, int type){
            mCallback = callback;
            mType = type;
        }

        @Override
        public void handleMessage(Message msg){
            addMessage(msg);
            switch(msg.what){
                case IOnHandleMessage.MSG_HANDSHAKE:
                    switch(mType){
                        case TYPE_SERVICE:
                            setMsgSender(msg.replyTo);
                            sendHandshake();
                            break;
                        case TYPE_ACTIVITY:
                            Log.v(LOGCAT, "HERE");
                            break;
                    }
                    break;
                default:
                    if(mCallback != null){
                        mCallback.onHandleMessage(msg);
                    }
                    break;
            }
        }

    }
    /** END Private Classes **/

}

Exemple d'activité:

public class activity extends AppCompatActivity
      implements     ServiceConnection,
                     MessageManager.IOnHandleMessage { 

    [....]

    private MessageManager mMessenger;

    private void initMyMessenger(IBinder iBinder){
        mMessenger = new MessageManager(this, iBinder);
        mMessenger.sendHandshake();
    }

    private void bindToService(){
        Intent intent = new Intent(this, TagScanService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        /* START THE SERVICE IF NEEDED */
    }

    private void unbindToService(){
    /* UNBIND when you want (onDestroy, after operation...)
        if(mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }

    /* START Override MessageManager.IOnHandleMessage Methods */
    @Override
    public void onHandleMessage(Message msg) {
        switch(msg.what){
            case Constants.MSG_SYNC_PROGRESS:
                Bundle data = msg.getData();
                String text = data.getString(Constants.KEY_MSG_TEXT);
                setMessageProgress(text);
                break;
            case Constants.MSG_START_SYNC:
                onStartSync();
                break;
            case Constants.MSG_END_SYNC:
                onEndSync(msg.arg1 == Constants.ARG1_SUCCESS);
                mBound = false;
                break;
        }
    }
    /* END Override MessageManager.IOnHandleMessage Methods */

    /** START Override ServiceConnection Methods **/
    private class BLEScanServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            initMyMessenger(iBinder);
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mMessenger = null;
            mBound = false;
        }
    }
    /** END Override ServiceConnection Methods **/

Exemple de service:

public class Blablabla extends Service
    implements     MessageManager.IOnHandleMessage {

    [...]

    private MessageManager mMessenger;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        super.onBind(intent);
        initMessageManager();
        return mMessenger.getMsgReceiver().getBinder();
    }

    private void initMessageManager(){
        mMessenger = new MessageManager(this);
    }

    /* START Override IOnHandleMessage Methods */
    @Override
    public void onHandleMessage(Message msg) {
    /* Do what you want when u get a message looking the "what" attribute */
    }
    /* END Override IOnHandleMessage Methods */

Envoyer un message de Activity/Service:

mMessenger.sendMessage(what, arg1, arg2, dataBundle);

Comment ça marche:

lors de l'activité que vous démarrez ou que vous liez le service . Le service "OnBind" du service renvoie le classeur à son MessageManager, dans l'activité via la mise en oeuvre des méthodes d'interface "Connexion au service" "OnServiceConnected", vous obtenez cet IBinder et vous initialisez MessageManager. l’utiliser . Une fois que l’activité a initialisé son MessageManager, MessageHandler envoie et Handshake au service afin qu’il puisse définir son expéditeur "MessageHandler" (le "messager privé mMsgSender" dans MessageManager). Ce faisant, le service sait à qui envoie ses messages.

Vous pouvez également implémenter cela en utilisant un "expéditeur" List/Queue de Messenger dans MessageManager pour pouvoir envoyer plusieurs messages à différentes Activités/Services ou vous pouvez utiliser un "récepteur" List/Queue of Messenger dans le MessageManager pour pouvoir recevoir plusieurs messages. message de différentes activités/services.

Dans l'instance "MessageManager", vous avez une liste de tous les messages reçus.

Comme vous pouvez voir que la connexion entre "Activity's Messenger" et "Service Messenger" à l'aide de cette instance de "MessageManager" est automatique, elle est effectuée via la méthode "OnServiceConnected" et via l'utilisation de la "poignée de main".

J'espère que cela vous sera utile :) Merci beaucoup! Bye: D

0
Z3R0