Comunicación entre Actividad y Servicio

Resuelto Dejan asked hace 54 años • 8 respuestas

Estoy intentando crear mi propio reproductor de música para Android. El problema fue ejecutar algunas cosas en segundo plano. La actividad principal gestiona la GUI y hasta ahora se están reproduciendo todas las canciones. Quería separar las clases de GUI y de reproducción de música. Quiero poner la parte de gestión de música en el Servicio y dejar otras cosas como están ahora.

Mi problema es que no puedo organizar la comunicación entre Actividad y Servicio ya que hay mucha comunicación entre ellos, incluido el movimiento de objetos en ambas direcciones. Probé muchas técnicas que busqué aquí en Stack Overflow pero cada vez tuve problemas. Necesito Servicio para poder enviar objetos a Actividad y viceversa. Cuando agrego un widget, también quiero que pueda comunicarse con el Servicio.

Se agradece cualquier consejo. Si necesita el código fuente, comente a continuación, pero ahora, en esta transición, se volvió caótico.

¿Existe algún tutorial más avanzado sobre esto que llamar a un método que devuelve un número aleatorio del servicio? :PAG

EDITAR: La posible solución es usar la biblioteca RoboGuice y mover objetos con inyección

Dejan avatar Jan 01 '70 08:01 Dejan
Aceptado

Implementé la comunicación entre Actividad y Servicio usando la interfaz Bind y Callbacks.

Para enviar datos al servicio utilicé Binder, que devuelve la instancia del Servicio a la Actividad, y luego la Actividad puede acceder a métodos públicos en el Servicio.

Para enviar datos a la Actividad desde el Servicio, utilicé la interfaz de Devoluciones de Llamadas como la que usas cuando quieres comunicarte entre Fragmento y Actividad.

Aquí hay algunos ejemplos de código para cada uno: El siguiente ejemplo muestra la relación bidireccional Actividad y Servicio: La Actividad tiene 2 botones: El primer botón iniciará y detendrá el servicio. El segundo botón iniciará un temporizador que se ejecuta en el servicio.

El servicio actualizará la Actividad mediante devolución de llamada con el progreso del temporizador.

Mi actividad:

    //Activity implements the Callbacks interface which defined in the Service  
    public class MainActivity extends ActionBarActivity implements MyService.Callbacks{

    ToggleButton toggleButton;
    ToggleButton tbStartTask;
    TextView tvServiceState;
    TextView tvServiceOutput;
    Intent serviceIntent;
    MyService myService;
    int seconds;
    int minutes;
    int hours;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         serviceIntent = new Intent(MainActivity.this, MyService.class);
        setViewsWidgets();
    }

    private void setViewsWidgets() {
        toggleButton = (ToggleButton)findViewById(R.id.toggleButton);
        toggleButton.setOnClickListener(btListener);
        tbStartTask = (ToggleButton)findViewById(R.id.tbStartServiceTask);
        tbStartTask.setOnClickListener(btListener);
        tvServiceState = (TextView)findViewById(R.id.tvServiceState);
        tvServiceOutput = (TextView)findViewById(R.id.tvServiceOutput);

    }

    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            Toast.makeText(MainActivity.this, "onServiceConnected called", Toast.LENGTH_SHORT).show();
            // We've binded to LocalService, cast the IBinder and get LocalService instance
            MyService.LocalBinder binder = (MyService.LocalBinder) service; 
            myService = binder.getServiceInstance(); //Get instance of your service! 
            myService.registerClient(MainActivity.this); //Activity register in the service as client for callabcks! 
            tvServiceState.setText("Connected to service...");
            tbStartTask.setEnabled(true);
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            Toast.makeText(MainActivity.this, "onServiceDisconnected called", Toast.LENGTH_SHORT).show();
            tvServiceState.setText("Service disconnected");
            tbStartTask.setEnabled(false);
        }
    };

    View.OnClickListener btListener =  new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(v == toggleButton){
                if(toggleButton.isChecked()){
                    startService(serviceIntent); //Starting the service 
                    bindService(serviceIntent, mConnection,            Context.BIND_AUTO_CREATE); //Binding to the service! 
                    Toast.makeText(MainActivity.this, "Button checked", Toast.LENGTH_SHORT).show();
                }else{
                    unbindService(mConnection);
                    stopService(serviceIntent);
                    Toast.makeText(MainActivity.this, "Button unchecked", Toast.LENGTH_SHORT).show();
                    tvServiceState.setText("Service disconnected");
                    tbStartTask.setEnabled(false);
                }
            }

            if(v == tbStartTask){
                if(tbStartTask.isChecked()){
                      myService.startCounter();
                }else{
                    myService.stopCounter();
                }
            }
        }
    };

    @Override
    public void updateClient(long millis) {
        seconds = (int) (millis / 1000) % 60 ;
        minutes = (int) ((millis / (1000*60)) % 60);
        hours   = (int) ((millis / (1000*60*60)) % 24);

        tvServiceOutput.setText((hours>0 ? String.format("%d:", hours) : "") + ((this.minutes<10 && this.hours > 0)? "0" + String.format("%d:", minutes) :  String.format("%d:", minutes)) + (this.seconds<10 ? "0" + this.seconds: this.seconds));
    }
}

Y aquí está el servicio:

 public class MyService extends Service {
    NotificationManager notificationManager;
    NotificationCompat.Builder mBuilder;
    Callbacks activity;
    private long startTime = 0;
    private long millis = 0;
    private final IBinder mBinder = new LocalBinder();
    Handler handler = new Handler();
    Runnable serviceRunnable = new Runnable() {
        @Override
        public void run() {
            millis = System.currentTimeMillis() - startTime;
            activity.updateClient(millis); //Update Activity (client) by the implementd callback
            handler.postDelayed(this, 1000);
        }
    };


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        //Do what you need in onStartCommand when service has been started
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    //returns the instance of the service
    public class LocalBinder extends Binder{
        public MyService getServiceInstance(){
            return MyService.this;
        }
    }

    //Here Activity register to the service as Callbacks client
    public void registerClient(Activity activity){
        this.activity = (Callbacks)activity;
    }

    public void startCounter(){
        startTime = System.currentTimeMillis();
        handler.postDelayed(serviceRunnable, 0);
        Toast.makeText(getApplicationContext(), "Counter started", Toast.LENGTH_SHORT).show();
    }

    public void stopCounter(){
        handler.removeCallbacks(serviceRunnable);
    }


    //callbacks interface for communication with service clients! 
    public interface Callbacks{
        public void updateClient(long data);
    }
}
Moti Bartov avatar Mar 17 '2015 14:03 Moti Bartov

Actualización: 10 de julio de 2016

En mi opinión, creo que usar BroadcastReceiver para eventos personalizados es una mejor manera, ya que los Mensajeros mencionados no manejan la recreación de actividades en la rotación del dispositivo ni posibles pérdidas de memoria.

Puede crear un receptor BroadCast personalizado para eventos en la actividad. Luego, también puede usar Messengers.

  1. En tusActivity

    crear una clase MessageHandler como

    public static class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            int state = message.arg1;
            switch (state) {
            case HIDE:
                progressBar.setVisibility(View.GONE);
                break;
            case SHOW:
                progressBar.setVisibility(View.VISIBLE);
                break;
            }
        }
    }
    

    Ahora puedes tener su instancia como

    public static Handler messageHandler = new MessageHandler();
    

    Comience Servicecon este objeto Handler como datos adicionales como

    Intent startService = new Intent(context, SERVICE.class)
    startService.putExtra("MESSENGER", new Messenger(messageHandler));
    context.startService(startService);
    
  2. En su Servicerecibe este objeto de la intención e inicializa la Messengervariable en Servicio como

    private Messenger messageHandler;
    Bundle extras = intent.getExtras();
    messageHandler = (Messenger) extras.get("MESSENGER");
    sendMessage(ProgressBarState.SHOW);
    

    Y luego escriba un método sendMessagepara enviar mensajes a la actividad.

    public void sendMessage(ProgressBarState state) {
    Message message = Message.obtain();
    switch (state) {
        case SHOW :
            message.arg1 = Home.SHOW;
            break;
        case HIDE :
            message.arg1 = Home.HIDE;
            break;
    }
    try {
        messageHandler.send(message);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
    }
    

El código de muestra anterior muestra y oculta una barra de progreso en la actividad a medida que se reciben mensajes del servicio.

Rachit Mishra avatar Dec 15 '2013 13:12 Rachit Mishra

Los intentos son una buena solución para la comunicación entre Actividad y Servicio.

Una solución rápida para recibir intenciones en su servicio es subclasificar la clase IntentService . Maneja solicitudes asincrónicas expresadas como intenciones utilizando una cola y un subproceso de trabajo.

Para la comunicación del servicio a la Actividad, puede transmitir la intención, pero en lugar de usar sendBroadcast() normal desde Context, una forma más eficiente es usar LocalBroadcastManager desde la biblioteca de soporte.

Servicio de ejemplo.

public class MyIntentService extends IntentService {
    private static final String ACTION_FOO = "com.myapp.action.FOO";
    private static final String EXTRA_PARAM_A = "com.myapp.extra.PARAM_A";

    public static final String BROADCAST_ACTION_BAZ = "com.myapp.broadcast_action.FOO";
    public static final String EXTRA_PARAM_B = "com.myapp.extra.PARAM_B";

    // called by activity to communicate to service
    public static void startActionFoo(Context context, String param1) {
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_FOO);
        intent.putExtra(EXTRA_PARAM1, param1);
        context.startService(intent);
    }

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_FOO.equals(action)) {
                final String param1 = intent.getStringExtra(EXTRA_PARAM_A);
                // do something
            }
        }
    }

    // called to send data to Activity
    public static void broadcastActionBaz(String param) {
        Intent intent = new Intent(BROADCAST_ACTION_BAZ);
        intent.putExtra(EXTRA_PARAM_B, param);
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.sendBroadcast(intent);
    }
}

Actividad de ejemplo

public class MainActivity extends ActionBarActivity {

    // handler for received data from service
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(MyIntentService.BROADCAST_ACTION_BAZ)) {
                final String param = intent.getStringExtra(EXTRA_PARAM_B);
                // do something
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        IntentFilter filter = new IntentFilter();
        filter.addAction(MyIntentService.BROADCAST_ACTION_BAZ);
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.registerReceiver(mBroadcastReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.unregisterReceiver(mBroadcastReceiver);
        super.onDestroy();
    }

    // send data to MyService
    protected void communicateToService(String parameter) {
        MyIntentService.startActionFoo(this, parameter);
    }
}
mpolci avatar Mar 13 '2015 23:03 mpolci

Creo que hay un problema con la respuesta correcta. No tengo suficiente reputación para comentar al respecto.

Justo en la respuesta: la llamada de actividad bindService() para obtener el puntero al Servicio está bien. Porque el contexto del servicio se mantiene cuando se mantiene la conexión.

incorrecto en la respuesta: el puntero de servicio a la clase Actividad para devolver la llamada es una mala manera. La instancia de actividad tal vez no sea nula durante el contexto de la actividad. Lanzamiento => excepción aquí.

Solución para el error en la respuesta: intención de envío del servicio a Actividad. e intención del receptor de actividad a través de BroadcastReceiver.

Nota: en este caso, Servicio y Actividad en el mismo Proceso, debes usar LocalBroadcastManager para enviar la intención. Mejora el rendimiento y la seguridad

HungNM2 avatar Sep 15 '2016 15:09 HungNM2