Comunicación entre Actividad y Servicio
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
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);
}
}
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.
En tus
Activity
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
Service
con este objeto Handler como datos adicionales comoIntent startService = new Intent(context, SERVICE.class) startService.putExtra("MESSENGER", new Messenger(messageHandler)); context.startService(startService);
En su
Service
recibe este objeto de la intención e inicializa laMessenger
variable en Servicio comoprivate Messenger messageHandler; Bundle extras = intent.getExtras(); messageHandler = (Messenger) extras.get("MESSENGER"); sendMessage(ProgressBarState.SHOW);
Y luego escriba un método
sendMessage
para 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.
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);
}
}
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