目录
- 0x10 介绍
- 0x20 知识准备
- 0x30 创建绑定服务
- 0x31 扩展 Binder 类
- 0x32 使用 Messenger
- 0x40 绑定到服务
- 0x41 附加说明
- 0x50 管理绑定服务的生命周期
可阅读官方原文,我只是重读旧文顺便记录,也改了一些翻译
https://developer.android.google.cn/guide/components/bound-services.html
0x10 介绍
(被)绑定(的)服务是客户端-服务器接口中的服务器。绑定服务可以让组件(例如 Activity)绑定到服务,发送请求、接收响应,执行进程间通信(IPC)。绑定服务通常只在为其它应用组件服务时处于活动状态,不会无限期在后台运行。
0x20 知识准备
绑定服务是 Service 的一种工作方式,可以让其它应用与其通过绑定实现交互。要提供绑定服务,必须实现 onBind() 回调方法。该方法返回的 IBinder 对象定义了客户端与服务器进行交互的编程接口。
客户端可以通过调用 bindService() 绑定到服务。调用时,客户端必须提供 ServiceConnection 的实现,后者会监控与服务的连接。bindService() 方法 boolean 返回值表示被绑定的服务是否存在或者客户端是否有权访问。当客户端与服务之间的连接创建完成, ServiceConnection 中的 onServiceConnected() 方法会被回调,向客户端传递用来与服务通信的 IBinder。
多个客户端可以同时连接一个服务,不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一个 IBinder 传递给任何其它绑定的客户端。
当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非也通过 startService() 启动了该服务)。
当实现绑定服务时,最重要的环节是定义 onBind() 回调方法返回的接口。可以通过集中不同的方法定义服务的 IBinder 接口,下面会逐一介绍。
0x30 创建绑定服务
创建提供绑定的服务时,必须提供 IBinder,用以提供客户端与服务进行交互的编程接口。可以通过以下三种方式定义接口:
- 扩展 Binder 类
如果服务只是自有应用专用,并且与客户端运行在同一个进程中,则可以通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可以通过它直接访问 Binder 中实现或者 Service 中可用的公共方法。
如果服务知识自有应用的后台工作线程,则优先采用这种方法。不以这种方式创建接口的唯一原因是,服务会被其它应用或不同的进程使用。 - 使用 Messenger
如果需要让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口。服务可以这种方式定义对应于不同类型 Message 对象的 Handler。此 Handler 是 Messenger 的基础,后者随后可以与客户端分享一个 IBinder,从而让客户端能够利用 Message 对象向服务发送命令。此外,客户端还可以定义自有 Messenger,以便服务回传消息。
这是执行进程间通信(IPC)最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,这样就不必对服务进行线程安全设计。 - 使用 AIDL
AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 IPC。上面提到的采用 Messenger 的方法实际上是以 AIDL 作为其底层结构。如上所述,Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果想让服务同时处理多个请求,则可以使用 AIDL。在此情况下,服务必须具有处理多线程的能力,并且采用线程安全式设计。
为了直接使用 AIDL,必须创建一个描述编程接口的 .aidl 文件。Android SDK 中工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可以在服务内对其进行扩展。
0x31 扩展 Binder 类
如果服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder 类,让客户端通过该类直接访问服务中的公共方法。
注:此方法只有在客户端和服务位于同一应用和进程这一常见的情况下才有效。例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效。
以下是实现方法:
- 在服务中,创建一个可以满足下列任一要求的 Binder 实例:
- 包含客户端可调用的公共方法
- 返回当前 Service 实例,其中包含客户端可调用的公共方法
- 或返回由服务承载的其它类的实例,其中包含客户端可调用的公共方法
- 从 onBind() 回调方法返回此 Binder 实例。
- 在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务。
注:之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其 API。服务和客户端还必须在同一进程内,因为此方法不执行任何跨进程编组。
例子:
Service 部分代码如下
public class LocalService extends Service {
// 返回给客户端的 Binder
private final IBinder mBinder = new LocalBinder();
// 随机数生成器
private final Random mGenerator = new Random();
/**
* 客户端 Binder 对应的类
**/
public class LocalBinder extends Binder {
LocalService getService() {
// 返回 LocalService 的实例,客户端可以调用其中的公共方法
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
LocalBinder 为客户端提供 getService() 方法,以检索 LocalService 的当前实例。这样,客户端便可调用服务中的公共方法。例如,客户端可调用服务中的 getRandomNumber()。
Activity 部分代码如下
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 性能,所以应该在单独的线程中调用
// 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();
}
}
/** 定义服务绑定时的回调 */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
0x32 使用 Messenger
如需让服务与远程进程通信,则可使用 Messenger 为服务提供接口。利用此方法,无需使用 AIDL 便可执行进程间通信(IPC)。
实现步骤:
- 服务实现一个 Handler,由其接收来自客户端的每个调用的回调
- Handler 用于创建 Messenger 对象(对 Handler 的引用)
- Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
- 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务
- 服务在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message
这样,客户端并没有调用服务的“方法”。而客户端传递的“消息”(Message 对象)是服务在其 Handler 中接收的。
Service 例子:
public class MessengerService extends Service {
static final int MSG_SAY_HELLO = 1;
/**
* 处理客户端发来的消息的 Handler
*/
class IncomingHandler extends Handler {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
/** 客户端用来给 IncomingHandler 发送消息的 Messenger */
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* 当绑定到服务时,返回可以给服务发送消息的接口
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
Activity 例子:
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 对象创建客户端使用的 Messenger
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// 当与服务间的连接意外断开时,这个方法被调用。
// 例如进程崩溃
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// 创建一个 Message 对象
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();
// 绑定到服务
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 与服务解除绑定
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
0x40 绑定到服务
应用组件(客户端)可通过调用 bindService() 绑定到服务。Android 系统随后调用服务的 onBind() 方法返回用于与服务交互的 IBinder。
绑定是异步的,bindService() 会立即返回一个 boolean 值,而不会返回 IBinder 给客户端。要接收 IBinder,客户端必须创建一个 ServiceConnection 实例,并将其传递给 bindService()。ServiceConnection 包括一个回调方法,系统通过调用它来传递 IBinder。
注:只有 Activity、Service 和 ContentProvider 可以绑定到服务,BroadcastReceiver 无法绑定到服务。
因此,要想从您的客户端绑定到服务,必须:
- 实现 ServiceConnection,重写两个回调方法:
- onServiceConnected()
系统会调用该方法以传递服务的 onBind() 方法返回的 IBinder。 - onServiceDisconnected()
Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统不会调用该方法。
- 调用 bindService(),传递 ServiceConnection 实现。
- 当系统调用 onServiceConnected() 回调方法时,可以使用接口定义的方法开始调用服务。
- 要断开与服务的连接,调用 unbindService()。
如果应用在客户端仍绑定到服务时销毁客户端,则销毁会导致客户端取消绑定。 更好的做法是在客户端与服务交互完成后立即取消绑定客户端。 这样可以关闭空闲服务。如需了解有关绑定和取消绑定的适当时机的详细信息,请参阅附加说明。
0x41 附加说明
以下是一些有关绑定到服务的重要说明:
- 应该始终捕获 DeadObjectException 异常,它是在连接中断时引发的。这是远程方法引发的唯一异常。
- 对象的引用计数是跨进程的。
- 通常应该在客户端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 时刻期间配对绑定和取消绑定。 例如:
- 如果只需要在 Activity 可见时与服务交互,则应在 onStart() 期间绑定,在 onStop() 期间取消绑定。
- 如果希望 Activity 在后台停止运行状态下仍可接收响应,则可在 onCreate() 期间绑定,在 onDestroy() 期间取消绑定。请注意,这意味着 Activity 在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当提高该进程的权重时,系统终止该进程的可能性会增加。
注:通常情况下,切勿在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,您应该使发生在这些转换期间的处理保持在最低水平。此外,如果您的应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一个 Activity 绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务。 (Activity 文档中介绍了这种有关 Activity 如何协调其生命周期的 Activity 转换。)
0x50 管理绑定服务的生命周期
当服务与所有客户端之间的绑定全部取消时,Android 系统便会销毁服务(除非还使用 onStartCommand() 启动了该服务)。因此,如果服务是纯粹的绑定服务,则无需对其生命周期进行管理 — Android 系统会根据它是否绑定到任何客户端代管理。
不过,如果选择实现 onStartCommand() 回调方法,则必须显式停止服务,因为系统现在已将服务视为已启动。在此情况下,服务将一直运行到其通过 stopSelf() 自行停止,或其他组件调用 stopService() 为止,无论其是否绑定到任何客户端。
此外,如果服务已启动并接受绑定,则当系统调用 onUnbind() 方法时,如果想在客户端下一次绑定到服务时接收 onRebind() 调用,则可选择返回 true
。onRebind() 返回空值,但客户端仍在其 onServiceConnected() 回调中接收 IBinder 。下文图 1 说明了这种生命周期的逻辑。