Android开发-API指南-Bound 类型的服务

Bound Services

英文原文:http://developer.android.com/guide/components/bound-services.html
采集(更新)日期:2014-12-24
原博客:http://blog.sina.com.cn/s/blog_48d4913001010696.html

Bound 类型的服务是客户端-服务器模式的服务端。Bound 类型的服务允许组件(比如 Activity)对其进行绑定、发送请求、接收响应、甚至进行进程间通信(IPC)。 Bound 类型的服务一般仅存活于为其他应用程序组件服务期间,而非一直在后台保持运行。

本文展示了如何创建一个 Bound 类型的服务,包括其他应用程序组件如何绑定该服务。不过,通常你还应该参考 服务 文档以获取关于服务的更多信息,比如如何从服务中发送通知、如何将服务设置为前台运行等等。

简介

Bound 类型的服务是 Service 类的一种实现,它允许其他应用程序与其绑定并交互。 要让服务支持绑定,必须实现 onBind() 回调方法。这个方法返回一个 IBinder 对象,此对象定义了客户端与服务进行交互时所需的编程接口。

客户端可以通过调用 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 。此 HandlerMessenger 与客户端能够共用同一个 IBinder 的基础,它使得客户端可以用消息对象 Message 向服务发送指令。此外,客户端还可以定义自己的 Message ,以便服务能够回送消息。

这是进行进程间通信(IPC)最为简便的方式,因为 Messenger 会把所有的请求放入一个独立线程中的队列,这样就不一定非要把服务设计为线程安全模式了。

使用 AIDL
Android 接口定义语言 AIDL(Android Interface Definition Language)完成以下全部工作: 将对象解析为操作系统可识别的简单类型(primitive),将它们跨进程序列化(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 供客户端访问其内部方法:

 1 public class LocalService extends Service {
 2     // 给客户端的Binder
 3     private final IBinder mBinder = new LocalBinder();
 4     // 生成随机数
 5     private final Random mGenerator = new Random();
 6 
 7     /**
 8      * 用于客户端Binder的类。
 9      * 因为知道本服务总是运行于与客户端相同的进程中,我们就不需要用IPC进行处理。
10      */
11     public class LocalBinder extends Binder {
12         LocalService getService() {
13 &nbsp            // 返回 LocalService 本身,以便客户端调用内部的公共方法
14             return LocalService.this;
15         }
16     }
17 
18     @Override
19     public IBinder onBind(Intent intent) {
20         return mBinder;
21     }
22 
23     /** 供客户端调用的方法 */
24     public int getRandomNumber() {
25       return mGenerator.nextInt(100);
26     }
27 }

LocalBinder 为客户端提供了 getService() 方法,用于返回当前 LocalService 的实例。 这就使得客户端能够调用服务中的公共方法。比如,客户端可以调用服务中的 getRandomNumber() 。

以下是一个绑定到 LocalService 的 Activity,当点击按钮时,它会调用 getRandomNumber():

 1 public class BindingActivity extends Activity {
 2     LocalService mService;
 3     boolean mBound = false;
 4 
 5     @Override
 6     protected void onCreate(Bundle savedInstanceState) {
 7         super.onCreate(savedInstanceState);
 8         setContentView(R.layout.main);
 9     }
10 
11     @Override
12     protected void onStart() {
13         super.onStart();
14         // 绑定到LocalService
15         Intent intent = new Intent(this, LocalService.class);
16         bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
17     }
18 
19     @Override
20     protected void onStop() {
21         super.onStop();
22         // 与服务解除绑定
23         if (mBound) {
24             unbindService(mConnection);
25             mBound = false;
26         }
27     }
28 
29     /** 当按下按钮时调用(该按钮在layout文件中利用android:onClick属性与本方法关联 */
30     public void onButtonClick(View v) {
31         if (mBound) {
32             // 调用LocalService中的方法。
33             // 不过,如果该调用会导致某些操作的挂起,那么调用应该放入单独的线程中进行,
34             // 以免降低activity的性能。
35             int num = mService.getRandomNumber();
36             Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
37         }
38     }
39 
40     /** 定义服务绑定时的回调方法,用于传给bindService() */
41     private ServiceConnection mConnection = new ServiceConnection() {
42 
43         @Override
44         public void onServiceConnected(ComponentName className,
45                 IBinder service) {
46             // 我们已经绑定到LocalService了,对IBinder进行类型转换(cast)并获得LocalService对象的实例
47             LocalBinder binder = (LocalBinder) service;
48             mService = binder.getService();
49             mBound = true;
50         }
51 
52         @Override
53         public void onServiceDisconnected(ComponentName arg0) {
54             mBound = false;
55         }
56     };
57 }

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

注意: 上述例子并没有给出解除绑定的代码,但所有的客户端都应该适时地解除绑定(比如当 Activity 进入暂停状态时)。

更多示例代码,请参阅 ApiDemos 中的 LocalService.java 类和 LocalServiceActivities.java 类。

使用 Messenger

如果服务需要与远程进程进行通信,可以用一个 Messenger 来提供该服务的接口。这样无需使用 AIDL 就能实现进程间通信(IPC)。

以下概括了 Messenger 的使用方法:

通过这种方式,客户端不需要调用服务息”( Message 对象),服务则接收位于 Handler 中的这个消息。

以下是服务使用 Messenger 做为接口的简单示例:

 1 public class MessengerService extends Service {
 2     /** 发送给服务的用于显示信息的指令*/
 3     static final int MSG_SAY_HELLO = 1;
 4 
 5     /**
 6      * 从客户端接收消息的Handler
 7      */
 8     class IncomingHandler extends Handler {
 9         @Override
10         public void handleMessage(Message msg) {
11             switch (msg.what) {
12                 case MSG_SAY_HELLO:
13                     Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
14                     break;
15                 default:
16                     super.handleMessage(msg);
17             }
18         }
19     }
20 
21     /**
22      * 向客户端公布的用于向IncomingHandler发送信息的Messager
23      */
24     final Messenger mMessenger = new Messenger(new IncomingHandler());
25 
26     /**
27      * 当绑定到服务时,我们向Messager返回接口,
28      * 用于向服务发送消息
29      */
30     @Override
31     public IBinder onBind(Intent intent) {
32         Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
33         return mMessenger.getBinder();
34     }
35 }

请注意 Handler 中的 handleMessage() 方法,这里是服务接收输入消息 Message 的地方,也是根据数字 what 来决定要执行什么操作的地方。

客户端要做的全部工作就是根据服务返回的 IBinder 创建一个 Messenger ,并用 send() 方法发送一个消息。例如,以下是一个 Activity 示例,它绑定到上述服务,并向服务发送 MSG_SAY_HELLO 消息:

 1 public class ActivityMessenger extends Activity {
 2     /** 用于和服务通信的Messenger*/
 3     Messenger mService = null;
 4 
 5     /** 标识我们是否已绑定服务的标志 */
 6     boolean mBound;
 7 
 8     /**
 9      * 与服务的主接口进行交互的类
10      */
11     private ServiceConnection mConnection = new ServiceConnection() {
12         public void onServiceConnected(ComponentName className, IBinder service) {
13             // 与服务建立联接后将会调用本方法,
14             // 给出用于和服务交互的对象。
15             // 我们将用一个Messenger来与服务进行通信,
16             // 因此这里我们获取到一个原始IBinder对象的客户端实例。
17             mService = new Messenger(service);
18             mBound = true;
19         }
20 
21         public void onServiceDisconnected(ComponentName className) {
22             // 当与服务的联接被意外中断时——也就是说服务的进程崩溃了,
23             // 将会调用本方法。
24             mService = null;
25             mBound = false;
26         }
27     };
28 
29     public void sayHello(View v) {
30         if (!mBound) return;
31         // 创建并向服务发送一个消息,用到了已约定的'what'值
32         Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
33         try {
34             mService.send(msg);
35         } catch (RemoteException e) {
36             e.printStackTrace();
37         }
38     }
39 
40     @Override
41     protected void onCreate(Bundle savedInstanceState) {
42         super.onCreate(savedInstanceState);
43         setContentView(R.layout.main);
44     }
45 
46     @Override
47     protected void onStart() {
48         super.onStart();
49         // Bind to the service
50         bindService(new Intent(this, MessengerService.class), mConnection,
51             Context.BIND_AUTO_CREATE);
52     }
53 
54     @Override
55     protected void onStop() {
56         super.onStop();
57         // Unbind from the service
58         if (mBound) {
59             unbindService(mConnection);
60             mBound = false;
61         }
62     }
63 }

请注意,上述例子中没有给出服务是如何响应客户端的。如果需要服务进行响应,那还要在客户端创建一个 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 进入暂停状态时,都应该确保解除绑定,以便服务能够在用完后及时关闭。 (绑定和解除绑定的合适时机将在后续章节中继续讨论。)

例如,以下代码段将客户端与前面 扩展Binder类 创建的服务联接,而要做的全部工作就是把返回的 IBinder 转换(cast)为 LocalService 类,并获取 LocalService 的实例:

 1 LocalService mService;
 2 private ServiceConnection mConnection = new ServiceConnection() {
 3     // 与服务的联接建立之后将会调用
 4     public void onServiceConnected(ComponentName className, IBinder service) {
 5         // 因为我们已经与明显是运行于同一进程中的服务建立了联接,
 6         // 我们就可以把它的IBinder转换为一个实体类并直接访问它。
 7           LocalBinder binder = (LocalBinder) service;
 8         mService = binder.getService();
 9         mBound = true;
10     }
11 
12     // 与服务的联接意外中断时将会调用
13     public void onServiceDisconnected(ComponentName className) {
14         Log.e(TAG, "onServiceDisconnected");
15         mBound = false;
16     }
17 };

利用这个 ServiceConnection ,客户端就能够把它传入 bindService() 完成与服务的绑定。例如:

1 Intent intent = new Intent(this, LocalService.class);
2 bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

其他注意事项

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

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

    注意: 通常不得在 Activity 的 onResume()onPause() 方法中进行绑定和解除绑定,因为这两个回调方法在每次切换生命周期状态时都会发生,方法里的工作应该尽可能少。 而且,如果应用程序中有多个 Activity 都绑定到同一个服务上,则在两个 Activity 间切换时都会发生状态转换, 因为当前 Activity 解除绑定(在暂停时)后,紧接着下一个 Activity 又会进行绑定(恢复时),所以服务也许在销毁后马上就要重建。 (这种 Activity 的状态转换、多个 Activity 间的生命周期协作在 Activity 文档中介绍。)

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

管理 Bound 类服务的生命周期

当所有的客户端都与某个服务解除了绑定,Android 系统就会销毁该服务(除非它同时又是用 onStartCommand() 启动的)。因此,如果某个服务就是一个纯粹的 Bound 类服务,那就不需要去管理它的生命周期 —— Android 系统会根据是否存在已绑定的客户端来自动管理。

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

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

图 1. Started 类型且允许绑定的服务的生命周期

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

posted on 2014-12-25 15:09  呆呆大虾  阅读(1431)  评论(0编辑  收藏  举报

导航