Cómo hacer que el servicio de Android se comunique con la actividad
Estoy escribiendo mi primera aplicación para Android y tratando de entender la comunicación entre servicios y actividades. Tengo un servicio que se ejecutará en segundo plano y realizará algunos registros basados en tiempo y GPS. 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 no dudes en ofrecer consejos).
Mi verdadero problema: si la Actividad se está ejecutando y el Servicio se inicia, necesito una forma para que el Servicio envíe mensajes a la Actividad. Cadenas simples y números enteros en este punto: principalmente mensajes de estado. Los mensajes no se enviarán con regularidad, por lo que no creo que sondear el servicio sea una buena manera de hacerlo si hay otra manera. Solo quiero esta comunicación cuando el usuario haya 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 UI de la Actividad cuando suceda algo interesante. Si no inicias la Actividad, no verás 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 hacerlo 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, eso parece excesivo y no tengo idea de cómo la Actividad debería implementar writeToParcel()/readFromParcel().
¿Existe 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
El autor de la pregunta probablemente ya haya superado esto 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 sencilla.
Añade un BroadcastReceiver
a tu actividad. Regístrelo para recibir alguna intención personalizada onResume
y cancele su registro en onPause
. Luego envíe esa intención desde su servicio cuando desee enviar sus actualizaciones de estado o lo que sea.
Asegúrate de no sentirte infeliz si alguna otra aplicación te escucha 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_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 a mi receptor como el mejor de la clase:
private DataUpdateReceiver dataUpdateReceiver;
Anulo onResume
para agregar esto:
if (dataUpdateReceiver == null) dataUpdateReceiver = new DataUpdateReceiver();
IntentFilter intentFilter = new IntentFilter(RefreshTask.REFRESH_DATA_INTENT);
registerReceiver(dataUpdateReceiver, intentFilter);
Y lo anulo onPause
para agregar:
if (dataUpdateReceiver != null) unregisterReceiver(dataUpdateReceiver);
Ahora mi actividad está escuchando a mi servicio para decir "Oye, actualízate". Podría pasar datos en Intent
lugar de actualizar las tablas de la base de datos y luego volver a buscar los cambios dentro de mi actividad, pero como quiero que los cambios persistan de todos modos, tiene sentido pasar los datos a través de la base de datos.
Hay tres formas obvias de comunicarse con los servicios:
- Usando intenciones
- Usando AIDL
- Usando el objeto de servicio en sí (como singleton)
En su caso, elegiría la opción 3. Haga una referencia estática al servicio en sí y complétela en onCreate():
void onCreate(Intent i) {
sInstance = this;
}
Haga una función estática MyService getInstance()
, que devuelva la estática sInstance
.
Luego, al Activity.onCreate()
iniciar el servicio, espere de forma asincrónica hasta que el servicio se inicie realmente (puede hacer que su servicio notifique a su aplicación que está lista enviando una intención a la actividad) y obtenga su instancia. Cuando tenga la instancia, registre su objeto de escucha de servicio en su servicio y estará listo. NOTA: al editar Vistas dentro de la Actividad, debe modificarlas en el subproceso de la interfaz de usuario; el servicio probablemente ejecutará su propio subproceso, por lo que deberá llamar a Activity.runOnUiThread()
.
Lo último que debe hacer es eliminar la referencia a su objeto de escucha en Activity.onPause()
; de lo contrario, se filtrará una instancia del contexto de su actividad, lo cual 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 tienes que utilizar la opción 1. o 2.
Úselo 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
También puedes utilizarlo LiveData
que funciona como un archivo EventBus
.
class MyService : LifecycleService() {
companion object {
val BUS = MutableLiveData<Any>()
}
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 en este blog.
Usar Messenger es otra forma sencilla 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.