原文链接:Bound Service 墙内链接:Bound Service
绑定的service是在客户端-服务器模型中的服务器。绑定service允许组件(如activity)绑定到service,发送请求,接收结果,甚至执行进程间通信(IPC)。一个绑定service通常只在它服务于其它应用组件时运行并且它不会一直在后台运行。
带着问题去学习
- 注册Service需要注意什么
- 什么是Service以及描述下它的生命周期。
- Service与Activity怎么实现通信
- Service有哪些启动方法,有什么区别,怎样停用Service?
这个文档介绍了怎样创建一个绑定service,包括怎样从其它应用组件绑定到service。然而,你也应该参考Services文档关于Service的信息,例如怎样从service分发通知,设置service运行在前台等等。
基础
绑定的service是Service类的实现允许其它应用绑定到它并和它交互。为了实现一个可绑定的service,你必须实现onBind()回调方法。这个方法返回一个IBinder对象定义了客户端可以用来和service进行交互的编程接口。
客户端可以调用bindService()绑定到service。当通过这种方式处理时,客户端必须提供一个ServiceConnection的实现,用来监视和service的连接。bindService()方法没有返回一个值,但当Android系统创建了客户端和service的连接,它会调用ServiceConnection中的onServiceConnected()方法,用于传递客户端可以用来和service进行通信的IBinder对象。
多个客户端可以连接到service。然而,系统只会在第一个客户端绑定时调用service的onBind()方法获取IBinder对象。然后系统会返回同一个IBinder给其它绑定的客户端,不会再调用onBind()方法。
当最后一个客户端从service解绑后,系统会销毁service(除非service是通过startService()启动的)。
当实现绑定的service时,最重要的部分就是定义onBind()回调方法返回的接口。这有几种不同的方式定义service的IBinder接口,下面的部分会分别进行讨论。
创建绑定的service
当创建一个提供绑定的service,你必须提供一个客户端可以用来和service进行交互的IBinder描述编程接口。这有三种定义接口的方法:
- 继承Binder类
如果你的service是对于你的应用是私有的并且和客户端运行在同一进程(最常见的),你应该通过继承Binder类创建接口并且从onBind()返回它的实例。客户端接收Binder并且可以使用它直接访问Binder实现或甚至是Service的可用的公有方法。
这是最好的方法当你的service对你自己的应用仅仅是一个后台工作者。你不会使用这种方法创建接口的唯一原因是因为你的service需要通过其它应用程序或跨进程使用。
- 使用Messenger
如果你的接口需要跨进程使用,你可以使用Messenger为Service创建一个接口。用这个方法,service定义一个Handler来响应不同类型Message对象。这个Handler基于可以给客户端共享IBinder的Messenger,允许客户端使用Message对象给service发送命令。另外,客户端还可以定义自己的Messenger,service可以返回发送消息。
- 使用AIDL
AIDL (Android接口声明语言)处理解析对象到操作系统可以理解的原语并且执行进程间通信。前面提到的技术,使用Messenger,实际上是基于AIDL作为其底层结构。正如上面提到的,Messenger在单线程中创建一个所有客户端请求的队列,因此service同一时间只能接收一个请求。但是,如果你想让你的service同时处理多个请求,你可以直接使用AIDL。在这种情况下,你的service必须具有多线程能力并且构建线程安全。
为了直接使用AIDL,你必须创建一个.aidl文件定义编程接口。Android SDK工具使用这个文件生成实现接口的抽象类并处理IPC,你可以在你的service中继承它。
注意: 大多数应用不应该使用AIDL创建绑定Service,因此它可能需要多线程功能并且会导致更复杂的实现。例如,AIDL不适用于多数应用并且本文不会讨论怎样使用它。如果你确实需要直接使用AIDL,请查看AIDL文档。
继承Binder类
如果你的service仅仅用于本应用并且不需要跨进程使用,那你可以实现自己的Binder类在service中提供给客户端可以直接访问的公有方法。
注意: 这个仅用于客户端和service是同一个应用程序和进程,这也是最常见的。例如,这个对于音乐类应用将很有用,绑定activity到在后台播放音乐的service。
下面是设置的步骤:
1.在service中,创建一个下面任一条件的Binder实例:
- 包含客户端可以调用的公有方法
- 返回当前Service实例,有客户端可以调用的公有方法
- 或者返回一个service托管的其它类的实例包含客户端可以调用的公有方法
2.从onBind()回调方法中返回这个Binder实例。
3.在客户端,从onServiceConnected()回调方法接收Binder并使用提供的方法调用绑定的Service。
注意: 因为service和客户端必须是在同一个应用中因此客户端可以转换返回的对象并正确的调用它的APIs。service和客户端也必须是在相同的进程,因为这个技术不会跨进程执行。
例如,这有一个service通过Binder的实现提供给客户端访问的方法:
1 | public class LocalService extends Service { |
LocalBinder为客户端提供了getService()方法返回LocalService的当前实例。这允许客户端调用service中公有方法。例如,客户端可以从service中调用getRandomNumber()。
这有一个绑定到LocalService的activity,当按钮被点击调用getRandomNumber()。
1 | public class BindingActivity extends Activity { |
上面的例子说明了怎样使用ServiceConnection的实现和onServiceConnected()回调绑定到service。下一部分提供更多关于绑定到service的处理信息。
注意: 上面的例子没有明确的从service解绑,但所有客户端应该在合适的时间(例如当activity暂停时)解绑。
更多示例代码,从ApiDemos中查看LocalService.java类和LocalServiceActivities.java类。
使用Messenger
如果你需要让service可以和remote进程进行通信,你可以使用Messenger为service提供接口。这个技术可以让你不使用AIDL执行进程间通信(IPC)。
下面概括了怎样使用 Messenger:
- 实现Handler的service接收从客户端每次调用的回调。
- Handler用于创建Messenger对象(持有Handler的引用)。
- Messenger创建service从onBind()返回给客户端的IBinder。
- 客户端使用IBinder实例化Messenger(持有Service的Handler的引用),客户端用来给service发送Message对象。
- service在它的Handler接收每个Message——在handleMessage()方法中。
使用这种方式,在service中没有提供给客户端“方法”调用。而是,客户端分发“消息”(Message对象),service在它的Handler中接收。
这有一个使用Messenger接口service的简单示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
注意Handler中的handleMessage()方法是service接收消息的地方并根据what数字决定做什么。
客户端需要做的就是基于service返回的IBinder创建一个Messenger,使用send()发送消息。例如,这有一个绑定到service的简单的activity,发送MSG_SAY_HELLO消息到service:
1 | public class ActivityMessenger extends Activity { |
注意这个示例没有说明service是怎样响应客户端。如果你想让service返回,你需要在客户端创建一个Messenger。这样当客户端接收到onServiceConnected()回调,它通过send()方法发送消息到service,在replyTo参数包含客户端的Messenger。
你也可以查看怎样提供双向消息通信在 MessengerService.java (service) 和 MessengerServiceActivities.java (client) 示例代码中。
绑定到Service
应用组件(客户端)可以通过调用bindService()绑定到service。Android系统会调用service的onBind()方法,返回一个可以和service进行交互的IBinder。
绑定是异步的。bindService()会立即返回并且不会把IBinder返回给客户端。为了接收IBinder,客户端必须创建一个ServiceConnection实例并传递给bindService()。ServiceConnection包含一个系统调用分发IBinder的回调方法。
注意: 只有activitys,services,content providers可以绑定到service——你不能从broadcast receiver绑定到service。
因此,客户端为了绑定到service,需要这么做:
1.实现ServiceConnection。
实现必须重写两个回调方法:
onServiceConnected()
系统调用这个分发service的onBind()方法返回的IBinder。
onServiceDisconnected()
当连接到的service被意外终止系统会调用这个,例如当service crashed 或被杀死。当客户端解绑时这个 不会被调用。
2.调用bindService(),传递ServiceConnection实现。
3.当系统调用onServiceConnected()回调方法时,你可以开始调用到service,使用接口定义的方法。
4.对于解绑service,调用unbindService()。
当客户端销毁后,它会从service解绑,但你应该总是当和service交互完成或当activity暂停时进行解绑,为了service不再被使用时可以关闭。(合适的时间进行绑定和解绑将在下面讨论。)
例如,下面的代码片段连接客户端到通过继承Binder类创建的service,因此要做的就是强转返回的IBinder到LocalBinder类并请求LocalService实例:
1 | LocalService mService; |
使用ServiceConnection,客户端可以调用bindService()绑定到service。例如:1
2Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- bindService()的第一个参数是一个显式的要绑定的service的Intent(考虑Intent可能是隐式的)。
- 第二个参数是ServiceConnection对象。
- 第三个参数是绑定的选项。通常使用的是BIND_AUTO_CREATE,如果service还没有启动会创建它。其它可用的值为BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND, 或 0 表示没有值.
补充笔记
关于绑定service这有一些重要的笔记:
- 你应该总是捕获DeadObjectException异常,当连接被中断时抛出的异常。这个是只会由远程(remote)方法抛出的异常。
- 对象引用跨进程计数。
- 通常绑定和解绑应该成对的出现匹配客户端生命周期的建立和销毁。例如:
如果你只需要在activity可见时和service进行交互,你应该在onStart()绑定并在onStop()解绑。
如果你想让你的activity接收响应甚至当它在后台已停止,你可以在onCreate()绑定并在onDestroy()解绑。注意这意味着activity在它运行的整个时间(甚至在后台)需要使用service,因此如果service在其它进程,那么你增加了该进程的消耗并且它会变得更容易被系统杀死。
注意: 通常你不应该在activity的onResume() 和 onPause()进行绑定和解绑,因为这些回调发生在每个生命周期的过渡并且你应该保持发生在这些过渡的处理到最小。还有,如果应用中的多个activity绑定到同一个service,这些activity中的两个之间是一个过渡,service可能被销毁并且会为当前activity重新创建在下一个绑定(resume期间)之前解绑(pause期间)。(在Activities文档描述了activity的过渡是怎样协调它们的生命周期)
对于更多示例代码,怎样绑定到service,请查看ApiDemos中的RemoteService.java类。
管理绑定Service的生命周期
当所有的客户端都从service解绑后,Android系统会销毁它(除非它也通过onStartCommand()进行的启动)。因此,如果它只是个绑定的service你就不需要管理它的生命周期——Android系统会基于是否有客户端绑定到它对它进行管理。
然而,如果你选择实现了onStartCommand()回调方法,那你必须明确地停止它,因此service现在被认为是启动的(started)。在这种情况下,service会一直运行直到使用stopSelf()进行停止或者其它组件调用stopService(),而不管是否有客户端绑定到它。
另外,如果service是启动的并且接受绑定,那么系统会调用onUnbind()方法,如果你想要客户端在下一次绑定到service接收onRebind()回调,那你可以选择返回true。onRebind()返回void,但客户端在它的onServiceConnected()仍会接收到IBinder。下面的图1说明了这种生命周期的逻辑。
图1 混合启动的service的生命周期。
对于启动service生命周期的更多信息,请查看Services文档。