绑定服务
绑定服务是在客户端 - 服务器接口中的服务器。绑定服务允许组件(如activity)绑定到该服务,发送请求,接收响应,甚至进行进程间通信(IPC)。绑定服务通常只活动在它服务于其他应用程序组件时,并不会一直在后台运行。
本文向您介绍如何创建一个绑定服务,包括如何从其他应用程序组件绑定到该服务上。然而,你也应该参考Services文档,了解更多关于普通情况下服务的额外信息,比如如何从服务发送通知,设置服务在前台运行等。
基础知识
绑定的服务是Services类的一个实现,允许其他应用程序绑定到它,并与它进行交互。为了提供一个服务绑定,你必须实现onBind()
回调方法。此方法返回一个IBinder
对象,它定义了程序的接口, 客户端可以用它跟服务进行交互。
客户端通过调用bindService()能绑定到服务上,当它这么做了,它必须提供一个ServiceConnection
实现,其监视与该服务的连接。bindService()
方法立即返回的值为空,但是当Android系统创建客户端和服务之间的连接时,它调用ServiceConnection
中的onServiceConnected()方法,以传递IBinder,
客户端可以用它来跟服务进行通信。
多个客户端可以同事连接到一个服务上。但是,系统只以第一个客户端绑定时调用onBind()
方法来获得的IBinder
对象。然后,系统将传递相同IBinder
给绑定该服务上的其他客户端,而不再调用onBind()
了。
当最后一个客户端从服务解除绑定,则系统会销毁该服务(除非服务还通过startService()
被启动)。
当你实现你的绑定服务,最重要的部分是定义onBind()
回调方法返回接口。有几种不同的方式,你可以定义服务的IBinder
接口,下面一节讨论每一项技术。
创建一个服务绑定
当创建一个提供绑定的服务,你必须提供IBinder
,它提供客户端与服务进行交互的程序接口,你有三种方法可以定义这些接口:
-
扩展Binder类(
Extending the Binder class)
-
如果你的服务是专用于自己的应用程序,并与
客户端运行在同一个进程中(这是常见的),你应该通过扩展
Binder
类创建你自己的接口,并从 onBind()返回它的一个实例。该客户端接收IBinder
,并可以使用它来 直接访问现有的公共方法,无论这些方法是在IBinder
的视线中,还是在Service中
。当你的服务只是为你自己的应用在后台运行时,这是一种首选的技术。唯一一个你不建立自己接口的原因是你的服务被其他应用程序所用或者使用了多个分开的进程。
使用消息传递其(
Using a Messenger)
-
如果您需要的接口在不同的进程中工作,你可以为你的服务用一个
Messenger
创建一个接口。以这种方式,该服务定义一个
Handler
来响应不同类型的Message
对象。该Handler 是 Messenger 基础,它可以 与客户端 共享一个
IBinder
,允许客户端使用Message
对象 发送命令到服务端。此外,客户端可以定义一个 自己的Message
,以便 服务端可以将消息发送回。这是执行进程间通信(IPC)最简单的方法,因为
Messenger
队列所有的请求都在单一的线程中,这样你就不必设计你的服务为线程安全( thread-safe)。
使用AIDL
-
AIDL(Android接口定义语言)执行把一个对象分解到操作系统能理解的基元,并安排它们到各个进程间去完成IPC等所有工作。上面提到的技术,使用一个
Messenger就是基于AIDL和其下面的结构。如前所述,
Messenger创建一个队列,把所有的请求都放在一个线程中,所有服务一次只接受一个请求。然而如果你的服务想要同时接受多个请求,那么你可以直接创建AIDL。在这种情况下,你的服务务必要有执行多个线程的能力,并且是线程安全的。
为了直接使用AIDL,你必须创建一个
.aidl
编程接口文件。Android的SDK工具使用这个文件来生成实现该接口并处理IPC,然后您可以为您服务内延伸一个抽象类。
注意:大多数应用程序不应该使用AIDL创建绑定服务,因为它可能需要多线程功能,导致实现起来更复杂。因此,AIDL是不适合大多数的应用程序,本文档不讨论如何使用它为您服务。如果你确定你需要直接使用AIDL,请看到AIDL 文档。
扩展Binder类(Extending the Binder class)
如果您的服务只用于本地应用程序,不需要跨进程工作,那么你就可以实现自己的Binder类,它为客户端提供直接访问服务中的公共方法。
注意:这仅限于客户端和服务都在相同的应用和进程中,这是最常见的。例如,这个方法对于一个音乐播放器应用程序将会非常有用,它需要绑定一个activity到它自己的服务上,用来后台音乐播放。
以下是如何设置Binder类:
- 在你的服务中创建一个Binder实例,并实现以下功能之一:
- 包含客户端可以调用的公共方法。
- 返回当前的服务实例,其中有客户端可以调用的公共方法。
- 或者,返回这个服务包含的另一个类的实例,并包含客户端可以访问的公共方法。
- 从
onBind()
回调方法返回Binder实例。 - 在客户端,接收从
onServiceConnected()
回调方法返回的Binder
,并用提供的方法来调用绑定服务。
注意:之所以服务和客户端必须在相同的应用程序是因为这样的客户端可以转换返回的对象,并正确地调用它的API。服务和客户端也必须在相同的进程中,因为该技术不能跨进程执行。
例如,下面是为客户端提供通过Binder实现接入服务中方法的示例:
public class LocalServiceextendsService{
// Binder given to clients
private final IBinder mBinder =new LocalBinder();
// Random number generator
private final Random mGenerator =new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with 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()
。
下面是当点击一个按钮时,activity绑定到
,并调用LocalService
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();
// Bind to LocalService
Intent intent =newIntent(this,LocalService.class);
bindService(intent, mConnection,Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop(){
super.onStop();
// Unbind from the service
if(mBound){
unbindService(mConnection);
mBound =false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v){
if(mBound){
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this,"number: "+ num,Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection =newServiceConnection(){
@Override
public void onServiceConnected(ComponentName className,
IBinder service){
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder =(LocalBinder) service;
mService = binder.getService();
mBound =true;
}
@Override
public void onServiceDisconnected(ComponentName arg0){
mBound =false;
}
};
}
上面的示例演示了如何在客户端使用 ServiceConnection
的实现和onServiceConnected()
回调绑定到服务。下一节将提供有关绑定到服务这一过程的详细信息。
注意:上面的例子中并没有显式地从服务解除绑定,但所有的客户端在适当的时候应解除绑定(当活动暂停如)。
欲了解更多的示例代码,请参阅ApiDemos中的LocalService.java
类和LocalServiceActivities.java
类。
使用Messenger(Using a Messenger)
如果您需要服务与远程进程进行通信,那么你可以使用一个Messenger为服务提供接口。这种技术可以让您无需使用AIDL进行进程间通信(IPC)。
下面是如何使用Messenger的总结:
- 该服务实现一个
Handler
,它接收来自客户端每次调用的回调。 - 该
Handler
是用于创建一个对象(其为Handler的一个引用)。 - 该
Messenger
创建一个IBinder
,服务从onBind()方法将其返回到客户端。 - 客户端使用此
IBinder
来实例化这个Messenger,该客户端可用来向服务端发送Message对象。
- 服务通过它的Handler接收的每个
Message
-确切的说,是在handleMessage()
方法中接收。
通过这种方式,在服务端不存在让客户端调用的方法。相反,客户端提供了“消息”(Message
对象),与此同时服务在Handler中接收。
下面是为服务提供一个Messenger接口的简单示例:
public class MessengerService extends Service{
/** Command to the service to display a message */
staticfinalint MSG_SAY_HELLO =1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler{
@Override
publicvoid 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()
方法,服务在其中接收正进入的Message
并且根据 what
参数决定要做什么。
所有的客户需要做的就是根据服务返回的IBinder创建一个Messenger
,并使用send()发送消息。例如,下面是一个简单的activity绑定到服务上,并传递MSG_SAY_HELLO
消息给服务:
public class ActivityMessenger extends Activity{
/** Messenger for communicating with the service. */
Messenger mService =null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection(){
publicvoid onServiceConnected(ComponentName className,IBinder service){
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService =new Messenger(service);
mBound =true;
}
public void onServiceDisconnected(ComponentName className){
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService =null;
mBound =false;
}
};
public void sayHello(View v){
if(!mBound)return;
// Create and send a message to the service, using a supported 'what' value
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
(客户端)提供双向通信,具体请参考相关实例程序。
绑定到服务(Binding to a Service)
应用程序组件(客户端)可以通过调用 bindService()
绑定到服务。而Android系统则调用服务的onBind()
方法,该方法返回一个IBinder
,用于与服务进行交互。
绑定是异步的。bindService()
立即返回,并且不会返回IBinder
给客户端。要接收IBinder
,客户端必须创建一个ServiceConnection实例,并将其传递给bindService()
。ServiceConnection
包含系统调用用来传递回调方法IBinder
。
注意:只有activity,服务和内容提供者可以绑定到一个服务上—你不能从广播接收者绑定到服务上。
因此,从客户端绑定到服务端,你必须:
- 实现
ServiceConnection
。你的实现必须重写两个回调方法:
-
系统调用它来 传递
由服务的
onBind()
方法返回的IBinder
。 - Android系统调用这个方法,在连接到服务意外丢失时,如当服务崩溃或已被销毁。当客户端解除绑定时,这个方法不会被调用。
onServiceConnected()
onServiceDisconnected()
-
系统调用它来 传递
由服务的
- 调用
bindService()
,传递ServiceConnection
实现。 - 当系统调用你的
onServiceConnected()
回调方法,你就可以开始使用接口中定义的方法来连接服务。 - 要从服务断开连接,调用
unbindService()
。如果您的客户端被销毁,它会从服务上解除绑定,但你应该总是在你已经与服务交互完成或者你的activity暂停时解除绑定,以便服务能在它没有被使用的时候关闭。(适当的时候绑定和取消绑定下面再讨论)。
例如,下面的代码片段是客户端连接到由上面的 extending the Binder class中创建的服务,所以一切必须做的是转换返回的IBinder
给
类,并要求LocalService
实例:LocalService
LocalService mService;
private ServiceConnection mConnection =new ServiceConnection(){
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className,IBinder service){
// Because we have bound to an explicit
// service that is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
LocalBinder binder =(LocalBinder) service;
mService = binder.getService();
mBound =true;
}
// Called when the connection with the service disconnects unexpectedly
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 );
- bindService()第一个参数是一个
Intent
,明确的要绑定的服务名(认为的意图可能是隐含的)。 - 第二个参数是
ServiceConnection
对象。 - 第三个参数是一个标志,说明绑定的选项。它通常应该是
BIND_AUTO_CREATE
,为了能够在服务不存在的情况下创建一个服务。其他可选值有BIND_DEBUG_UNBIND
和BIND_NOT_FOREGROUND
,或0
为无。
其他注意事项(Additional notes)
下面是一些关于绑定到一个服务的重要注意事项:
- 你应该总是捕获
DeadObjectException
异常,当异常被抛出时,连接已经中断。这是通过远程方法抛出的唯一异常。 - 在整个进程中的对象是引用的计数。
- 在客户端生命周期的创建和销毁时,你应该成对使用绑定和解绑。例如:
- 如果你只是在你的activity可见的时候才需要与服务进行交互,你应该在
ONSTART()
时绑定并在onStop()时取消绑定。 - 如果你希望即使activity在后台停止时也能接收响应,那么你应该在onCreate()方法中绑定到服务并在onDestroy()方法中解绑。需要注意的是,这个实现使得你的activity需要在所有服务运行的时候占用该服务(即便在后台运行时)。所以如果服务存在于另一进程中,那么你增加了进程的比重,系统也因此将更有可能性将其杀死。
注意:通常不应该在你activity的
onResume()
和onPause()
期间绑定和解绑,因为这些回调发生在每一个生命周期的过渡时期,你应该把此期间的发生的进程减小到最少。另外,如果有多个activity绑定到同一个服务上,两个activity间必然存在着转换过渡时期,当前的activity在下一个activity绑定(在resume期间)之前,进行解绑(在Pause)期间,服务可能会被销毁或者重新创建。(这个activity转换过渡中,关于activity如何协调其生命周期的过程,在Activities文档中做了详细的描述) - 如果你只是在你的activity可见的时候才需要与服务进行交互,你应该在
欲了解更多的示例代码,演示如何绑定到服务,请参阅ApiDemos中的RemoteService.java
类。
管理绑定服务的生命周期(Managing the Lifecycle of a Bound Service)
当一个服务于从所有客户端上解绑,Android系统会销毁它(除非它也开始于onStartCommand()
)。因此,你不必来管理服务的生命周期,Android系统将根据其是否被绑定到客户端来管理其生命周期。
但是,如果您选择实现onStartCommand()
回调方法,那么你必须明确的停止服务,因为该服务现在被认为是已启动。在这种情况下,服务运行,直到服务通过stopSelf()
停止自己或另一组件调用stopService()
,而不管它是否被绑定到任何客户端。
另外,如果您的服务已启动,并接收绑定,那么当系统调用你的onUnbind()
方法,你可以选择返回真
,如果你希望下一次客户端绑定到服务时接收一个onRebind()方法的调用(而不是接收一个onBind()方法的调用)。onRebind()返回一个空值,但客户端依然可以接收到onServiceConnected()回调方法中的IBinder。图1展示了这种服务生命周期的逻辑。
图1.关于已启动并允许绑定的服务的生命周期。
有关一个已被启动的服务的整个生命周期的详细信息,请参阅 Services文档。