Como fazer com que o Serviço Android se comunique com a Atividade


251

Estou escrevendo meu primeiro aplicativo Android e tentando entender a comunicação entre serviços e atividades. Eu tenho um serviço que será executado em segundo plano e fará alguns logs baseados em tempo e gps. Terei uma Atividade que será usada para iniciar e parar o Serviço.

Então, primeiro, preciso descobrir se o Serviço está sendo executado quando a Atividade é iniciada. Existem algumas outras perguntas aqui sobre isso, então acho que posso descobrir isso (mas fique à vontade para oferecer conselhos).

Meu verdadeiro problema: se a Atividade estiver em execução e o Serviço for iniciado, preciso de uma maneira para o Serviço enviar mensagens para a Atividade. Sequências simples e números inteiros neste momento - mensagens de status principalmente. As mensagens não acontecerão regularmente, então não acho que pesquisar o serviço seja um bom caminho a percorrer, se houver outro caminho. Eu só quero essa comunicação quando a Atividade tiver sido iniciada pelo usuário - não quero iniciar a Atividade pelo Serviço. Em outras palavras, se você iniciar a Atividade e o Serviço estiver em execução, verá algumas mensagens de status na IU da Atividade quando algo interessante acontecer. Se você não iniciar a atividade, não verá essas mensagens (elas não são tão interessantes).

Parece que eu devo determinar se o Serviço está em execução e, se estiver, adicione a Atividade como ouvinte. Em seguida, remova a Atividade como ouvinte quando a Atividade pausar ou parar. Isso é realmente possível? A única maneira de descobrir isso é fazer com que a Activity implemente o Parcelable e crie um arquivo AIDL para que eu possa transmiti-lo pela interface remota do Serviço. Isso parece um exagero, e eu não tenho idéia de como a Atividade deve implementar writeToParcel () / readFromParcel ().

Existe uma maneira mais fácil ou melhor? Obrigado por qualquer ajuda.

EDITAR:

Para quem estiver interessado nisso posteriormente, há um código de exemplo do Google para lidar com isso via AIDL no diretório de exemplos: /apis/app/RemoteService.java

Respostas:


94

Existem três maneiras óbvias de se comunicar com os serviços:

  1. Usando intenções
  2. Usando AIDL
  3. Usando o próprio objeto de serviço (como singleton)

No seu caso, eu usaria a opção 3. Faça uma referência estática ao serviço em si e preencha-o em onCreate ():

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

Faça uma função estática MyService getInstance(), que retorna a estática sInstance.

Em seguida, ao Activity.onCreate()iniciar o serviço, aguarde de forma assíncrona até que o serviço seja realmente iniciado (você poderá notificar seu aplicativo de que está pronto enviando uma intenção para a atividade.) E obter sua instância. Quando você tiver a instância, registre seu objeto de ouvinte de serviço e estará pronto. OBSERVAÇÃO: ao editar as vistas dentro da atividade, você deve modificá-las no segmento da interface do usuário, o serviço provavelmente executará seu próprio segmento, portanto, é necessário ligar Activity.runOnUiThread().

A última coisa que você precisa fazer é remover a referência ao objeto do ouvinte Activity.onPause(), caso contrário, uma instância do seu contexto de atividade vazará, nada bom.

NOTA: Este método é útil apenas quando seu aplicativo / Atividade / tarefa é o único processo que acessará seu serviço. Se não for esse o caso, você precisará usar a opção 1. ou 2.


2
Como posso busy-waita atividade? Você pode explicar por favor?
iTurki

1
Portanto, após onCreate ou onStartCommand em sua classe de serviço, defina uma variável estática pública, chame-a isConnected ou algo como true. Em sua Atividade, faça o seguinte: Intent intent = new Intent (act, YourService.class); startService (intenção); while (! YourService.isConnected) {sono (300); } Após esse loop, seu serviço está sendo executado e você pode se comunicar com ele. Na minha experiência, existem definitivamente algumas limitações para interagir com um serviço como este.
Matt Wolfe

Por que ele pode ser iniciado apenas em Activity.onCreate()?
Skywall

Alguma palavra sobre por que você não gostaria de usar Intentsenviando dados através de Parcellablemensagens entre eles?
Aman Alam

2
Isso funciona, mas a resposta do @ MaximumGoat é muito mais limpa e não envolve nenhuma espera ocupada.
Matt

262

O solicitante provavelmente já passou disso, mas no caso de alguém procurar por isso ...

Há outra maneira de lidar com isso, que eu acho que pode ser a mais simples.

Adicione um BroadcastReceivera sua atividade. Registre-o para receber alguma intenção personalizada onResumee cancelar o registro onPause. Em seguida, envie essa intenção do seu serviço quando desejar enviar suas atualizações de status ou o que você tem.

Certifique-se de que você não ficaria infeliz se algum outro aplicativo o escutasse Intent(alguém poderia fazer algo malicioso?), Mas, além disso, você deve ficar bem.

Exemplo de código foi solicitado:

No meu serviço, eu tenho o seguinte:

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

( RefreshTask.REFRESH_DATA_INTENTé apenas uma string constante.)

Na minha atividade de escuta, defino meu 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 meu destinatário no topo da classe:

private DataUpdateReceiver dataUpdateReceiver;

Eu substituo onResumepara adicionar isso:

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

E substituo onPausepara adicionar:

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

Agora, minha atividade está ouvindo meu serviço dizer "Ei, atualize-se". Eu poderia passar dados nas Intenttabelas do banco de dados em vez de atualizar e depois voltar para encontrar as alterações na minha atividade, mas como eu quero que as alterações persistam de qualquer maneira, faz sentido passar os dados via DB.


5
Quão? O que você descreve é ​​exatamente o que eu preciso. Mais do que isso, preciso de um código de amostra completo. Desde já, obrigado. Para sua informação, na última vez em que tentei escrever um widget, a parte de mensagens levou 2 meses. Ria se você quiser, mas seus especialistas precisam publicar mais, já que o IMHO Android é mais complicado que o iOS!

1
aaaaaaa, O Guia do codificador ocupado para o desenvolvimento avançado do Android tem um excelente capítulo sobre widgets. É o único recurso que me fez passar por essa bagunça. O CommonsWare, o autor, tem 115k de reputação no StackOverflow, e eu recomendo o livro. commonsware.com/AdvAndroid
MaximumGoat

64
Broadcastssão fantásticos e, além disso, se você não quer que sua transmissão para ir além do seu próprio processo, em seguida, considere o uso de um LocalBroadcast: developer.android.com/reference/android/support/v4/content/...
Cody Caughlan

2
Obrigado Cody, eu nunca tinha visto isso antes.
MaximumGoat 27/02/12

3
Esta é uma maneira muito boa de comunicação. Mas o que fazer caso os resultados sejam entregues, quando a atividade está em estado de pausa? Portanto, após a atividade onResume, pode esperar muito tempo pelos resultados. E nenhum dado será recebido porque os dados foram perdidos.
Anton-H

39

Use LocalBroadcastManagerpara registrar um receptor e ouvir uma transmissão enviada pelo serviço local dentro do seu aplicativo. A referência é aqui:

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


3
Sim, um muito bom discussão sobre localbroadcast aqui ligação
SohailAziz

1
LocalBroadcastManageragora está obsoleto em favor de LiveDataou outras ferramentas de padrão de observador: developer.android.com/reference/androidx/localbroadcastmanager/…
Ali Nem

20

Estou surpreso que ninguém tenha dado referência à biblioteca de ônibus do evento Otto

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

Eu tenho usado isso em meus aplicativos Android e funciona perfeitamente.


7
Como alternativa, existe a biblioteca EventBus do greenrobot .
Jazzer

15
Mas lembre-se de que EventBus e otto estão sempre em um processo. Quando você está usando um serviço que está em seu próprio processo (iniciado com a configuração de que android:process=":my_service"eles não são capazes de se comunicar.
Ron

20

Usar um Messenger é outra maneira simples de se comunicar entre um Serviço e uma Atividade.

Na atividade, crie um manipulador com um Messenger correspondente. Isso manipulará as mensagens do seu serviço.

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

O Messenger pode ser passado para o serviço anexando-o a uma Mensagem:

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

Um exemplo completo pode ser encontrado nas demos da API: MessengerService e MessengerServiceActivity . Consulte o exemplo completo de como o MyService funciona.


1
Muito apreciado! Funciona perfeitamente. Apenas um detalhe no código: myService.send(message);deve ser o messenger.send(message);contrário.
Federico Cristina


9

Você também pode usar LiveDataisso funciona como um 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
    }
}

Em seguida, adicione um observador do seu Activity.

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

Você pode ler mais neste blog.


2

Outra maneira poderia ser usar observadores com uma classe de modelo falsa por meio da atividade e do próprio serviço, implementando uma variação de padrão MVC. Não sei se é a melhor maneira de conseguir isso, mas é a maneira que funcionou para mim. Se você precisar de algum exemplo, peça e eu publicarei algo.


Estou lutando com pensamentos semelhantes. Eu tenho atividade e 2 serviços que têm o mesmo modelo. O modelo pode ser atualizado em qualquer atividade ou em qualquer um desses serviços, então pensei em usar os Observadores de alguma forma, mas não consigo descobrir. Você pode postar um exemplo para mostrar sua ideia?
Pzo

2

Meu método:

Classe para gerenciar o envio e recebimento de mensagens de / para serviço / atividade:

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

}

No exemplo de atividade:

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

Exemplo no serviço:

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

Envie uma mensagem da Atividade / Serviço:

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

Como isso funciona:

na atividade que você inicia ou liga o serviço. Os métodos de serviço "OnBind" retornam o fichário ao MessageManager, na atividade por meio da implementação de métodos de interface "Conexão de serviço" "OnServiceConnected" você obtém esse IBinder e inicia o MessageManager usando-o. Após a Atividade iniciar o MessageManager, o MessageHandler envia e Handshake para o serviço, para que ele possa definir o remetente "MessageHandler" (o "Messenger particular mMsgSender;" no MessageManager). Fazendo isso, o serviço sabe para quem envia suas mensagens.

Você também pode implementá-lo usando um "remetente" Lista / Fila de Messenger no MessageManager para poder enviar várias mensagens para diferentes Atividades / Serviços ou usar um "receptor" Lista / Fila de Messenger no MessageManager para receber vários mensagem de diferentes Atividades / Serviços.

Na instância "MessageManager", você tem uma lista de todas as mensagens recebidas.

Como você pode ver a conexão entre o "Activity's Messenger" e o "Service Messenger" usando esta instância "MessageManager" é automática, isso é feito pelo método "OnServiceConnected" e pelo uso do "Handshake".

Espero que isso seja útil para você :) Muito obrigado! Tchau: D


2

Para acompanhar a resposta do @MrSnowflake com um exemplo de código. Esta é a XABBER agora open source Applicationclasse . A Applicationturma está centralizando e coordenando Listenerse ManagerInterfaces e muito mais. Gerentes de todos os tipos são carregados dinamicamente. Activity´siniciado no Xabber relatará em que tipo Listenereles são. E quando um Serviceinício é reportado à Applicationturma como iniciado. Agora, para enviar uma mensagem a Activitytudo o que você precisa fazer é tornar- Activityse um listenerde que tipo você precisa. No OnStart() OnPause()registro / unreg. O Servicealuno pode pedir à Applicationclasse apenas o que listenerprecisa falar e, se estiver lá, a Atividade está pronta para receber.

Passando pela Applicationaula, você verá que há muito mais acontecendo então isso.


O link fornecido agora resulta em um github 404. Ele foi movido?
JE Carter II

Sim, parece funcionar agora. Deve ter sido um problema temporário no github.
JE Carter II

1

A ligação é outra maneira de se comunicar

Criar um retorno de chamada

public interface MyCallBack{

   public void getResult(String result);

}

Lado da atividade:

  1. Implemente a interface na atividade

  2. Forneça a implementação para o método

  3. Vincular a atividade ao serviço

  4. Registre e cancele o registro do retorno de chamada quando o Serviço for vinculado e desvinculado da Atividade.

    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 do serviço:

  1. Inicializar retorno de chamada

  2. Invoque o método de retorno de chamada sempre que necessário

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

você serviceConnectionestá fora de onCreate. Por favor edite-o.
Adeel Zafar

Não precisa estar dentro de onCreate
Rohit Singh


0

Além do LocalBroadcastManager, o Event Bus e o Messenger já responderam nesta pergunta, podemos usar a intenção pendente para se comunicar pelo serviço.

Como mencionado aqui no meu blog

A comunicação entre o serviço e a Atividade pode ser feita usando PendingIntent.Para que possamos usar createPendingResult () .createPendingResult () cria um novo objeto PendingIntent que você pode entregar ao serviço para usar e enviar dados de resultado para sua atividade dentro de onActivityResult (int, int, Intent) callback. Como um PendingIntent é Parcelable e, portanto, pode ser colocado em um Intent extra, sua atividade pode transmitir esse PendingIntent para o serviço. O serviço, por sua vez, pode chamar o método send () no PendingIntent para notificar o atividade via onActivityResult de um evento.

Atividade

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

Serviço

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

                }
            }
        }
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.