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


Estoy escribiendo mi primera aplicación para Android y tratando de conseguir mi cabeza alrededor de la comunicación entre servicios y actividades. Tengo un Servicio que se ejecutará en segundo plano y hacer un poco de gps y registro basado en el tiempo. Tendré una Actividad que se utilizará para iniciar y detener el Servicio.

Así que primero, necesito ser capaz de averiguar si el Servicio se está ejecutando cuando se inicia la Actividad. Hay algunas otras preguntas aquí sobre eso, así que creo que puedo averiguarlo (pero siéntete libre para ofrecer asesoramiento).

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 enteros en este punto - mensajes de estado en su mayoría. Los mensajes no sucederán regularmente, así que no creo que encuestar al servicio sea una buena manera de hacerlo si hay otra manera. Solo quiero esta comunicación cuando la Actividad ha sido iniciada por el usuario-no quiero iniciar la Actividad desde el Servicio. En otros palabras, si inicia la Actividad y el Servicio se está ejecutando, verá algunos mensajes de estado en la interfaz de usuario 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 ser capaz de 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 realmente posible? La única manera que puedo imaginar para hacerlo es hacer que la Actividad implemente Parcelable y construya un archivo AIDL para que pueda pasarlo a través de la interfaz remota del Servicio. Sin embargo, eso parece exagerado, y no tengo idea de cómo debería implementar la Actividad 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 ejemplo de Google para manejar esto a través de AIDL en el directorio de muestras: / apis / app / RemoteService.java

Author: peterh, 2010-03-17

10 answers

Hay tres maneras obvias de comunicarse con los servicios:

  1. Usando Intents
  2. Usando AIDL
  3. Usando el propio objeto de servicio (como singleton)

En su caso, yo iría con la opción 3. Haga una referencia estática al servicio it self y rellénelo en onCreate ():

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

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

Luego, en Activity.onCreate() inicia el servicio, espere asíncronamente hasta que el servicio se inicie realmente (puedes hacer que tu servicio notifique a tu app que está lista enviando una intent a la actividad.) y obtener su instancia. Cuando tenga la instancia, registre su objeto de escucha de servicio en su servicio y estará configurado. NOTA: cuando edites Vistas dentro de la Actividad deberías modificarlas en el subproceso de IU, el servicio probablemente ejecutará su propio Subproceso, por lo que necesitas llamar a Activity.runOnUiThread().

Lo último que debe hacer es eliminar la referencia a su objeto de escucha en 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.

 77
Author: MrSnowflake,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-29 08:30:05

El preguntador probablemente ha pasado de esto, pero en caso de que alguien más busque esto...

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

Agrega un BroadcastReceiver a tu Actividad. Registrarse para recibir personalizado de intención en onResume y anular el registro de onPause. Luego envía esa intent desde tu servicio cuando quieras enviar tus actualizaciones de estado o lo que tengas.

Asegúrese de que no sería infeliz si alguna otra aplicación escuchó su intención (¿podría alguien 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 a mi receptor en la parte superior de la clase:

private DataUpdateReceiver dataUpdateReceiver;

Anulo onResume para añadir:

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

Y sobreescribo onPause para agregar:

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

Ahora mi actividad es escuchar a mi servicio para decir " Oye, ve a actualizarte."Podría pasar datos en la Intent en lugar de actualizar las tablas de la base de datos y luego volver a encontrar 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.

 239
Author: MaximumGoat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-25 05:02:32

Use LocalBroadcastManager para registrar un receptor para escuchar una transmisión enviada desde localservice dentro de su aplicación, la referencia va aquí:

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

 37
Author: Jack Gao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-27 11:41:11

Me sorprende que nadie haya dado referencia a Otto event Bus library

Http://square.github.io/otto/

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

 18
Author: Madhur Ahuja,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-11 11:22:14

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

En la Actividad, cree un Controlador 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 Mensajero 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();
}

Un ejemplo completo se puede encontrar en las demostraciones de la API: MessengerService y MessengerServiceActivity. Consulte el ejemplo completo para ver cómo funciona MyService.

 18
Author: Kjetil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-09 22:37:28

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

 7
Author: miguel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-04 20:35:20

Otra forma podría ser usar observadores con una clase de modelo falsa a través de la actividad y el propio servicio, implementando una variación de patrón MVC. No se si es la mejor manera de lograr esto, pero es la manera que funcionó para mí. Si necesitas algún ejemplo pídelo y publicaré algo.

 2
Author: ReDirEct__,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-26 13:58:03

Para seguir la respuesta de @MrSnowflake con un ejemplo de código. Esta es la clase XABBER now open source Application . La clase Application está centralizando y coordinando Listeners y ManagerInterfaces y más. Los gestores de todo tipo se cargan dinámicamente. Activity´s iniciado en el Xabber informará en qué tipo de Listener son. Y cuando un Service iniciarlo informe a la clase Application como iniciado. Ahora para enviar un mensaje a un Activity todo lo que tienes que hacer es hacer que tu Activity se convierta en un listener de qué tipo lo necesitas. En el OnStart() OnPause() registrarse / unreg. El Service puede pedir a la clase Application solo que listener necesita hablar y si está allí, entonces la Actividad está lista para recibir.

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

 2
Author: Erik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-22 10:46:45

Como menciona Madhur, puede usar un bus para la comunicación.

En caso de usar un Bus tienes algunas opciones:

Biblioteca Otto event Bus (en desuso a favor de RxJava)

Http://square.github.io/otto/

EventBus del robot verde

Http://greenrobot.org/eventbus /

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

Https://github.com/MindorksOpenSource/NYBus

 0
Author: Mahpooya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-07 13:41:57

Además de LocalBroadcastManager , Event Bus y Messenger ya contestados en esta pregunta,podemos usar Pending Intent para comunicarnos desde el servicio.

Como se mencionó aquí en mi entrada de blog

La comunicación entre el servicio y la actividad se puede hacer utilizando PendingIntent.Para eso podemos usar createPendingResult ().createPendingResult () crea un nuevo PendingIntent objeto que puede entregar al servicio para usar y enviar resultado datos de vuelta a su actividad dentro de onActivityResult (int, int, Intención) callback.Puesto que un PendingIntent es Parcelable, y puede por lo tanto se pone en una intención extra, su actividad puede pasar esto PendingIntent al servicio.El servicio, a su vez, puede llamar enviar() método sobre el PendingIntent para notificar la actividad a través de Onactivity Result of an event.

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

                }
            }
        }
    }
}
 0
Author: Bhuvnesh Varma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-27 09:08:45