原文地址:Bound Services
一、前言
bound service是client-server接口中的server端。bound service允许其他组件绑定它,发送请求、获取结果,允许进程间通信。bound service只有在其他组件绑定它时才存活,不会像started service那样无限的存活在后台。
本文档描述了怎样创建bound service,与怎样通过其他应用组件绑定它。
二、基本内容
bound service是Service的一种实现,你需要实现Service类的onBind()回调方法。这个回调方法需要返回一个IBinder对象,此对象定义了调用方与此bound service交互的接口。
调用方可以通过bindService()方法绑定一个bound service。这个方法的一个入参为ServiceConnection,你需要实现ServiceConnection接口以监控与service的连接情况。bindService()方法在调用后会立刻返回,然后Android系统试图在调用方与service间建立连接,当连接建立时,Android系统会调用ServiceConnection接口中的onServiceConnected()方法,以发送service的IBinder对象。
bound service可以同时被多个调用方绑定,但是系统只会调用bound service的onBind()方法一次(在第一个调用方绑定它时)。然后其他调用方绑定时,系统会将同一个IBinder发送给它们。
当所有的调用方都与bound service解绑时,系统会销毁此service(除非这个service还被startService()启动了)。
在实现bound service时,最重要的就是定义onBind()方法返回的通信接口IBinder。下面将介绍几种定义IBinder的方法。
2.1 关于绑定已经start了的service的说明
你可以创建一个既可以被start又可以被bind的service,然后通过startService()启动它(此service将在后台无限期运行),或者通过bindService()绑定它。
如果你的service同时支持两种启动方式,而且已经被start了,那么当所有调用方都解绑时service不会被销毁。你必须显式的调用stopSelf()或stopService()来停止它。
三、创建bound service
在实现onBind()方法时,你需要返回一个IBinder对象,你可以通过三种方式定义它:
- 继承Binder类
如果你要创建的service是自己应用内的service,而且调用方在同一个进程运行,那么你应该通过继承Binder类来创建自己的binder,并通过onBind()返回给调用方。调用方在获得Binder实例后,可以通过它访问Binder或service实现的public方法。
如果你要创建的service仅仅用于本应用,那么使用本种方式是较好的选择。而如果你要创建的service是被其他应用使用的,或者通过其他的进程使用的,那么请使用其他方式。 - 使用Messenger
如果你需要在其他进程使用binder,那么你可以用Messenger为service创建它。在这种方式下,service会定义一个handler来响应不同的Message对象。Handler是Messenger的基础,可以与调用方共同使用一个IBinder对象,允许调用方使用Message对象向service发送命令。另外,调用方也可以实现自己的Messenger,这样的话,service就可以向它发送信息了。
本种方式是实现进程间通信的最简单的方法。Messenger会将所有请求以队列的形式存在另外的线程中,所以你不需要将你的service设计为线程安全的。 - 使用AIDL
AIDL(Android interface definition language)可以将对象分解为操作系统可以理解的原始数据,这样的话操作系统就可以将这些对象安排到不同的进程中,从而实现进程间通信。之前提到的Messenger,实际在内部结构上是基于AIDL的。Messenger会创建一个队列存放调用方的请求,service每次可以收到一个请求。但是,如果你希望你的service可以同时处理多个请求,Messenger就不够用了,你需要直接使用AIDL(在用AIDL方式时,你需要保证自己的service支持多线程且线程安全)。
使用AIDL方法,你需要声明一个.aidl文件,这个文件中定义了你需要的接口。Android SDK工具会通过这个文件生成一个实现了你需要的接口的抽象类来处理进程间通信,你可以在自己的service中继承这个抽象类。
注意:大多数应用不需要使用AIDL创建bound service。而且使用AIDL需要更复杂的实现。所以,本文档不对AIDL细节进行描述,如果想要更多信息,请看单独的一篇文档:AIDL
3.1 继承Binder类
通过此方式实现bound service需要以下步骤:
- 在service中,用以下方法之一创建Binder:
- 实现调用方需要的public方法
- 返回当前Service对象,这个对象中有调用方需要的public方法
- 返回某个类的实例,此类被service持有,且包含调用方需要的public方法 - 在onBind()方法中返回此Binder
- 调用方通过onServiceConnected()方法获取Binder对象,然后通过其中的方法与service通信
注意:service与调用方必须处于同一个应用,调用方才可以将onBind()返回的对象强转为需要的对象从而调用其API。service和调用也必须处于同一个进程,即此方式不支持跨进程通信。
下面是一个通过Binder实现bound 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的public方法了。例如,调用方可以调用service的getRandomNumber()方法。
下面是一个activity的示例代码,其绑定了LocalService并在button被点击时调用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;
}
};
}
上面的示例显示了调用方怎样绑定service并通过onServiceConnected()获取Binder。下个部分将会更详细的介绍绑定service的过程。
注意:在上面的示例中没有显式的与service解绑,但是所有的调用方都应该在适当时候与service解绑,如activity进入pause状态时
3.2 使用Messenger
下面是使用Messenger的步骤:
- service实现一个Handler以接收调用方的信息
- 使用此Handler创建Messenger对象
- 通过Messenger创建IBinder并通过onBind()返回给调用方
- 调用方通过IBinder创建Messenger,并通过此Messenger向service发送Message对象
- service在Handler中获取Message
在上面的过程中,没有说明service主动向调用方发送信息的方法。只是说明了调用方发送“message”而service获取它的过程。
下面是使用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()方法会收到Message并根据Message中的what决定做什么。
调用方需要做的是根据service返回的IBinder创建Messenger,并调用其send()方法发送信息。下面的例子说明了一个简单的activity,其绑定了上面的service并发送了一个MSG_SAY_HELLO信息:
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如何响应调用方的message。你可以在调用方创建一个Messenger,当onServiceConnected()方法被调用时,通过send()方法的replyTo参数将其发送给service。从而让service也可以向调用方发送信息。
四、绑定service
应用组件(调用方)可以通过bindService()方法绑定service。然后Android系统会调用service的onBind()方法,并将service的IBinder返回给调用方以供通信。
绑定过程是异步的。bindService()方法会立刻返回,且并不会返回IBinder对象。调用方需要在调用bindService()时传入一个ServiceConnection对象,系统会通过此对象的回调方法分发IBinder。
注意:只有activity、service、content provider可以绑定service,broadcast receiver不能绑定service
要绑定一个service,你需要:
- 实现ServiceConnection
你需要重写两个方法:
- onServiceConnected():系统通过此方法分发service通过onBind()方法返回的IBinder
- onServiceDisconnected():当与service的连接意外断开时系统会调用此方法,如service崩溃或者被kill时。调用方与service解绑,不会调用此方法 - 调用bindService()并传入ServiceConnection
- 当系统调用onServiceConnected()时,获取IBinder并与service通信
- 调用unbindService()与service解绑
如果调用方被销毁,会自动与service解绑。但是你还是应该在通信结束或者activity进入pause状态时主动与service解绑,以控制service的正常销毁过程
下面是调用方绑定某个service并将返回的IBinder强转为LocalService并与service通信的示例:
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():
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
bindService()的三个参数说明如下:
- 第一个参数为Intent,其说明要绑定的service的名称(本例中为显式intent,你也可以使用隐式intent)
- 第二个参数为ServiceConnection
- 第三个参数为一个flag,其指明绑定的方式。通常此值为BIND_AUTO_CREATE,表示如果目标service实例不存在则新建一个service实例。其他可用的值有BIND_DEBUG_UNBIND、BIND_NOT_FOREGROUND、0
4.1 其他说明
下面是绑定service的一些重要说明:
- 你应该尝试捕获DeadObjectException异常,当连接中断时会抛出此异常。此异常是remote方法会抛出的唯一异常
- 对象可以被跨进程引用
- 你应该根据调用方的生命周期成对的绑定与解绑service
注意:不要在activity的onResume()和onPause()方法绑定与解绑service,因为这两个方法的过渡很频繁,而且会导致在多个activity绑定此service时出现问题
五、管理bound service的生命周期
当所有的调用方都解绑service时,系统会销毁此service(除非此service还被onStartCommand()启动了)。所以,如果你的service是一个单纯的bound service,那么你不需要管理它的生命周期。
然而,如果你实现了onStartCommand()方法,那么你需要显式的停止这个service,因为此service现在被认为是started service。这个service将会一直运行,直到调用stopSelf()或者stopService()方法,不管是不是所有的调用方都解绑了这个service。
如果你的service既被start又被bind,那么当系统调用service的onUnbind()方法时,你可以选择返回true,这样的话再有调用方绑定这个service时你会收到onRebind()回调。service生命周期详见下图。