Bound服务
英文原文:http://developer.android.com/guide/topics/fundamentals/bound-services.html
版本:Android 4.0 r1
译者署名: 呆呆大虾
译者注:黄色底色为未决译文
快速查看在本文中- 简介
- 创建一个Bound服务
- 扩展Binder类
- 使用Messenger
- 绑定一个服务
- 管理Bound服务的生命周期
- Service
- ServiceConnection
- IBinder
- RemoteService
- LocalService
- Services
bound服务是客户端-服务器模式的服务。bound服务允许组件(比如activity)对其进行绑定、发送请求、接收响应、甚至进行进程间通信(IPC)。bound服务一般只在为其它应用程序组件服务期间才是存活的,而不会一直在后台保持运行。
本文展示了如何创建一个bound服务,包括如何从其它应用程序组件绑定到该服务。不过,通常你还应该参考 Services 文档以获取关于服务的更多信息,比如如何从服务中发送通知、如何将服务设置为前台运行等等。
简介
bound服务是 Service类的一种实现,它允许其它应用程序与其绑定并交互。为了让服务支持绑定,你必须实现 onBind() 回调方法。这个方法返回一个 IBinder对象,此对象定义了客户端与服务进行交互时所需的编程接口。
绑定到一个started服务
正如 Services 一文中所述,你可以创建一个同时支持started和bound的服务。也就是说,服务可以通过调用startService() 来启动,这使它一直保持运行,同时它也允许客户端通过调用 bindService() 来与之绑定。
如果你的服务确实可以是started和bound的,那么服务启动后,系统将不会在所有客户端解除绑定时销毁它。取而代之的是,你必须通过调用stopSelf() 或 stopService() 显式终止此服务。
虽然你通常应该要实现 onBind() 或 onStartCommand() 中的一个,但有时需要同时实现两者。比如,音乐播放器的服务也许就需要同时实现后台运行和支持绑定。这样,activity就可以启动服务来播放音乐,并且音乐会一直播放下去,即使用户离开该应用程序也没关系,这个activity可以绑定播放服务来重新获得播放控制权。
请确保已经阅读了 管理Bound服务的生命周期章节,以获取更多向started服务添加绑定时的服务生命周期的有关信息。
客户端可以通过调用 bindService() 方法来绑定服务。在调用时,必须提供一个 ServiceConnection 的实现代码,用于监控与服务的联接。 bindService()将会立即返回,没有返回值。但是Android系统在创建客户端与服务之间的联接时,会调用 ServiceConnection 中的 onServiceConnected() 方法,传递一个 IBinder,客户端将用它与服务进行通信。
多个客户端可以同时联接到一个服务上。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来获取 IBinder。然后,系统会向后续请求绑定的客户端传送这同一个 IBinder,而不再调用 onBind() 。
当最后一个客户端解除绑定后,系统会销毁服务(除非服务同时是通过 startService() 启动的)。
当你实现自己的bound服务时,最重要的工作就是定义 onBind() 回调方法所返回的接口。定义服务 IBinder接口的方式有好几种,后续章节将会对每种技术进行论述。
创建一个Bound服务
创建一个支持绑定的服务时,你必须提供一个 IBinder,用作客户端和服务间进行通信的编程接口。定义这类接口的方式有三种:
- 扩展Binder类
- 如果服务是你的应用程序所私有的,并且与客户端运行于同一个进程中(通常都是如此),你应该通过扩展 Binder类来创建你的接口,并从 onBind() 返回一个它的实例。客户端接收该 Binder对象并用它来直接访问 Binder甚至 Service中可用的公共(public)方法。
如果你的服务只是为你自己的应用程序执行一些后台工作,那这就是首选的技术方案。不用这种方式来创建接口的理由只有一个,就是服务要被其它应用程序使用或者要跨多个进程使用。
- 使用Messenger
- 如果你需要接口跨越多个进程进行工作,可以通过 Messenger来为服务创建接口。在这种方式下,服务定义一个响应各类消息对象 Message的 Handler。此 Handler是 Messenger与客户端共享同一个 IBinder的基础,它使得客户端可以用消息对象 Message向服务发送指令。此外,客户端还可以定义自己的 Message,以便服务能够往回发送消息。
这是执行进程间通信(IPC)最为简便的方式,因为 Messenger会把所有的请求放入一个独立进程中的队列,这样你就不一定非要把服务设计为线程安全的模式了。
- 使用AIDL
- Android接口定义语言AIDL(Android Interface Definition Language)完成以下的所有工作:将对象解析为操作系统可识别的原始形态,并将它们跨进程序列化(marshal)以完成IPC。 前一个使用Messenger的方式,实际上也是基于AIDL的,它用AIDL作为底层结构。如上所述, Messenger将在一个单独的进程中创建一个包含了所有客户端请求的队列,这样服务每次就只会收到一个请求。可是,如果想让你的服务能同时处理多个请求,那你就可以直接使用AIDL。这种情况下,你的服务必须拥有多线程处理能力,并且是以线程安全的方式编写的。
要直接使用AIDL,你必须创建一个.aidl文件,其中定义了编程的接口。Android SDK 工具使用此文件来生成一个抽象类(abstractclass),其中实现了接口及对IPC的处理,然后你就可以在自己的服务中扩展该类。
注意:绝大多数应用程序都不应该用AIDL来创建bound服务,因为这可能需要多线程处理能力并且会让代码变得更为复杂。因此,AIDL对绝大多数应用程序都不适用,并且本文也不会讨论如何在服务中使用它的内容。如果你确信需要直接使用AIDL,那请参阅AIDL文档。
扩展Binder类
如果你的服务只用于本地应用程序并且不需要跨进程工作,那你只要实现自己的 Binder类即可,这样你的客户端就能直接访问服务中的公共方法了。
注意: 仅当客户端和服务位于同一个应用程序和进程中,这也是最常见的情况,这种方式才会有用。比如,一个音乐应用需要把一个activity绑定到它自己的后台音乐播放服务上,采用这种方式就会很不错。
以下是设置步骤:
- 在你的服务中,创建一个 Binder的实例,其中实现以下三者之一:
- 从回调方法 onBind() 中返回 Binder的该实例。
- 在客户端中,在回调方法 onServiceConnected() 中接收 Binder并用所提供的方法对绑定的服务进行调用。
注意:服务和客户端之所以必须位于同一个应用程序中,是为了让客户端能够正确转换(cast)返回的对象并调用对象的API。服务和客户端也必须位于同一个进程中,因为这种方式不能执行任何跨进程的序列化(marshalling)操作。
比如,以下是一个服务的示例,它通过实现一个 Binder来为客户端访问它内部的方法提供支持:
public class LocalService extends Service {
// 给客户端的Binder
private final IBinder mBinder = new LocalBinder();
// 生成随机数
private final Random mGenerator = new Random();
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
LocalBinder为客户端提供了getService()方法,用于返回当前LocalService的实例。这就让客户端可以调用服务中的公共方法。比如,客户端可以调用服务中的getRandomNumber()。
以下是一个绑定到LocalService的activity,当点击按钮时,它会调用getRandomNumber():
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// 绑定到LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 与服务解除绑定
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
public void onButtonClick(View v) {
if (mBound) {
// 调用LocalService中的方法。
// 不过,如果该调用会导致某些操作的挂起,那么调用应该放入单独的线程中进行,
// 以免降低activity的性能。
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// 我们已经绑定到LocalService了,对IBinder进行类型转换(cast)并获得LocalService对象的实例
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
上述例子展示了客户端如何利用 ServiceConnection 和 onServiceConnected() 回调方法绑定到服务。下一节将给出更多有关服务绑定过程的信息。
注意:上述例子并没有明确地解除绑定,但所有的客户端都应该适时地解除绑定(比如activity暂停pause时)。
更多示例代码,请参阅 ApiDemos 中的 LocalService.java类和 LocalServiceActivities.java类。
使用Messenger
与AIDL相比
当你需要进行IPC时,使用 Messenger要比用AIDL实现接口要容易些,因为 Messenger会把所有调用服务的请求放入一个队列。而纯粹的AIDL接口会把这些请求同时发送给服务,这样服务就必须要能够多线程运行。
对于绝大多数应用程序而言,服务没有必要多线程运行,因此利用 Messenger可以让服务一次只处理一个调用。如果 你的服务非要多线程运行,那你就应该用 AIDL来定义接口。
如果你的服务需要与远程进程进行通信,那你可以使用一个 Messenger来提供服务的接口。这种技术能让你无需使用AIDL就能进行进程间通信(IPC)。
以下概括了 Messenger的使用方法:
通过这种方式,客户端不需要调用服务中的“方法”。取而代之的是,客户端发送“消息”( Message对象),服务则接收位于 Handler中的这个消息。
以下是服务使用一个 Messenger做为接口的简单例子:
public class MessengerService extends Service {
static final int MSG_SAY_HELLO = 1;
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);
}
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
请注意 Handler中的 handleMessage() 方法,这里是服务接收输入消息 Message的地方,也是根据 what 数字来决定要执行什么操作的地方。
客户端要做的全部工作就是根据服务返回的 IBinder创建一个 Messenger,并用 send() 方法发送一个消息。例如,以下是一个activity示例,它绑定到上述服务,并向服务发送MSG_SAY_HELLO消息:
public class ActivityMessenger extends Activity {
Messenger mService = null;
boolean mBound;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// 与服务建立联接后将会调用本方法,
// 给出用于和服务交互的对象。
// 我们将用一个Messenger来与服务进行通信,
// 因此这里我们获取到一个原始IBinder对象的客户端实例。
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// 当与服务的联接被意外中断时——也就是说服务的进程崩溃了,
// 将会调用本方法。
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// 创建并向服务发送一个消息,用到了已约定的'what'值
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
请注意,上述例子中没有给出服务是如何响应客户端的。如果你需要服务进行响应,那你还需要在客户端创建一个 Messenger。然后,当客户端接收到 onServiceConnected() 回调后,它再发送一个消息 Message给服务,消息的 send() 方法中的 replyTo 参数里包含了客户端的 Messenger。
在 MessengerService.java(服务)和 MessengerServiceActivities.java(客户端)例程中,你可以看到如何双向发送消息的例子。
绑定一个服务
应用程序组件(客户端)可以通过调用 bindService() 来绑定服务。然后Android系统会调用服务的 onBind() 方法,返回一个用于和服务进行交互的 IBinder。
绑定是异步进行的。 bindService() 将立即返回,并不会向客户端返回 IBinder。为了接收 IBinder,客户端必须创建一个 ServiceConnection 的实例,并把它传给 bindService() 。 ServiceConnection 包含了一个回调方法,系统将会调用该方法来传递客户端所需的那个IBinder。
注意: 只有activity、服务和contentprovider才可以绑定到服务上——你不能从广播接收器(broadcastreceiver)中绑定服务。
因此,要把客户端绑定到服务上,你必须:
- 实现 ServiceConnection 。
你的实现代码必须重写两个回调方法:
- onServiceConnected()
- 系统调用该方法来传递服务的 onBind() 方法所返回的 IBinder。
- onServiceDisconnected()
- 当与服务的联接发生意外中断时,比如服务崩溃或者被杀死时,Android系统将会调用该方法。客户端解除绑定时,不会调用该方法。
- 调用 bindService() ,传入已实现的 ServiceConnection 。
- 当系统调用你的 onServiceConnected() 回调方法时,你可以利用接口中定义的方法开始对服务的调用。
- 要断开与服务的联接,请调用 unbindService() 。
当客户端被销毁时,与服务的绑定也将解除。但与服务交互完毕后,或者你的activity进入pause状态时,你都应该确保解除绑定,以便服务能够在用完后及时关闭。(绑定和解除绑定的合适时机将在后续章节中继续讨论。)
例如,以下代码段将客户端与前面 扩展Binder类创建的服务联接,而要做的全部工作就是把返回的 IBinder转换(cast)为LocalService类,并获取LocalService的实例:
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// 与服务的联接建立之后将会调用
public void onServiceConnected(ComponentName className, IBinder service) {
// 因为我们已经与明显是运行于同一进程中的服务建立了联接,
// 我们就可以把它的IBinder转换为一个实体类并直接访问它。
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
// 与服务的联接意外中断时将会调用
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
利用这个 ServiceConnection ,客户端就能够把它传入 bindService() 完成与服务的绑定。例如:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
其它注意事项
以下是有关绑定服务的一些重要注意事项:
注意: 你通常不应该在activity的onResume() 和 onPause()中绑定和解除绑定,因为这两个回调方法在每次切换生命周期状态时都会发生,这时你应该让处理工作最少化。而且,如果应用程序中有多个activity都绑定到同一个服务上,则在两个activity间切换时都会发生状态转换,因为当前activity解除绑定(在pause时)后,紧接着下一个activity又会进行绑定(resume时),所以服务也许在销毁后马上就要重建。(这种activity状态转换、多个activity间的生命周期协作在 Activities 文档中描述。)
更多展示绑定服务的示例代码,请参阅 ApiDemos中的 RemoteService.java类。
管理Bound服务的生命周期
图 1. started且允许绑定的服务的生命周期
一旦服务被所有客户端解除绑定,则Android系统将会销毁它(除非它同时又是用 onStartCommand()started)。因此,如果你的服务就是一个纯粹的bound服务,那你就不需要管理它的生命周期——Android系统会替你管理,根据是否还有客户端对其绑定即可。
不过,如果你选择实现 onStartCommand()回调方法,那么你就必须显式地终止服务,因为此服务现在已经被视为started了。这种情况下,无论是否还存在客户端与其绑定,此服务都会运行下去,直至自行用stopSelf() 终止或由其它组件调用 stopService() 来终止。
此外,如果你的服务是started且允许被绑定,那么系统调用你的 onUnbind() 方法时,你可以选择返回true。这样作的结果就是,下次客户端绑定时将会收到onRebind() 调用(而不是收到 onBind() 调用)。 onRebind() 返回void,但客户端仍然能在它的 onServiceConnected() 回调方法中收到 IBinder。图1展示了这种生命周期的运行逻辑。
关于started服务生命周期的更多信息,请参阅 Services文档。