原文地址 http://developer.android.com/guide/components/bound-services.html
(按照个人理解进行翻译,方便以后查找资料,水平有限,如有错误,还请谅解)
翻译:
Bound Services
一个bound service是客户端——服务器端接口中的服务器端。一个bound service允许组件(例如activity)绑定到service上,发送请求,接收回复,甚至是进程间通信(IPC)。一个典型地bound service,只存在于它为其它组件提供服务的期间,并不会在后台一直运行。
这篇文档向我们展示了如何创建一个bound service,包含其他组件如何绑定到service上。然而,我们也应该参考Services文档,获取一般情况下的service的信息,例如如何从一个service发送通知信息,使service在前台运行,等等。
The Basics
一个bound service是一个Service类的实现,它允许其他应用程序组件绑定到它身上并和它交互。为了使一个service提供绑定功能,我们必须实现onBind()回调方法。这个方法返回一个IBinder对象,它定义了客户端用来与服务器端交互的程序接口。
一个客户端可以通过调用bindService()绑定到service上。当客户端这么做了,它必须提供一个ServiceConnection的实现,用来监视与service的连接状况。bindService()将会立即返回,无返回值,但是当Android系统创建客户端与服务器端的连接时,系统会调用ServiceConnection中onServiceConnected()方法,并传递一个客户端用来与服务器端通信的IBinder。
多个客户端可以同时连接到相同的service上。然而,只有第一个客户端绑定时,系统才会调用onBind()方法返回一个IBinder。接下来,系统会将相同的IBinder发送给其他绑定的客户端,不会再次调用onBind()。
当最后一个客户端从service解绑定时,系统会销毁这个service(除非这个service也被startService()启动过)。
当我们实现bound service时,最重要的部分是定义onBind()回调方法返回的接口。有几种不同方式可以定义我们的service的IBinder接口,下面的部分将会讨论每一种技术。
Binding to a started Service
正如在Services文档中讨论的一样,我们可以同时以started和bound两种方式创建一个service。那就是说,这个service可以被startService()调用启动,使它一直运行,并且也允许客户端通过调用bindService()绑定到它身上。
如果我们允许service成为started和bound,那么当所有客户端解绑定时,如果这个service曾经启动过(started),系统将不会销毁它。反而,我们必须调用stopSelf()或stopService()显示地停止这个service。
尽管我们通常只实现onBind()或onStartCommand(),但有时候必须实现两者。例如,我们可能会发现,允许一个音乐播放器的service一直运行并提供绑定,非常有用。这种情况下,一个activity可以启动service播放音乐,甚至当用户离开应用程序时,音乐也继续播放。接下来,当用户回到应用程序时,这个activity重新绑定到service上,并获取播放的控制权。
一定要读Managing the Lifecycle of Bound Service部分,获取当添加绑定到一个started service上,关于这个service生命周期的更多信息。
Creating a Bound Service
当创建一个提供绑定的service时,我们必须提供一个IBinder,它规定了客户端与服务器端交互的程序接口。这里有三种定义接口的方式:
Extending the Binder class
如果我们的service为应用程序私有,并与客户端运行在相同的进程中(大多数情况),我们应该继承Binder类来创建接口并在onBind()方法中返回一个实例。客户端接收这个Binder实例,并使用它直接访问Binder实现的可用的public方法,或者service的public方法。
当我们的service仅仅是我们的应用程序的后台工作者,这是首选技术。唯一我们不能以这种方式创建接口的原因是,我们的service被其他应用程序使用,或在别的进程中运行。
Using a Messenger
如果我们需要接口工作于不同的进程中,我们可以用一个Messenger(信使)创建一个service的接口。在这种方式下,这个service定义一个Handler处理不同类型的Message对象。这个Handler是Messenger的主要部分,它能与客户端共享IBinder,允许客户端使用Message对象发送一个命令。此外,客户端可以定义它自己的 Messenger,使service也能向它发送消息。
这是执行IPC最简单的方式,因为Messenger队列中的所有请求都在单线程中,因此我们不必考虑service里的线程安全问题。
Using AIDL
AIDL(Android接口定义语言)所做的工作就是,将对象分解成操作系统能理解的原始类型,并通过IPC传递它们。之前使用Messenger的技术,实际上基于AIDL作为它的底层结构。正如上面提到的一样,Messenger在单线程中创建一个队列接收所有客户端的请求,因此service一次接收一个请求。如果我们想要我们的service同时处理多个请求,那么我们可以直接使用AIDL。在这种情况下,我们的service必须是多线程并且线程安全的。
为了直接使用AIDL,我们必须创建一个定义了程序接口的 .aidl 文件。Android SDK工具集使用这个文件自动生成,一个实现了接口并处理IPC的抽象类,可以在我们的service中继承它。
注意:大多数应用程序不应该使用AIDL创建一个bound service,因为它需要多线程,会导致更复杂的实现。正因如此,AIDL不适合大多数应用程序,这篇文档也不会讨论如何在我们的service中使用它。如果我们确定需要使用AIDL,请查看AIDL文档。
Extending the Binder class
如果我们的service只在本地应用程序使用,并且不需要跨进程工作,那么可以实现我们自己的Binder类,它提供客户端直接访问service里面的公共方法。
注意:这项工作只适合客户端和service在相同的应用程序并在相同的进程中,大多数情况都是如此。例如,对于一个音乐类应用,它只需绑定一个activity到它自己的service上,用来在后台播放音乐,这会工作的很好。
下面是编写步骤:
1. 在我们的service里,创建一个Binder实例,要么:
* 包含客户端可以调用的公共方法
* 返回当前Service实例,它包含客户端可以调用的公共方法
* 或者,返回一个托管service的其它类的实例,它包含客户端可以调用的公共方法
2. 在onBind()回调方法中返回这个Binder实例。
3. 在客户端里,接收从onServiceConnected()回调方法返回的Binder,使用它调用bound service提供的方法。
注意:service和客户端必须在同一个应用程序的原因是,客户端可以确定返回的对象类型(进行强制类型转换),并恰当的调用它的APIs。service和客户端也必须在同一个进程中,因为这项技术不会执行任何跨进程传输。
例如,下面的service,提供客户端通过Binder实现访问service里面的方法:
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()方法,客户端通过它获取LocalService的当前实例。它允许客户端调用service里的公共方法。例如,客户端可以调用service里的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 = 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;
}
};
}
上面的示例展示了,客户端如何使用ServiceConnection的实现和onServiceConnected()回调方法绑定到service上。下面的部分提供,关于绑定到service上的过程的更多信息。
注意:上面的示例没有显示地从service解绑定,但是所有的客户端都应该在恰当的时间解绑定(例如当activity暂停时)。
想要更多的示例代码,请查看ApiDemos里面的LocalService.java类和LocalServiceActivities.java类。
Using a Messenger
如果我们需要我们的service与远程进程通信,那么我们可以使用一个Messenger提供service的接口。这项技术允许我们不通过使用AIDL来执行IPC。
下面概括如何使用一个Messenger:
* service实现一个Handler,处理客户端的每个请求
* 这个Handler被用来创建一个Messenger对象(一个Handler的引用)
* 这个Messenger创建一个IBinder,service在onBind()回调时将其返回给客户端
* 客户端使用这个IBinder实例化Messenger(service中Handler的引用),然后使用这个实例发送Message给service
* 这个service在它的Handler中接收每一个Message——其实,在handleMessage()方法中。
以这种方式,service里面没有方法给客户端调用。取而代之,客户端发送“messages”(Message对象),service在它的Handler中接收。
下面是使用Messenger接口的service的简单示例:
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的地方,并且根据message里what的值,决定做什么。
客户端所需做的是,基于service返回的IBinder创建一个Messenger,使用send()发送message。例如,下面是简单示例,一个activity绑定到service上,发送MSG_SAY_HELLO消息给service:
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如何回应客户端。如果我们想要service回复,那么我们也需要在客户端中创建一个Messenger。接下来,当客户端从onServiceConnected()返回时,它发送一个Message给service,在send()方法的replyTo参数中包含客户端的Messenger。
我们可以在MessengerService.java(service)和MessengerServiceActivities.java(client)示例中看到,如何提供两种message的例子。
Compared to AIDL
当我们需要执行IPC时,使用一个Messenger实现接口要比使用AIDL简单,因为Messenger排队所有的service请求,而纯粹的AIDL接口同时发送多个请求给service,导致必须用多线程处理。
对于大多数应用程序而言,service不需要执行多线程操作,因此使用一个Messenger一次处理一个service请求。如果我们的service使用多线程很重要,那么应该使用AIDL定义我们的接口。
Binding to a Service
应用程序组件(客户端)可以通过bindService()调用绑定到service上。之后,Android系统会调用service的onBind()方法,返回一个IBinder,用来与service交互。
绑定是异步的。bindService()会立即返回,并且不会返回IBinder给客户端。为了接受IBinder,客户端必须创建一个ServiceConnection的实例,并将它传递给bindService()。这个ServiceConnection包含一个系统可以调用回调方法,用来接受IBinder。
注意:只有activity,service,和content provider可以绑定到一个service上——我们不能将一个broadcast receiver绑定到service上。
因此,为了将我们的客户端绑定到service上,我们必须:
1. 实现ServiceConnection
我们的实现必须重写两个回调方法:
onServiceConnected()
系统调用这个方法接收从service的onBind()方法返回的IBinder。
onServiceDisconnected()
当与service的连接异常丢失时,android系统会调用这个方法。例如当service崩溃或被销毁时。当客户端解绑定时,这个方法不会被调用。
2. 调用bindService(),传递ServiceConnection的实例。
3. 当系统调用我们的onServiceConnected()回调方法时,我们可以使用接口中定义的方法,发送请求给service。
4. 调用unbindService()将断开与service的连接。
当我们的客户端被销毁时,它将与service解绑定。但是当完成与service的交互时,或我们的activity暂停,以至于service在它不被使用时可以关闭,我们总是应该主动的解绑定。(下面会更多的讨论,绑定与解绑定的恰当时间。)
例如,下面的代码段连接客户端,与上面使用继承Binder类创建的service。因此,客户端所需做的就是,将返回的IBinder强制类型转换为LocalBinder类(原文为LocalService,这里我认为应该是LocalBinder),并获得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(),绑定到一个service上。例如:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
* bindService()的第一个参数是一个Intent,明确地表明要绑定的service的名字(想象intent也能是隐式的)。
* 第二个参数是一个ServiceConnection对象。
* 第三个参数是一个标志,它指出绑定选项。为了使service还没有活动时也能被创建,通常应该为BIND_AUTO_CREATE。其他可能的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或无意义的0。
Additional notes
下面是绑定到一个service上的一些要点:
* 我们总是应该捕获DeadObjectException异常,当连接无效时抛出。这是远程方法抛出的唯一异常。
* 跨进程的对象引用计数。
* 通常我们应该将绑定和解绑定放在客户端生命周期的开始与结束。例如:
* 如果我们只需当activity可见时,与service交互,应该在onStart()绑定,并在onStop()解绑定。
* 如果想要我们的activity在后台停止时也接收回复,那么我们可以在onCreate()绑定,并在onDestroy()解绑定。这意味着我们的activity在它运行的整个周期(甚至是在后台),都需要使用service。因此,如果service在另一个进程,我们要增加这个进程的优先级,否则它更有可能会被系统销毁。
注意:通常,我们不应该在activity的onResume()和onPause()中绑定和解绑定,因为这些回调发生生命周期的每一次转变中,我们应该使转变过程中的处理降到最低。同样地,如果应用程序中多个activity绑定到同一个service上,在这些activity的两者之间service也会发生转变,随着当前activity解绑定(pause期间)发生在下一个activity绑定(resume期间)之前,这个service有可能被销毁然后重新创建。(这里关于activity如何进行它们的生命周期的转变,在Activities文档中描述。)
更多的示例代码展示了如何绑定到一个service上,请查看ApiDemos中的RemoteService.java类。
Managing the Lifecycle of a Bound Service
当一个service被所有客户端解绑定时,Android系统会销毁它(除非它也被用onStartCommand()启动过)。同样地,如果service仅仅是一个bound service,我们不需要管理它的生命周期——根据它是否被其它客户端绑定,Android系统为我们进行管理。
无论如何,如果我们选择实现onStartCommand()回调方法,那么我们必须显式的停止这个service,因为此时这个service被认为是started。在这种情况下,无论它是否被其它客户端绑定,service将一直运行直到它自己调用stopSelf()或其它组件调用stopService()。
此外,如果我们的service是started并接受绑定,那么当系统调用onUnbind()方法时,如果我们想要在下一次客户端绑定到service时执行onRebind()调用(而不是onBind()调用),我们可以选择性的返回true。onRebind()返回void,但是客户端仍然可以在onServiceConnected()方法中接收IBinder。下面,图1展示了这种service的生命周期。
图1. 一个既是started有接收绑定的service的生命周期。
想要更多关于一个started service生命周期的信息,请查看Services文档。