Cómo hacer que el servicio de Android se comunique con Activity


251

Estoy escribiendo mi primera aplicación de Android y estoy tratando de entender la comunicación entre servicios y actividades. Tengo un Servicio que se ejecutará en segundo plano y que hará algunos gps y registros basados ​​en el tiempo. Tendré una Actividad que se utilizará para iniciar y detener el Servicio.

Primero, necesito poder determinar si el Servicio se está ejecutando cuando se inicia la Actividad. Hay algunas otras preguntas aquí sobre eso, así que creo que puedo resolverlo (pero siéntase libre de ofrecer consejos).

Mi verdadero problema: si la Actividad se está ejecutando y se inicia el Servicio, necesito una forma para que el Servicio envíe mensajes a la Actividad. Cadenas simples y enteros en este punto: mensajes de estado en su mayoría. Los mensajes no sucederán regularmente, por lo que no creo que sondear el servicio sea una buena opción si hay otra. Solo quiero esta comunicación cuando el usuario ha iniciado la Actividad; no quiero iniciar la Actividad desde el Servicio. En otras palabras, si inicia la Actividad y el Servicio se está ejecutando, verá algunos mensajes de estado en la IU de la Actividad cuando ocurra algo interesante. Si no inicia la Actividad, no verá estos mensajes (no son tan interesantes).

Parece que debería poder determinar si el Servicio se está ejecutando y, de ser así, agregar la Actividad como escucha. Luego, elimine la Actividad como oyente cuando la Actividad se detenga o se detenga. ¿Es eso realmente posible? La única forma en que puedo resolverlo es hacer que la Actividad implemente Parcelable y cree un archivo AIDL para poder pasarlo a través de la interfaz remota del Servicio. Sin embargo, parece una exageración, y no tengo idea de cómo la Actividad debe implementar writeToParcel () / readFromParcel ().

¿Hay una manera más fácil o mejor? Gracias por cualquier ayuda.

EDITAR:

Para cualquiera que esté interesado en esto más adelante, hay un código de muestra de Google para manejar esto a través de AIDL en el directorio de muestras: /apis/app/RemoteService.java

Respuestas:


94

Hay tres formas obvias de comunicarse con los servicios:

  1. Usando intenciones
  2. Usando AIDL
  3. Usar el objeto de servicio en sí (como singleton)

En su caso, iría con la opción 3. Haga una referencia estática al servicio en sí mismo y complételo en onCreate ():

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

Realiza una función estática MyService getInstance(), que devuelve la estática sInstance.

Luego, Activity.onCreate()cuando inicie el servicio, espere asincrónicamente hasta que el servicio se inicie realmente (puede hacer que su servicio notifique a su aplicación que está listo enviando una intención a la actividad) y obtenga su instancia. Cuando tenga la instancia, registre su objeto de escucha de servicio para su servicio y estará listo. NOTA: cuando edite Vistas dentro de la Actividad, debe modificarlas en el hilo de la IU, el servicio probablemente ejecutará su propio Hilo, por lo que debe llamar Activity.runOnUiThread().

Lo último que debe hacer es eliminar la referencia a su objeto de escucha Activity.onPause(), de lo contrario, una instancia de su contexto de actividad se filtrará, no es bueno.

NOTA: Este método solo es útil cuando su aplicación / Actividad / tarea es el único proceso que accederá a su servicio. Si este no es el caso, debe usar la opción 1. o 2.


2
¿Cómo puedo hacer busy-waitla actividad? ¿Puedes explicar por favor?
iTurki

1
Entonces, después de que onCreate o después de que onStartCommand en su clase de servicio establezca una variable estática pública, llámela isConnected o algo verdadero. Luego, en su Actividad, haga esto: Intención intención = nueva intención (acto, YourService.class); startService (intento); while (! YourService.isConnected) {sleep (300); } Después de ese ciclo, su servicio se está ejecutando y puede comunicarse con él. En mi experiencia, definitivamente hay algunas limitaciones para interactuar con un servicio como este.
Matt Wolfe

¿Por qué solo se puede iniciar en Activity.onCreate()?
skywall

¿Alguna palabra sobre por qué no querría usar Intentsenviando datos a través de Parcellablemensajes entre ellos?
Aman Alam

2
Esto funciona, pero la respuesta de @ MaximumGoat es mucho más limpia y no implica ninguna espera ocupada.
Matt

262

El autor de la pregunta probablemente ya pasó hace mucho tiempo, pero en caso de que alguien más busque esto ...

Hay otra forma de manejar esto, que creo que podría ser la más simple.

Agrega un BroadcastReceivera tu actividad. Regístrese para recibir alguna intención personalizada onResumey anule el registro onPause. Luego envíe esa intención desde su servicio cuando desee enviar sus actualizaciones de estado o lo que tenga.

Asegúrate de no ser infeliz si alguna otra aplicación te escuchara Intent(¿alguien podría hacer algo malicioso?), Pero más allá de eso, deberías estar bien.

Se solicitó una muestra de código:

En mi servicio, tengo esto:

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

( RefreshTask.REFRESH_DATA_INTENTes solo una cadena constante).

En mi actividad auditiva, defino mi 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
        }
    }
}

Declaro mi receptor en la parte superior de la clase:

private DataUpdateReceiver dataUpdateReceiver;

Anulo onResumepara agregar esto:

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

Y anulo onPausepara agregar:

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

Ahora mi actividad está escuchando mi servicio para decir "Hey, ve y actualízate". Podría pasar datos en Intentlugar de actualizar las tablas de la base de datos y luego regresar para encontrar los cambios dentro de mi actividad, pero dado que quiero que los cambios persistan de todos modos, tiene sentido pasar los datos a través de DB.


55
¿Cómo? Lo que describe es exactamente lo que necesito. Pero más que eso, necesito un código de muestra completo. Gracias por adelantado. Para su información, la última vez que intenté escribir un widget, la parte de mensajería me llevó 2 meses. ¡Ríete si quieres, pero tus expertos necesitan publicar más ya que IMHO Android es más complicado que iOS!

1
aaaaaaa, la Guía del codificador ocupado para el desarrollo avanzado de Android tiene un excelente capítulo sobre widgets. Es el único recurso que me ayudó a superar ese desastre. CommonsWare, el autor, tiene 115k de reputación en StackOverflow, y recomiendo el libro. commonsware.com/AdvAndroid
MaximumGoat

64
Broadcastsson fantásticos y, además, si no desea que su transmisión vaya más allá de su propio proceso, considere usar un LocalBroadcast: developer.android.com/reference/android/support/v4/content/…
Cody Caughlan

2
Gracias Cody, no había visto eso antes.
MaximumGoat

3
Esta es una muy buena forma de comunicación. Pero, ¿qué hacer en caso de que se entreguen los resultados, cuando la actividad estaba en estado de pausa? Entonces, después de la actividad de onResume, puede esperar mucho tiempo para obtener resultados. Y no se recibirán datos, porque los datos se perdieron.
Anton-M

39

Use LocalBroadcastManagerpara registrar un receptor para escuchar una transmisión enviada desde el servicio local dentro de su aplicación, la referencia va aquí:

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


3
Sí, una muy buena discusión sobre localbroadcast aquí enlace
SohailAziz

1
LocalBroadcastManagerahora está en desuso a favor LiveDatau otras herramientas de patrones de observación: developer.android.com/reference/androidx/localbroadcastmanager/…
Ali Nem

20

Me sorprende que nadie haya hecho referencia a la biblioteca de autobuses de eventos de Otto.

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

He estado usando esto en mis aplicaciones de Android y funciona a la perfección.


77
Alternativamente, existe la biblioteca EventBus de greenrobot .
Jazzer

15
Pero recuerde que EventBus y otto siempre están en un proceso. Cuando está utilizando un servicio que está en su propio proceso (comenzó con la configuración de android:process=":my_service"que no pueden comunicarse.)
Ron

20

Usar un Messenger es otra forma simple de comunicarse entre un Servicio y una Actividad.

En la actividad, cree un controlador con un Messenger correspondiente. Esto manejará los mensajes de su Servicio.

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

El Messenger se puede pasar al servicio adjuntándolo a un Mensaje:

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

Puede encontrar un ejemplo completo en las demostraciones de API: MessengerService y MessengerServiceActivity . Consulte el ejemplo completo de cómo funciona MyService.


1
¡Muy apreciado! Funciona perfectamente. Solo un detalle en el código: myService.send(message);debería ser messenger.send(message);en su lugar.
Federico Cristina


9

También puede usar LiveDataque funciona como 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
    }
}

Luego agregue un observador de su Activity.

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

Puedes leer más de este blog.


2

Otra forma podría ser utilizar observadores con una clase de modelo falso a través de la actividad y el servicio en sí, implementando una variación de patrón MVC. No sé si es la mejor manera de lograr esto, pero es la forma en que funcionó para mí. Si necesita algún ejemplo, solicítelo y publicaré algo.


Estoy luchando con un pensamiento similar. Tengo actividad y 2 servicios que tienen el mismo modelo. El modelo se puede actualizar en cualquiera de las actividades o en cualquiera de esos servicios, así que pensé en usar Observers de alguna manera, pero no puedo entenderlo. ¿Puedes publicar un ejemplo para mostrar tu idea?
pzo

2

Mi metodo:

Clase para gestionar enviar y recibir mensajes de / al servicio / actividad:

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 **/

}

En el ejemplo de actividad:

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 **/

En servicio Ejemplo:

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 */

Enviar un mensaje de Actividad / Servicio:

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

Cómo funciona esto:

en la actividad que inicia o vincula el servicio. Los métodos de servicio "OnBind" devuelven el Binder a su MessageManager, y en la Actividad a través de la implementación de métodos de interfaz de "Conexión de servicio" "OnServiceConnected" obtiene este IBinder e inicia su MessageManager usándolo. Después de que la Actividad haya iniciado su MessageManager, MessageHandler envía y Handshake al servicio para que pueda configurar su remitente "MessageHandler" (el "Messenger mMsgSender privado" en MessageManager). Al hacer esto, el servicio sabe a quién envía sus mensajes.

También puede implementar esto usando un "remitente" de Lista / Cola de Messenger en el Administrador de mensajes para que pueda enviar múltiples mensajes a diferentes Actividades / Servicios o puede usar un "receptor" de Lista / Cola de Mensajero en el Administrador de mensajes para que pueda recibir múltiples mensaje de diferentes actividades / servicios.

En la instancia "MessageManager" tiene una lista de todos los mensajes recibidos.

Como puede ver, la conexión entre "Activity Messenger" y "Service Messenger" utilizando esta instancia de "MessageManager" es automática, se realiza mediante el método "OnServiceConnected" y mediante el uso del "Handshake".

Espero que esto sea útil para ti :) ¡Muchas gracias! Adiós: D


2

Para dar seguimiento a @MrSnowflake, responda con un ejemplo de código. Esta es la XABBER ahora de código abierto Applicationde clase . La Applicationclase se está centralizando y coordinando Listenersy ManagerInterfaces y más. Los gerentes de todo tipo se cargan dinámicamente. Activity´siniciado en el Xabber informará en qué tipo de Listenerson. Y cuando Servicecomienza, informa a la Applicationclase como iniciada. Ahora, para enviar un mensaje a Activitytodo lo que tiene que hacer es convertirse Activityen uno listenerde los tipos que necesita. En el OnStart() OnPause()registro / unreg. La Servicepuede pedir a la Applicationclase para sólo que listenerse necesita hablar con y si está allí entonces la actividad está listo para recibir.

Al pasar por la Applicationclase verás que hay mucho más en juego que esto.


El enlace proporcionado ahora da como resultado un github 404. ¿Se movió?
JE Carter II

Sí, parece funcionar ahora. Debe haber sido un problema temporal en github.
JE Carter II

1

El enlace es otra forma de comunicarse

Crear una devolución de llamada

public interface MyCallBack{

   public void getResult(String result);

}

Lado de la actividad:

  1. Implemente la interfaz en la actividad

  2. Proporcionar la implementación del método.

  3. Vincula la actividad al servicio

  4. Registre y anule el registro de devolución de llamada cuando el Servicio se vincula y se desvincula con Activity.

    public class YourActivity extends AppCompatActivity implements MyCallBack{
    
          private Intent notifyMeIntent;
          private GPSService gpsService;
          private boolean bound = false;
    
          @Override
          public void onCreate(Bundle sis){
    
              // activity code ...
    
              startGPSService();
    
          }
    
          @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 startGPSService(){
               notifyMeIntent = new Intent(this, GPSService.class);
               startService(myIntent );
          }
    
     }

Lado del servicio:

  1. Inicializar devolución de llamada

  2. Invoque el método de devolución de llamada cuando sea necesario

     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;
        }
     }

el serviceConnectionestá fuera de onCreate. Por favor edítelo.
Adeel Zafar

No tiene que estar adentro en Crear
Rohit Singh


0

Además de LocalBroadcastManager, Event Bus y Messenger ya respondieron en esta pregunta, podemos usar la intención pendiente para comunicarnos desde el servicio.

Como se menciona aquí en mi blog

La comunicación entre el servicio y la Actividad se puede hacer usando PendingIntent. Para eso podemos usar createPendingResult () .createPendingResult () crea un nuevo objeto PendingIntent que puede entregar al servicio para usar y enviar datos de resultados a su actividad dentro deActivityResult (int, int, Intent) callback. Dado que un PendingIntent es Parcelable y, por lo tanto, se puede poner en un Intent extra, su actividad puede pasar este PendingIntent al servicio. El servicio, a su vez, puede llamar al método send () en el PendingIntent para notificar al actividad a través de onActivityResult de un evento.

Actividad

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

Servicio

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);

                }
            }
        }
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.