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

Escribo mi primera aplicación de Android y trato de entender la comunicación entre servicios y actividades. Tengo un servicio que se ejecutará en segundo plano y hará algunos gps y el registro basado en el tiempo. Tendré una actividad que se utilizará para iniciar y detener el servicio.

Entonces, primero debo ser capaz de averiguar si el Servicio se está ejecutando cuando se inicia la Actividad. Aquí hay algunas otras preguntas al respecto, así que creo que puedo resolverlo (pero no dude en ofrecer consejos).

Mi verdadero problema: si la actividad se está ejecutando y se inicia el servicio, necesito que el servicio envíe mensajes a la actividad. Cadenas simples y enteros en este punto: mensajes de estado principalmente. Los mensajes no se transmitirán con regularidad, por lo que no creo que el servicio sea una buena manera de hacerlo si hay otra manera. Solo deseo esta comunicación cuando la Actividad haya sido iniciada por el usuario; no deseo 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 Actividad cuando ocurra algo interesante. Si no comienzas la actividad, no verás estos mensajes (no son tan interesantes).

Parece que debería poder determinar si el servicio se está ejecutando, y si es así, agregar la actividad como un oyente. A continuación, 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 crear un archivo AIDL para poder pasarlo a través de la interfaz remota del Servicio. Sin embargo, eso parece exagerado, y no tengo idea de cómo la Actividad debería 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 ejemplos: /apis/app/RemoteService.java

    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, me gustaría ir con la opción 3. Haga una referencia estática al servicio en sí y colóquelo en onCreate ():

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

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

    Luego, en Activity.onCreate() , inicia el servicio, espera asíncronamente hasta que el servicio se inicie realmente (puede hacer que su servicio notifique a su aplicación que está listo enviando un bash a la actividad) y obtenga su instancia. Cuando tenga la instancia, registre su objeto detector de servicio y se establecerá. NOTA: al editar vistas dentro de la actividad, debe modificarlas en el hilo de la interfaz de usuario, el servicio probablemente ejecutará su propio subproceso, por lo que debe llamar a Activity.runOnUiThread() .

    Lo último que debe hacer es eliminar la referencia a su objeto detector en Activity.onPause() ; de lo contrario, se perderá una instancia del contexto de su actividad, no es buena.

    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.

    El asker probablemente ya pasó de esto, 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.

    Agregue un BroadcastReceiver a su actividad. Regístrelo para recibir algún bash personalizado en onResume y anule el registro en onPause. Luego envíe esa intención de su servicio cuando desee enviar sus actualizaciones de estado o lo que sea.

    Asegúrate de no estar descontento si alguna otra aplicación escuchó tu intención (¿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:

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

    (RefreshTask.REFRESH_DATA_INTENT es solo una cadena constante).

    En mi actividad de escucha, 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 onResume para agregar:

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

    Y anulo onPause para agregar:

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

    Ahora mi actividad es escuchar mi servicio para decir “Oye, ve a actualizarte”. Podría pasar datos en el Intento en lugar de actualizar las tablas de la base de datos y luego volver a encontrar los cambios dentro de mi actividad, pero dado que deseo que los cambios persistan de todos modos, tiene sentido pasar los datos a través de db.

    Utilice LocalBroadcastManager para 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

    Me sorprende que nadie haya hecho referencia a la biblioteca de Otto event bus

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

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

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

    En la Actividad, crea un Manejador con un Mensajero 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(); } 

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

    El otro método que no se menciona en los otros comentarios es enlazar al servicio de la actividad utilizando bindService () y obtener una instancia del servicio en la callback de ServiceConnection. Como se describe aquí http://developer.android.com/guide/components/bound-services.html

    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 manera que funcionó para mí. Si necesita un ejemplo, solicítelo y publicaré algo.

    Para realizar un seguimiento en @MrSnowflake, responda con un ejemplo de código. Esta es la clase de Application XABBER ahora de código abierto . La clase de Application está centralizando y coordinando Listeners y ManagerInterfaces y más. Los gerentes de todo tipo son cargados dinámicamente. Activity´s iniciadas en el Xabber reportarán en qué tipo de Listener se encuentran. Y cuando un Service comienza, informe a la clase de la Application como iniciada. Ahora, para enviar un mensaje a una Activity todo lo que tienes que hacer es hacer que tu Activity convierta en un listener del tipo que necesitas. En OnStart() OnPause() register / unreg. El Service puede solicitarle a la clase de Application listener que necesita hablar y, si está allí, entonces la Actividad está lista para recibir.

    Pasando por la clase de Application verás que hay un botín más en marcha que esto.

    Como mencionó Madhur, puedes usar un autobús para comunicarse.

    En caso de utilizar un autobús, tiene algunas opciones:

    Otto event Bus library (obsoleto a favor de RxJava)

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

    Evento de Green Robot

    http://greenrobot.org/eventbus/

    NYBus (RxBus, implementado usando RxJava. Muy similar al EventBus)

    https://github.com/MindorksOpenSource/NYBus

    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 mencioné aquí en mi publicación de 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 de ActivityResult (int, int, Intent) callback. Dado que un PendingIntent es Parcelable, y por lo tanto puede incluirse en un Intent extra, su actividad puede pasar este PendingIntent al servicio. El servicio, a su vez, puede llamar al método send () en PendingIntent para notificar al actividad vía 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); } } } } }