Activity和Service之间的通信

64

我正在尝试为Android制作自己的音乐播放器。 我遇到的问题是在后台运行一些东西。 主要活动管理GUI,现在所有歌曲都在播放。 我想分离GUI和音乐播放类。 我想将音乐管理部分放在服务中,并将其他事情保持现状。

我的问题是,我无法组织Activity和Service之间的通信,因为它们之间包括双向移动对象的大量通信。 我尝试了许多技术,这些技术我在Stack Overflow上搜索到,但每次都会遇到问题。 我需要Service能够将对象发送到Activity,反之亦然。 当我添加小部件时,我也希望它能够与Service通信。

欢迎任何提示,如果您需要源代码,请在下面发表评论,但在此过渡期间,它变得混乱了。

是否有比调用一个从服务返回随机数的方法更高级的教程?:P

编辑:可能的解决方案是使用RoboGuice库并使用注入移动对象

8个回答

85

我使用Bind和Callbacks接口来实现Activity和Service之间的通信。

为了将数据发送到Service,我使用了Binder,它将Service实例返回给Activity,然后Activity就可以访问Service中的公共方法。

为了从Service向Activity发送数据,我使用了Callbacks接口,就像在Fragment和Activity之间通信时使用的那样。

这里有每个部分的一些代码示例:

以下示例展示了Activity和Service之间的双向关系:

Activity中有两个按钮: 第一个按钮将启动和停止Service。 第二个按钮将在Service中运行一个计时器。

Service将通过回调函数更新Activity中的计时器进度。

我的Activity:

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

这里是服务:

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

2
我建议使用bindService将服务与Activity绑定,然后调用服务类中定义的方法。这对我来说更有意义,因为Activity可能会启动或暂停,但服务始终存在。在上面的代码中,“callback”变量(指向Activity的引用)如果Activity未运行,则可能失败。这将导致NPE。希望我的观点清楚明了。 - amsurana
5
我建议在服务的 unBind 方法中将 Callbacks 活动设置为 null,以避免内存泄漏。 - Anton Kizema
1
是的,我相信这个解决方案可能还有一些改进空间,这只是一个基本模式。 - Moti Bartov
在Android中使用AIDL怎么样?这是从服务返回对象的另一种方式。 - neilQ5
如果我需要计时器在应用程序被销毁并重新启动时继续运行,该怎么办? - Balraj Allam
1
@MotiBartov,你应该获得诺贝尔奖,感谢你提供的解决方案!:) - Carlos.V

63

更新:2016年7月10日

个人认为,对于自定义事件,使用广播接收器(BroadcastReceiver)是更好的方式,因为提到的 Messengers 不太可能很好地处理设备旋转时的活动重建以及潜在的内存泄漏问题。

您可以为活动中的事件创建自定义广播接收器,然后还可以使用 Messengers。

  1. 在您的Activity

    创建一个名为 MessageHandler 的类,如下所示:

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

    现在您可以将它的实例表示为

    public static Handler messageHandler = new MessageHandler();
    

    使用此处理程序对象作为额外数据启动您的Service

    Intent startService = new Intent(context, SERVICE.class)
    startService.putExtra("MESSENGER", new Messenger(messageHandler));
    context.startService(startService);
    
  2. 在你的Service中,你从Intent接收到这个对象,并将Messenger变量在Service中初始化为:

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

    然后编写一个名为sendMessage的方法,用于向活动发送消息。

    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();
    }
    }
    
    上面的示例代码显示和隐藏了 Activity 中的 ProgressBar,随着从 Service 接收到的消息而变化。


@DjDexter5GHz 谢谢,但我建议你整个东西都做一下。在我的情况下,我必须隐藏/显示一个布局和隐藏/显示一个进度条,整个概念运作良好。你有很多对象,希望它能正常工作。 - Rachit Mishra
如何进行反向连接的通信?从Activity到Service? - Dejan
1
让服务成为IntentService,这样您就可以轻松地向服务发送Intent来执行某些操作。 - Rachit Mishra
5
MessageHandler 如何访问 Activity 的 progressBar,因为该类是静态的? - cd1
2
当服务仍在运行时,旋转设备怎么办?我猜这样会创建一个新的 MessageHandler,并且服务的 MessageHandler 将无法与活动进行通信。处理程序是您可以在 onSavedInstanceState() 中保存和恢复的内容吗? - Micro
显示剩余5条评论

15

意图是活动和服务之间通信的好解决方案。

在您的服务中快速接收意图的解决方案是通过继承IntentService类。它使用队列和工作线程处理表示为意图的异步请求。

对于从服务到活动的通信,可以广播意图,但与使用Context的普通sendBroadcast()不同,更有效的方法是使用支持库中的LocalBroadcastManager

服务示例。

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

示例活动

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

2
如果我想在onPause()或onDestroy()后仍然监听一个服务,该怎么办? - Kaveesh Kanwal
IntentService已经被弃用。 - prsnlme

5
我认为正确答案存在问题。我没有足够的声望来对其进行评论。
在答案中正确的地方: Activity调用bindService()以获取指向Service的指针是可以的。因为当连接被维护时,Service上下文也将被维护。
在答案中错误的地方: Service指向Activity类进行回调是不好的方式。在Activity上下文被释放期间,Activity实例可能为空=>这里会出现异常。
解决答案中错误的方法: Service通过Intent发送给Activity。并且Activity通过BroadcastReceiver接收Intent。
注意: 在这种情况下,Service和Activity在同一个进程中,应该使用LocalBroadcastManager来发送Intent。它可以提高性能和安全性。

4
这是一个关于活动和服务之间通信的简单示例。

活动

MyReceiver myReceiver; //my global var receiver
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.layourAwesomexD);
    registerReceiver();
}

//When the activity resume, the receiver is going to register...
@Override
protected void onResume() {
    super.onResume();
    checkStatusService(); // verficarStatusServicio(); <- name change
    registerReceiver();
}
//when the activity stop, the receiver is going to unregister...
@Override
protected void onStop() {
    unregisterReceiver(myReceiver); //unregister my receiver...
    super.onStop();
}
//function to register receiver :3
private void registerReceiver(){
    //Register BroadcastReceiver
    //to receive event from our service
    myReceiver = new MyReceiver();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(MyService.SENDMESAGGE);
    registerReceiver(myReceiver, intentFilter);
}

// class of receiver, the magic is here...
private class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context arg0, Intent arg1) {
        //verify if the extra var exist
        System.out.println(arg1.hasExtra("message")); // true or false
        //another example...
        System.out.println(arg1.getExtras().containsKey("message")); // true or false
        //if var exist only print or do some stuff
        if (arg1.hasExtra("message")) {
            //do what you want to
            System.out.println(arg1.getStringExtra("message"));
        }    
    }
}

public void checkStatusService(){
    if(MyService.serviceStatus!=null){
        if(MyService.serviceStatus == true){
            //do something
            //textview.text("Service is running");
        }else{
            //do something
            //textview.text("Service is not running");
        }
    }
}

服务

public class MyService extends Service {

final static String SENDMESAGGE = "passMessage";

public static Boolean serviceStatus = false;

@Override
public void onCreate() {
    super.onCreate();
    serviceStatus=true;
}

@Nullable
@Override
    public IBinder onBind(Intent intent) {return null;}

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //you service etc...             
        passMessageToActivity("hello my friend this an example of send a string...");
        return START_STICKY;
    }

@Override
    public void onDestroy() {
        super.onDestroy();
        passMessageToActivity("The service is finished, This is going to be more cooler than the heart of your ex...");
        System.out.println("onDestroy");
        serviceStatus=false;
   }

private void passMessageToActivity(String message){
        Intent intent = new Intent();
        intent.setAction(SENDMESAGGE);
        intent.putExtra("message",message);
        sendBroadcast(intent);
    }
}
  • 如果我们不注销BroadcastReceiver,就会出现错误,当activity进入onPause、onStop、onDestroy时需要注销BroadcastReceiver...
  • 如果你回到activity时没有注册BroadcastReceiver,它将无法从service中监听任何信息... service将向BroadcastReceiver发送信息,但由于未注册,它将无法接收任何信息。
  • 当您创建多个服务时,以下服务将在onStartCommand中开始执行。
  • 您可以通过intent将信息传递给服务,并在onStartCommand中获取它。
  • onStartCommandreturn的区别: START_STICKY和START_REDELIVER_INTENT之间的区别?并查看google的官方网站:Services

如果我想在 onPause() 或 onDestroy() 之后继续监听服务怎么办? - Kaveesh Kanwal
1
你说的“将输出保存在getsharedpreference中”,是什么意思?当活动恢复时将其放置...例如:如果 activity == null -> 保存在 sharedPreference 中 -> activity.resume -> 从 sharedPreference 获取数据。 - DarckBlezzer
你的意思是 verficarStatusServicio() 吗? - Mahmoud Alnouno
抱歉我没有放函数,它是一个验证器,类似于检查状态服务,如果它正在运行或停止,或者你在服务中检查的某些东西... 例如:我想在TextView中显示运行或停止...我创建了一个函数来检查状态(检查服务类中的静态变量),如果它有"running"或"stopped"或存在... - DarckBlezzer

3
在这种情况下,最好的方法是通过从您的服务进行广播并在您的活动中接收来进行通信。您可以创建自定义广播并发送一些代码来定义特定事件,例如完成、更改、准备等...

你知道有什么教程吗?我尝试了BroadcastReceivers,但遇到了实例化Service的问题。这是让人头疼的部分。 - Dejan
非常感谢提供任何演示初始化和双向对象通信的示例代码。 - Dejan
嗯...我自己做了这个,不知道是否有完整的教程...但你可以分别搜索它的部分,比如自定义广播接收器等,然后组装起来... :) - Vikram Singh

2

最简单和高效的方法是使用GreenRobot的EventBus

只需简单的3个步骤:

1 定义事件

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

2 准备订阅者:声明并注释您的订阅方法,可以选择指定线程模式:

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

注册和取消注册订阅者。例如在Android上,活动和片段通常应根据其生命周期进行注册:

@Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }

3篇文章事件:

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

2
我实际上使用了这个解决方案。如果你有一个大项目,代码很容易变得混乱。我发现使用Dagger和依赖注入与Repository模式和MVP相结合可以获得最佳结果。 - Dejan

0

非常简单而又强大的方法是使用EventBus,您可以将其添加到gradle构建中,并享受易于使用的发布者/订阅者模式。


2
我不会推荐使用EventBus,因为我曾尝试并陷入了困境。调试整个代码时,我无法知道它是从应用程序的哪个部分调用的。 - Dejan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接