Ejemplo: comunicación entre actividad y servicio usando mensajería

No pude encontrar ningún ejemplo de cómo enviar mensajes entre una actividad y un servicio, y he pasado demasiadas horas averiguando esto. Aquí hay un proyecto de ejemplo para que otros hagan referencia.

Este ejemplo le permite iniciar o detener un servicio directamente y vincularlo / desvincularlo por separado del servicio. Cuando el servicio se está ejecutando, incrementa un número a 10 Hz. Si la actividad está vinculada al Service , mostrará el valor actual. Los datos se transfieren como un Entero y como una Cadena para que pueda ver cómo hacerlo de dos maneras diferentes. También hay botones en la actividad para enviar mensajes al servicio (cambia el valor de incremento).

Captura de pantalla:

Captura de pantalla del ejemplo de mensaje de servicio de Android

AndroidManifest.xml:

              

res \ values ​​\ strings.xml:

   ExampleService Example Service started Example Service Label  

res \ layout \ main.xml:

                

src \ com.exampleservice \ MainActivity.java:

 package com.exampleservice; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; 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 android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity { Button btnStart, btnStop, btnBind, btnUnbind, btnUpby1, btnUpby10; TextView textStatus, textIntValue, textStrValue; Messenger mService = null; boolean mIsBound; final Messenger mMessenger = new Messenger(new IncomingHandler()); class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyService.MSG_SET_INT_VALUE: textIntValue.setText("Int Message: " + msg.arg1); break; case MyService.MSG_SET_STRING_VALUE: String str1 = msg.getData().getString("str1"); textStrValue.setText("Str Message: " + str1); break; default: super.handleMessage(msg); } } } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { mService = new Messenger(service); textStatus.setText("Attached."); try { Message msg = Message.obtain(null, MyService.MSG_REGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // In this case the service has crashed before we could even do anything with it } } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been unexpectedly disconnected - process crashed. mService = null; textStatus.setText("Disconnected."); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnStart = (Button)findViewById(R.id.btnStart); btnStop = (Button)findViewById(R.id.btnStop); btnBind = (Button)findViewById(R.id.btnBind); btnUnbind = (Button)findViewById(R.id.btnUnbind); textStatus = (TextView)findViewById(R.id.textStatus); textIntValue = (TextView)findViewById(R.id.textIntValue); textStrValue = (TextView)findViewById(R.id.textStrValue); btnUpby1 = (Button)findViewById(R.id.btnUpby1); btnUpby10 = (Button)findViewById(R.id.btnUpby10); btnStart.setOnClickListener(btnStartListener); btnStop.setOnClickListener(btnStopListener); btnBind.setOnClickListener(btnBindListener); btnUnbind.setOnClickListener(btnUnbindListener); btnUpby1.setOnClickListener(btnUpby1Listener); btnUpby10.setOnClickListener(btnUpby10Listener); restreMe(savedInstanceState); CheckIfServiceIsRunning(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("textStatus", textStatus.getText().toString()); outState.putString("textIntValue", textIntValue.getText().toString()); outState.putString("textStrValue", textStrValue.getText().toString()); } private void restreMe(Bundle state) { if (state!=null) { textStatus.setText(state.getString("textStatus")); textIntValue.setText(state.getString("textIntValue")); textStrValue.setText(state.getString("textStrValue")); } } private void CheckIfServiceIsRunning() { //If the service is running when the activity starts, we want to automatically bind to it. if (MyService.isRunning()) { doBindService(); } } private OnClickListener btnStartListener = new OnClickListener() { public void onClick(View v){ startService(new Intent(MainActivity.this, MyService.class)); } }; private OnClickListener btnStopListener = new OnClickListener() { public void onClick(View v){ doUnbindService(); stopService(new Intent(MainActivity.this, MyService.class)); } }; private OnClickListener btnBindListener = new OnClickListener() { public void onClick(View v){ doBindService(); } }; private OnClickListener btnUnbindListener = new OnClickListener() { public void onClick(View v){ doUnbindService(); } }; private OnClickListener btnUpby1Listener = new OnClickListener() { public void onClick(View v){ sendMessageToService(1); } }; private OnClickListener btnUpby10Listener = new OnClickListener() { public void onClick(View v){ sendMessageToService(10); } }; private void sendMessageToService(int intvaluetosend) { if (mIsBound) { if (mService != null) { try { Message msg = Message.obtain(null, MyService.MSG_SET_INT_VALUE, intvaluetosend, 0); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { } } } } void doBindService() { bindService(new Intent(this, MyService.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; textStatus.setText("Binding."); } void doUnbindService() { if (mIsBound) { // If we have received the service, and hence registered with it, then now is the time to unregister. if (mService != null) { try { Message msg = Message.obtain(null, MyService.MSG_UNREGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // There is nothing special we need to do if the service has crashed. } } // Detach our existing connection. unbindService(mConnection); mIsBound = false; textStatus.setText("Unbinding."); } } @Override protected void onDestroy() { super.onDestroy(); try { doUnbindService(); } catch (Throwable t) { Log.e("MainActivity", "Failed to unbind from the service", t); } } } 

src \ com.exampleservice \ MyService.java:

 package com.exampleservice; import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; 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; public class MyService extends Service { private NotificationManager nm; private Timer timer = new Timer(); private int counter = 0, incrementby = 1; private static boolean isRunning = false; ArrayList mClients = new ArrayList(); // Keeps track of all current registered clients. int mValue = 0; // Holds last value set by a client. static final int MSG_REGISTER_CLIENT = 1; static final int MSG_UNREGISTER_CLIENT = 2; static final int MSG_SET_INT_VALUE = 3; static final int MSG_SET_STRING_VALUE = 4; final Messenger mMessenger = new Messenger(new IncomingHandler()); // Target we publish for clients to send messages to IncomingHandler. @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } class IncomingHandler extends Handler { // Handler of incoming messages from clients. @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_REGISTER_CLIENT: mClients.add(msg.replyTo); break; case MSG_UNREGISTER_CLIENT: mClients.remove(msg.replyTo); break; case MSG_SET_INT_VALUE: incrementby = msg.arg1; break; default: super.handleMessage(msg); } } } private void sendMessageToUI(int intvaluetosend) { for (int i=mClients.size()-1; i>=0; i--) { try { // Send data as an Integer mClients.get(i).send(Message.obtain(null, MSG_SET_INT_VALUE, intvaluetosend, 0)); //Send data as a String Bundle b = new Bundle(); b.putString("str1", "ab" + intvaluetosend + "cd"); Message msg = Message.obtain(null, MSG_SET_STRING_VALUE); msg.setData(b); mClients.get(i).send(msg); } catch (RemoteException e) { // The client is dead. Remove it from the list; we are going through the list from back to front so this is safe to do inside the loop. mClients.remove(i); } } } @Override public void onCreate() { super.onCreate(); Log.i("MyService", "Service Started."); showNotification(); timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 0, 100L); isRunning = true; } private void showNotification() { nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // In this sample, we'll use the same text for the ticker and the expanded notification CharSequence text = getText(R.string.service_started); // Set the icon, scrolling text and timestamp Notification notification = new Notification(R.drawable.icon, text, System.currentTimeMillis()); // The PendingIntent to launch our activity if the user selects this notification PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0); // Set the info for the views that show in the notification panel. notification.setLatestEventInfo(this, getText(R.string.service_label), text, contentIntent); // Send the notification. // We use a layout id because it is a unique number. We use it later to cancel. nm.notify(R.string.service_started, notification); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("MyService", "Received start id " + startId + ": " + intent); return START_STICKY; // run until explicitly stopped. } public static boolean isRunning() { return isRunning; } private void onTimerTick() { Log.i("TimerTick", "Timer doing work." + counter); try { counter += incrementby; sendMessageToUI(counter); } catch (Throwable t) { //you should always ultimately catch all exceptions in timer tasks. Log.e("TimerTick", "Timer Tick Failed.", t); } } @Override public void onDestroy() { super.onDestroy(); if (timer != null) {timer.cancel();} counter=0; nm.cancel(R.string.service_started); // Cancel the persistent notification. Log.i("MyService", "Service Stopped."); isRunning = false; } } 

    Mire el ejemplo de servicio local .

    Su Service devuelve una instancia de sí mismo a los consumidores que llaman a onBind . Luego puede interactuar directamente con el servicio, por ejemplo, registrando su propia interfaz de escucha con el servicio, para que pueda obtener devoluciones de llamada.

    Para enviar datos a un servicio puede usar:

     Intent intent = new Intent(getApplicationContext(), YourService.class); intent.putExtra("SomeData","ItValue"); startService(intent); 

    Y después de estar en servicio en onStartCommand () obtener datos del bash.

    Para enviar datos o eventos desde un servicio a una aplicación (para una o más actividades):

     private void sendBroadcastMessage(String intentFilterName, int arg1, String extraKey) { Intent intent = new Intent(intentFilterName); if (arg1 != -1 && extraKey != null) { intent.putExtra(extraKey, arg1); } sendBroadcast(intent); } 

    Este método está llamando desde su servicio. Simplemente puede enviar datos para su actividad.

     private void someTaskInYourService(){ //For example you downloading from server 1000 files for(int i = 0; i < 1000; i++) { Thread.sleep(5000) // 5 seconds. Catch in try-catch block sendBroadCastMessage(Events.UPDATE_DOWNLOADING_PROGRESSBAR, i,0,"up_download_progress"); } 

    Para recibir un evento con datos, cree y registre el método registerBroadcastReceivers () en su actividad:

     private void registerBroadcastReceivers(){ broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int arg1 = intent.getIntExtra("up_download_progress",0); progressBar.setProgress(arg1); } }; IntentFilter progressfilter = new IntentFilter(Events.UPDATE_DOWNLOADING_PROGRESS); registerReceiver(broadcastReceiver,progressfilter); 

    Para enviar más datos, puede modificar el método sendBroadcastMessage(); . Recuerde: debe registrar las transmisiones en los métodos onResume () y anular el registro en onStop ().

    ACTUALIZAR

    Por favor, no use mi tipo de comunicación entre Actividad y Servicio. Esta es la forma incorrecta. Para una mejor experiencia, utilice libs especiales, como nosotros:

    1) EventBus desde greenrobot

    2) Otto de Square Inc

    PD. Solo estoy usando EventBus desde greenrobot en mis proyectos,

    Nota: No necesita verificar si su servicio se está ejecutando, CheckIfServiceIsRunning() , porque bindService() lo iniciará si no se está ejecutando.

    Además: si gira el teléfono, no desea que bindService() nuevamente, ya que se llamará de nuevo a onCreate() . Asegúrese de definir onConfigurationChanged() para evitar esto.

    Todo está bien. Buen ejemplo de comunicación de activity/service usando Messenger .

    Un comentario: el método MyService.isRunning() no es obligatorio. bindService() se puede hacer cualquier cantidad de veces. no hay daño en eso.

    Si MyService se ejecuta en un proceso diferente, la función estática MyService.isRunning() siempre devolverá false. Entonces no hay necesidad de esta función.

     Message msg = Message.obtain(null, 2, 0, 0); Bundle bundle = new Bundle(); bundle.putString("url", url); bundle.putString("names", names); bundle.putString("captions",captions); msg.setData(bundle); 

    Entonces lo envías al servicio. Después recibir

    Así es como implementé Activity-> Service Communication: en mi actividad tuve

     private static class MyResultReciever extends ResultReceiver { /** * Create a new ResultReceive to receive results. Your * {@link #onReceiveResult} method will be called from the thread running * handler if given, or from an arbitrary thread if null. * * @param handler */ public MyResultReciever(Handler handler) { super(handler); } @Override protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == 100) { //dostuff } } 

    Y luego usé esto para comenzar mi Servicio

     protected void onCreate(Bundle savedInstanceState) { MyResultReciever resultReciever = new MyResultReciever(handler); service = new Intent(this, MyService.class); service.putExtra("receiver", resultReciever); startService(service); } 

    En mi servicio tuve

     public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) resultReceiver = intent.getParcelableExtra("receiver"); return Service.START_STICKY; } 

    Espero que esto ayude

    Me parece que podrías haber guardado algo de memoria al declarar tu actividad con “implementa Handler.Callback”

    Gran tutorial, presentación fantástica. Limpio, simple, breve y muy explicativo. Aunque, notification.setLatestEventInfo(this, getText(R.string.service_label), text, contentIntent); método no es más. Como dijo trante aquí , un buen enfoque sería:

     private static final int NOTIFICATION_ID = 45349; private void showNotification() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("My Notification Title") .setContentText("Something interesting happened"); Intent targetIntent = new Intent(this, MainActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(contentIntent); _nManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); _nManager.notify(NOTIFICATION_ID, builder.build()); } @Override public void onDestroy() { super.onDestroy(); if (_timer != null) {_timer.cancel();} _counter=0; _nManager.cancel(NOTIFICATION_ID); // Cancel the persistent notification. Log.i("PlaybackService", "Service Stopped."); _isRunning = false; } 

    Compruebé que todo funciona como un amuleto (los nombres de actividades y servicios pueden diferir del original).

    He visto todas las respuestas. Quiero decir la forma más sólida ahora un día . Eso lo hará comunicarse entre Activity - Service - Dialog - Fragments (Todo).

    EventBus

    Esta lib que estoy usando en mis proyectos tiene grandes características relacionadas con la mensajería.

    EventBus en 3 pasos

    1. Definir eventos:

      public static class MessageEvent { /* Additional fields if needed */ }

    2. Preparar suscriptores:

    Declare y anote su método de suscripción; opcionalmente, especifique un modo de secuencia :

     @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) {/* Do something */}; 

    Regístrese y elimine el registro de su suscriptor. Por ejemplo, en Android, las actividades y los fragmentos generalmente deberían registrarse de acuerdo con su ciclo de vida:

     @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } 
    1. Publicar eventos:

      EventBus.getDefault().post(new MessageEvent());

    Solo agregue esta dependencia en su gradle de nivel de aplicación

     compile 'org.greenrobot:eventbus:3.1.1'