快速查看
bound服务允许被其它控件绑定,以便与之交互并进行进程间通信一旦所有的客户端都解除了绑定,bound服务将被销毁。除非该服务同时又是started类型的。
在本文中(参见目录)
关键类
范例
bound服务是客户端-服务器模式的服务。bound服务允许组件(比如activity)对其进行绑定、发送请求、接收响应、甚至进行进程间通信(IPC)。 bound服务一般只在为其它应用程序组件服务期间才是存活的,而不会一直在后台保持运行。
本文展示了如何创建一个bound服务,包括如何从其它应用程序组件绑定到该服务。不过,通常你还应该参考服务文档以获取关于服务的更多信息,比如如何从服务中发送通知、如何将服务设置为前台运行等等。 目录
bound服务是 Service 类的一种实现,它允许其它应用程序与其绑定并交互。为了让服务支持绑定,你必须实现onBind() 回调方法。这个方法返回一个 IBinder 对象,此对象定义了客户端与服务进行交互时所需的编程接口。
绑定到一个started服务
正如服务一文中所述,你可以创建一个同时支持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 接口的方式有好几种,后续章节将会对每种技术进行论述。
创建一个支持绑定的服务时,你必须提供一个 IBinder,用作客户端和服务间进行通信的编程接口。定义这类接口的方式有三种:
扩展Binder类如果服务是你的应用程序所私有的,并且与客户端运行于同一个进程中(通常都是如此),你应该通过扩展 Binder类来创建你的接口,并从 onBind()返回一个它的实例。客户端接收该 Binder对象并用它来直接访问 Binder甚至 Service中可用的公共(public)方法。如果你的服务只是为你自己的应用程序执行一些后台工作,那这就是首选的技术方案。不用这种方式来创建接口的理由只有一个,就是服务要被其它应用程序使用或者要跨多个进程使用。使用Messenger如果你需要接口跨越多个进程进行工作,可以通过 Messenger来为服务创建接口。在这种方式下,服务定义一个响应各类消息对象 Message的 Handler。此 Handler是 Messenger与客户端共享同一个 IBinder的基础,它使得客户端可以用消息对象 Message向服务发送指令。此外,客户端还可以定义自己的 Message,以便服务能够往回发送消息。这是执行进程间通信(IPC)最为简便的方式,因为 Messenger会把所有的请求放入一个独立进程中的队列,这样你就不一定非要把服务设计为线程安全的模式了。使用AIDLAndroid接口定义语言AIDL(Android Interface Definition Language)完成以下的所有工作:将对象解析为操作系统可识别的原始形态,并将它们跨进程 序列化(marshal)以完成IPC。前一个使用 Messenger的方式,实际上也是基于AIDL的,它用AIDL作为底层结构。如上所述, Messenger将在一个单独的进程中创建一个包含了所有客户端请求的队列,这样服务每次就只会收到一个请求。可是,如果想让你的服务能同时处理多个请求,那你就可以直接使用AIDL。这种情况下,你的服务必须拥有多线程处理能力,并且是以线程安全的方式编写的。要直接使用AIDL,你必须创建一个 .aidl文件,其中定义了编程的接口。Android SDK 工具使用此文件来生成一个抽象类(abstract class),其中实现了接口及对IPC的处理,然后你就可以在自己的服务中扩展该类。
注意: 绝大多数应用程序都不应该用AIDL来创建bound服务,因为这可能需要多线程处理能力并且会让代码变得更为复杂。 因此,AIDL对绝大多数应用程序都不适用,并且本文也不会讨论如何在服务中使用它的内容。如果你确信需要直接使用AIDL,那请参阅 AIDL 文档。
如果你的服务只用于本地应用程序并且不需要跨进程工作,那你只要实现自己的 Binder 类即可,这样你的客户端就能直接访问服务中的公共方法了。
注意:仅当客户端和服务位于同一个应用程序和进程中,这也是最常见的情况,这种方式才会有用。比如,一个音乐应用需要把一个activity绑定到它自己的后台音乐播放服务上,采用这种方式就会很不错。
以下是设置步骤:
注意:
服务和客户端之所以必须位于同一个应用程序中,是为了让客户端能够正确转换(cast)返回的对象并调用对象的API。 服务和客户端也必须位于同一个进程中,因为这种方式不能执行任何跨进程的序列化(marshalling)操作。
比如,以下是一个服务的示例,它通过实现一个Binder来为客户端访问它内部的方法提供支持:
public class LocalService extends Service {
// 给客户端的Binder
private final IBinder mBinder = new LocalBinder();
// 生成随机数
private final Random mGenerator = new Random();
/**
* 用于客户端Binder的类。
* 因为知道本服务总是运行于与客户端相同的进程中,我们就不需要用IPC进行处理。
*/
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;
}
/** method for clients */
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;
}
}
/** 当按下按钮时调用(该按钮在layout文件中利用android:onClick属性与本方法关联 */
public void onButtonClick(View v) {
if (mBound) {
// 调用LocalService中的方法。
// 不过,如果该调用会导致某些操作的挂起,那么调用应该放入单独的线程中进行,
// 以免降低activity的性能。
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** 定义服务绑定时的回调方法,用于传给bindService() */
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 类。
与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;
/**
* 从客户端接收消息的Handler
*/
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);
}
}
}
/**
* 向客户端公布的用于向IncomingHandler发送信息的Messager
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* 当绑定到服务时,我们向Messager返回接口,
* 用于向服务发送消息
*/
@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*/
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、服务和content provider才可以绑定到服务上——你不能从广播接收器(broadcast receiver)中绑定服务。
因此,要把客户端绑定到服务上,你必须:
当客户端被销毁时,与服务的绑定也将解除。但与服务交互完毕后,或者你的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类。
一旦服务被所有客户端解除绑定,则Android系统将会销毁它(除非它同时又是用onStartCommand()started)。因此,如果你的服务就是一个纯粹的bound服务,那你就不需要管理它的生命周期——Android系统会替你管理,根据是否还有客户端对其绑定即可。
不过,如果你选择实现onStartCommand()回调方法,那么你就必须显式地终止服务,因为此服务现在已经被视为started了。这种情况下,无论是否还存在客户端与其绑定,此服务都会运行下去,直至自行用stopSelf()终止或由其它组件调用stopService()来终止。
此外,如果你的服务是started且允许被绑定,那么系统调用你的onUnbind()方法时,你可以选择返回true。这样作的结果就是,下次客户端绑定时将会收到onRebind()调用(而不是收到onBind()调用)。onRebind()返回void,但客户端仍然能在它的onServiceConnected()回调方法中收到IBinder。图1展示了这种生命周期的运行逻辑。