[译]Bound Service

Bound Service

Bound Service在C-S中属于服务端(server)。Bound Service允许其他组件(比如Activity)来bind它,发送请求,接收响应,甚至通过IPC。Bound Service通常仅在为其他组件服务时才存活,且不会在后台无限运行。

此文档告诉你如何创建一个Bound Service,包括如何通过其他组件来bind此Service。当然,你也可以参看Services文档来获得更多信息。

The Basics

Bound Service是一个Service的实现,允许其他Application去bind并与之交互。你必须实现onBind方法来提供Service的bind。此onBind方法会返回一个IBinder对象(定义client与你的Service交互的接口)。

Client可以调用bindService来bind一个Service。如果这么做了,client必须实现提供一个ServiceConnection的实现,用来监听与Service的连接。bindService会直接无结果返回,但是当Android系统创建Service与Client的连接时,会调用ServiceConnection中的onServiceConnected方法,分发IBinder使Client可以用来与Service交互。

多个Client可以同时与一个Service保持连接。然而,系统仅会在第一个Client bind时调用你的Service的onBind方法去获取IBinder,系统会分发同样的IBinder给其他bind的Client,而不再调用onBind。

当最后一个Client从Service unbind后,系统会销毁此Service(除非此Service是通过startService启动的)。

当你实现一个Bound Service,重要的部分是你要实现一个IBinder的接口来给onBind返回。有几种方式可以实现这个IBinder接口,下面的章节将会讨论到。

Creating a Bound Service

创建一个Bound Service时,必须提供一个IBinder来供client用来与Service交互。以下有三种定义接口的方式:

  • Extending the Binder class
    如果你的Service是你的Application私有的,且与Client运行在相同的process(通常是这样的),你应该通过继承Binder来实现你的接口,Client接收到此Binder后可以使用它直接访问这个Binder实现甚至是Service的public方法。
    如果你的Service仅仅是为你自己的Application提供后台工作,此方式是建议使用的。仅当你的Service要被其他Application使用或是跨进程的时候才不使用这种方式。

  • Using a Messenger
    如果你需要你的接口工作在不同的process,你可以使用Messenger来为Service创建接口。通过这种方式,Service定义一个Handler来响应不同类型的Message对象。此Handler是Messenger的基础,可以与Client共享一个IBinder,允许Client使用Message对象发送命令给Service。另外,Client可以定义自己的Messenger,这样Service就可以回馈Message。
    这是一个简单的执行IPC的方式,因为Messenger会在一个简单的线程里排列所有请求,所以你没有必要设计你的Service的线程安全。

  • Using AIDL
    AIDL(Android接口定义语言)所要做的事是将对象分解成基本类型使系统可以识别并marshall(整编)通过IPC跨进程传送。在前一种方式中(使用Messenger),事实上底层结构也是基于AIDL的。如上所述,Messenger使用一个简单线程来排列client的请求,所以Service一次只能接受一个请求。然而,如果你想要Service能同时处理多个请求,你可以直接使用AIDL。这种情况下,你的Service必须建立线程安全来处理多线程。
    要直接使用AIDL,你必须创建一个.aidl文件来定义一个编程接口。Android SDK工具使用此文件来生成一个抽象类来实现此接口并处理IPC,你可以在你Service中继承此抽象类。

注意:大多数应用不需要使用AIDL来创建Bound Service,因为这样会要求有多线程处理能力并且会导致更加复杂的实现。因此,AIDL不适用与大多数Application,此文档不讨论如何在你的Service中使用它,如果你确实需要使用,可以参见AIDL文档。

Extending the Binder class

如果你的Service仅仅在你自己的Application中使用且不用跨进程工作,你可以实现自己的Binder类提供给你的Client直接访问Service的public方法。

注意:仅在client和Service在同一Application和process时使用这种方式(这是最常见的)。例如,音乐播放器的Activity需要与自己的播放音乐的后台Service bind的时候,这个就可以使用。

如何创建:

  1. 在你的Service中,创建一个Binder的实例:
    a) 包含Client可以调用的public方法
    b) 返回当前Service的实例,拥有Client可以调用的public方法
    c) 或者返回一个Service中其他类的实例(拥有Client可以调用的public方法)
  2. 在onBind回调方法中返回此Binder实例。
  3. 在Client端,在onServiceConnected回调中接收此Binder,使用同的public方法调用使用Bound Service。

注意:Service和Client必须是同一个Application是因为这样的话Client可以转换返回的结果和正确的调用API。Service和Client也必须是同一个process,因为这个方式不会执行跨进程的重组(marshall)。

如下实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class LocalService extends Service {
// 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方法来让client获取当前LocalService的实例,允许client调用Service的public方法。比如client可以调用Service的getRandomNumber方法。
如下有一个Activity bind此Service并调用getRandomNumber方法,当按钮点击时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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 = new Intent(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 = new ServiceConnection() {
@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;
}
};
}

上述例子展示了Client如何bind到Service,使用ServiceConnection的实现及其onServiceConnected回调。下一章节会详细说明这一bind过程。

注意:例子代码没有明确从Service中unbind,但是所有client应该在合适的时间(例如Activity的onPause等)unbind。

Using a Messenger

如果你需要你的Service与远程进程(remote process)交互,你可以使用Messenger为你的Service提供一个接口。此方式允许你跨进程(IPC)而不需要使用AIDL。

使用Messenger时有以下要点:

  • Service实现一个Handler来接收client的每一个调用
  • 使用Handler创建一个Messenger对象(与此Handler关联)
  • Messenger创建一个IBinder供Service的onBind方法返回
  • Client使用此IBinder实例化Messenger(Client用来发送Message给Service)
  • Service在它的Handler的handleMessage方法中接收每个Message

这种方式,Service没有供Client调用的方法,而是通过Client发送message给Service的Handler处理。

参见如下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
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);
}
}
}
/**
* 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,在此Service接收Message并基于what成员来决定怎么做。

Client所有要做是就是基于Service返回的IBinder创建一个Messenger并使用send方法发送Message。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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() {
public void 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;
}
}
}

实例中没有展示Service如何给Client回应。如果你想要Service予以回应,你需要在你的Client也创建一个Messenger。放Client接收到onServiceConnected回调,调用send方法发送一个replyTo参数为Client的Messenger的Message给Service。

Binding to a Service

Application组件(Client)调用bindService来bind一个Service,之后Android系统会调用Service的onBind方法,返回一个与Service交互的IBinder。

这个bind是异步的。bindService会直接返回而不会给Client返回IBinder。要接收这个IBinder,Client必须创建一个ServiceConnection实例并传送给bindService方法。ServiceConnection有一个回调方法,系统会调用它来分发此IBinder。

注意:只有Activity,Content Provider和Service才可以bind一个Service,Broadcast Receiver不能bind Service。

为了在你的Client绑定一个Service,你必须:

  1. 实现ServiceConnection
    你的实现必须override两个回调:
    onServiceConnected
    系统调用它来分发Service的onBind方法返回的IBinder。
    onServiceDisconnected
    系统在Service的连接意外丢失(例如Service崩溃或是被kill)时调用此方法。当Client与Service unbind时不会调用。
  2. 调用bindService并传入此ServiceConnection的实现
  3. 当系统调用onServiceConnected回调时,你可以使用接口中的方法调用Service
  4. 调用unbindService来与Service断开连接

如果你的Client销毁了,它会与Service unbind,但是你必须总是自己unbind,当你与Service交互完成或是你的Activity处于pause状态的时候,这样Service就可以在不使用的时候关闭(合适的时间bind,unbind在下面会讨论)。

在上述Extending the Binder class的例子中,都必须将返回的IBinder转型为LocalBinder,从而得到LocalService实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
};

Client可以将此ServiceConnection传给bindService来bind一个Service。如下:

1
2
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • bindService的第一个参数是一个Intent,显式指明了要bind的Service的class name(此Intent也可以是隐式的)
  • 第二个参数就是ServiceConnection对象
  • 第三个参数是一个flag,标识bind的选项。通常使用BIND_AUTO_CERATE来指明如果Service还未存活的话则创建该Service。其他可用的选项包括BIND_DEBUG_UNBIND,BIND_NOT_FOREGROUND或是0。

Additional notes

Bind一个Service时有一些重要的注意事项:

  • 你应该总是捕捉DeadObjectException异常,此异常会在连接被破坏时抛出。这是远程方法(remote method)抛出的唯一异常。
  • Objects are reference counted across processes. (理解:跨进程对象引用计数)。
  • 你通常应该成对使用bind和unbind对应你的client端生命周期的“bring-up”和“tear-down”时刻。例如:
  • 如果你仅仅是在你的Activity可见时与Service交互,你应该在onStart中bind而在onStop中unbind。
  • 如果你想你的Activity就算是stop在后台的情况仍能接收到响应,你可以在onCreate中bind而在onDestroy中unbind。要当心的是这意味着你的Activity的整个生命周期都要用到Service(即使在后台),所以如果此Service运行在另外的process,当你增加了此process的负担时,此Service会变得更可能被系统kill掉。

注意:你通常不应该在你的Activity的onResume和onPause中进行bind和unbind,因为这两个回调在每次生命周期转换中都会发生,你应该确保这种处理(bind和unbind)发生在转换最少的时候。同样,如果你的Application中多个Activity绑定了同一个Service并且在其中两个Activity之间有一个状态转换,在当前Activity unbind(onPause中)之后,下一个Activity bind(onResume中)之前,此Service可能被销毁而重新创建。

Managing the Lifecycle of a Bound Service

当Service从所有Client unbind之后,Android系统会销毁它(除非它也通过onStartCommand启动)。所以一个纯的Bound Service,你不需要去管理其生命周期,系统会根据它bind的client来管理它的生命周期。

然而,如果你选择实现onStartCommand回调,你就必须显式的stop该Service,因为该Service现在被认为是Started Service。这种情况下,Service会一直运行直到Service调用stopSelf或是其他组件调用stopService来stop它,不管它是否与其他Client bind。

另外,如果你的Service是Started且接受bind,当系统调用你的onUnbind回调时,你可以返回一个true,这样在下次Client来bind它时你将接收onRebind而非onBind。onRebind返回一个void,但是Client依旧会在onServiceConnected中接收到一个IBinder。下图展示了此类型Service的生命周期:

service_binding_tree_lifecycle

图解:Started 且允许Bind的Service的生命周期。