to top

Bound Services

快速查看
bound服务允许被其它控件绑定,以便与之交互并进行进程间通信
一旦所有的客户端都解除了绑定,bound服务将被销毁。除非该服务同时又是started类型的。
在本文中(参见目录)
关键类
Service
ServiceConnection
IBinder
范例
RemoteService
LocalService

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 接口的方式有好几种,后续章节将会对每种技术进行论述。

创建一个Bound服务

创建一个支持绑定的服务时,你必须提供一个 IBinder ,用作客户端和服务间进行通信的编程接口。定义这类接口的方式有三种:

扩展Binder类
如果服务是你的应用程序所私有的,并且与客户端运行于同一个进程中(通常都是如此),你应该通过扩展Binder类来创建你的接口,并从onBind()返回一个它的实例。客户端接收该Binder对象并用它来直接访问Binder甚至Service中可用的公共(public)方法。
如果你的服务只是为你自己的应用程序执行一些后台工作,那这就是首选的技术方案。不用这种方式来创建接口的理由只有一个,就是服务要被其它应用程序使用或者要跨多个进程使用。
使用Messenger
如果你需要接口跨越多个进程进行工作,可以通过Messenger来为服务创建接口。在这种方式下,服务定义一个响应各类消息对象MessageHandler。此HandlerMessenger与客户端共享同一个IBinder的基础,它使得客户端可以用消息对象Message向服务发送指令。此外,客户端还可以定义自己的Message,以便服务能够往回发送消息。
这是执行进程间通信(IPC)最为简便的方式,因为Messenger会把所有的请求放入一个独立进程中的队列,这样你就不一定非要把服务设计为线程安全的模式了。
使用AIDL
Android接口定义语言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类

如果你的服务只用于本地应用程序并且不需要跨进程工作,那你只要实现自己的 Binder 类即可,这样你的客户端就能直接访问服务中的公共方法了。

注意:仅当客户端和服务位于同一个应用程序和进程中,这也是最常见的情况,这种方式才会有用。比如,一个音乐应用需要把一个activity绑定到它自己的后台音乐播放服务上,采用这种方式就会很不错。

以下是设置步骤:

  1. 在你的服务中,创建一个Binder的实例,其中实现以下三者之一:
    • 包含了可供客户端调用的公共方法
    • 返回当前Service实例,其中包含了可供客户端调用的公共方法。
    • 或者,返回内含服务类的其它类的一个实例,服务中包含了可供客户端调用的公共方法。
  2. 从回调方法onBind()中返回Binder的该实例。
  3. 在客户端中,在回调方法onServiceConnected()中接收Binder并用所提供的方法对绑定的服务进行调用。
注意:

服务和客户端之所以必须位于同一个应用程序中,是为了让客户端能够正确转换(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;
       
}
   
};
}

上述例子展示了客户端如何利用 ServiceConnectiononServiceConnected() 回调方法绑定到服务。下一节将给出更多有关服务绑定过程的信息。

注意:

上述例子并没有明确地解除绑定,但所有的客户端都应该适时地解除绑定(比如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;

   
/**
     * 从客户端接收消息的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)中绑定服务。

因此,要把客户端绑定到服务上,你必须:

  1. 实现ServiceConnection
    你的实现代码必须重写两个回调方法:
    onServiceConnected()
    系统调用该方法来传递服务的onBind()方法所返回的IBinder
    onServiceDisconnected()
    当与服务的联接发生意外中断时,比如服务崩溃或者被杀死时,Android系统将会调用该方法。客户端解除绑定时,不会调用该方法。
  2. 调用bindService(),传入已实现的ServiceConnection
  3. 当系统调用你的onServiceConnected()回调方法时,你可以利用接口中定义的方法开始对服务的调用。
  4. 要断开与服务的联接,请调用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);

其它注意事项

以下是有关绑定服务的一些重要注意事项:

  • 你应该确保捕获DeadObjectException异常,当联接中断时会抛出该异常。这是远程方法唯一会抛出的异常。
  • 对象的引用计数是跨进程的。
  • 你通常应该成对地进行绑定和解除绑定,并与客户端生命周期的启动和结束过程相呼应。比如:
    • 如果仅当你的activity可见时才需要与服务进行交互,则你应该在onStart()中进行绑定,并在onStop()中解除绑定。
    • 如果你的activity需要在stopped后并进入后台期间仍然能接收响应,则你可以在onCreate()中进行绑定,并在[1]中解除绑定。请注意这表明你的activity在整个运行期间都需要使用服务(即使在后台),因此假如服务位于其它进程中,则你会增加进程的重量级,进程也会更容易被系统杀死。

注意:你通常应该在activity的onResume()onPause()中绑定和解除绑定,因为这两个回调方法在每次切换生命周期状态时都会发生,这时你应该让处理工作最少化。而且,如果应用程序中有多个activity都绑定到同一个服务上,则在两个activity间切换时都会发生状态转换,因为当前activity解除绑定(在pause时)后,紧接着下一个activity又会进行绑定(resume时),所以服务也许在销毁后马上就要重建。(这种activity状态转换、多个activity间的生命周期协作在Activities文档中描述。)

更多展示绑定服务的示例代码,请参阅ApiDemos中的RemoteService.java类。

管理Bound服务的生命周期

Service binding tree lifecycle.png

图 1. started且允许绑定的服务的生命周期

一旦服务被所有客户端解除绑定,则Android系统将会销毁它(除非它同时又是用onStartCommand()started)。因此,如果你的服务就是一个纯粹的bound服务,那你就不需要管理它的生命周期——Android系统会替你管理,根据是否还有客户端对其绑定即可。

不过,如果你选择实现onStartCommand()回调方法,那么你就必须显式地终止服务,因为此服务现在已经被视为started了。这种情况下,无论是否还存在客户端与其绑定,此服务都会运行下去,直至自行用stopSelf()终止或由其它组件调用stopService()来终止。

此外,如果你的服务是started且允许被绑定,那么系统调用你的onUnbind()方法时,你可以选择返回true。这样作的结果就是,下次客户端绑定时将会收到onRebind()调用(而不是收到onBind()调用)。onRebind()返回void,但客户端仍然能在它的onServiceConnected()回调方法中收到IBinder。图1展示了这种生命周期的运行逻辑。

关于started服务生命周期的更多信息,请参阅服务文档。