一个bound service是一个client-server接口中的server端。一个bound service允许应用组件(比如activities)bind到它,发送请求,接收响应,甚至是执行进程间通信(IPC)。一个bound service在典型情况下,只有在它服务于另一个应用组件时才存活,而不是在后台无限期的运行。
这份文档向您说明了要如何创建bound service,包括在其他的应用组件中如何bind到service。然而,你也应该参考Services文档来大体地了解关于services的额外信息,比如如何在service中传送通知,设置service在前台运行,等等。
基本概念
一个bound service是一个Service类的实现,它允许其它应用bind到它并与它交互。为了给一个service提供binding功能,你必须实现onBind()回调方法。这个方法返回一个IBinder对象,该对象则定义了客户端可以用来与service进行交互的编程接口。
Binding到一个Started Service
如同在Services文档中讨论的那样,你可以创建一个service,既可以被started,也可以被bound。即,service可以通过调用startService()被started,从而允许service无限期的运行,也可以允许一个客户端通过调用bindService()来bind到service。
如果你确实允许你的service被started和bound,则当service已经被started时,系统不会在所有的客户端都unbind时销毁那个service。相反,你必须通过调用stopSelf()或stopService()来显式地停止那个service。
尽管你通常应该实现onBind()或onStartCommand()之一,但有时需要同时实现两者。比如,一个音乐播放器可能会发现同时允许它的service无限期的运行及提供binding是很有用的。以这种方式,一个activity可以start service来播放一些音乐,而在用户离开了应用后音乐也可以继续播放。然后,在用户回到应用时,activity可以bind到service来恢复对回放的控制。
请确保阅读了关于管理一个Bound Service的生命周期的部分,来了解更多关于给一个started service添加binding功能时service生命周期的信息。
一个客户端可以通过调用bindService()来bind到service。当它bind时,它必须提供一个ServiceConnection的实现,其者监视与service之间的连接。bindService()将立即返回,并且没有返回值,但当Android系统创建客户端和service之间的连接时,它会调用ServiceConnection的onServiceConnected(),来传送IBinder给客户端用于和service进行通信。
然而,系统只有在第一个客户端binds时才会去调用你的service的onBind()来获取IBinder。随后系统传送相同的IBinder给其它bind的客户端,而不再次调用onBind()。
当最后一个客户端从service unbinds时,系统将销毁service(除非service也通过startService()被started了)。
当你实现你的bound service时,最重要的部分是定义你的onBind()回调方法返回的接口。你可以用一些不同的方法来定义你的service的IBinder接口,下面的小节将讨论每种技术。
创建一个Bound Service
当创建一个提供binding功能的service时,你必须提供一个客户端可以用于与service端交互的提供了编程接口的IBinder。有三种方法你可以用来定义这样的接口:
如果你的service是你自己的应用私有的,并且与客户端运行在相同的进程中(很常见的情况),你应该通过扩展Binder类来创建你的接口,并在onBind()方法中返回一个它的实例。客户端接收到Binder,然后就可以使用它来直接访问Binder实现或Service的可用的public方法。
当你的service只是你自己的应用的一个后台工作者时,这是首选的技术。不使用这种方式来创建你的接口的仅有的理由就是,你的service被其他应用或跨越不同的进程被用到了。
如果你需要你的接口跨进程工作,你可以借助于一个Messenger来为你的service创建一个接口。用这种方式,service定义了一个Handler,来响应不同类型的Message对象。这个Handler是一个Messenger的基础,然后后者可以与客户端共享一个IBinder,允许客户端使用Message对象给service发送命令。此外,客户端可以定义一个它自己的Messenger以使service可以发送消息回去。
这是执行进程间通信(IPC)最简单的方式,因为Messenger把所有的请求入队到一个单独的线程,因此你不需要为线程安全而外设计你的service。
使用AIDL
AIDL (Android Interface Definition Language)执行所有将对象分解成操作系统可以理解的元语的工作,并在进程之间处理它们来执行IPC。前面的一种技术,即使用一个Messenger,实际上是以AIDL为它的底层结构的。如上面提到的,Messenger在一个单独的线程中创建一个所有的客户端请求的队列,以使service在某一时刻只接收一个请求。如果,然而,你想要你的service并行的处理多个请求,那么你可以直接使用AIDL。在这种情况下,你的service必须具有多线程的能力,并且要被构建的是线程安全的。
要直接使用AIDL,你必须创建一个定义了编程接口的.aidl文件。Android SDK工具使用这个文件来产生一个实现了接口并处理IPC的抽象类,随后你可以在你的service中扩展这个抽象类。
注意:大多数应用不应该使用AIDL来创建一个bound service,因为它可能需要多线程能力,并可能导致一个更复杂的实现。因此,AIDL对于大多数应用都是不合适的,并且这份文档中不讨论如何使用它来实现你的service。如果你确认你需要直接使用AIDL,请参考AIDL文档。
扩展Binder类
如果你的service只被本地应用用到了,并且不需要跨进程工作,那么你可以实现你自己的Binder类,来为你的客户端提供直接访问service中的public方法的能力。
注意:这种方法只有在客户端和service在相同的应用和进程中时才有效,当然这种情况很常见。例如,对于一个音乐应用,需要bind一个activity到它自己的在后台播放音乐的service,就很有效。
这里是如何设置它:
在你的service中创建一个下述类型之一的Binder实例:
(1). 包含了客户端可以调用的public方法
(2). 返回当前的Service实例,其中具有客户端可以调用的public方法
(3). 或者,返回另一个类的实例,其中寄宿了service,具有客户端可以调用的public方法。
在客户端中,从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中的public方法。比如,客户端可以调用service的getRandomNumber()。
这里是一个activity,它bind到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;
}
};
}
上面的例子演示了客户端如何使用一个ServiceConnection实现和onServiceConnected()回调bind到service。下一节将提供更多关于binding到service的这一过程的信息。
注意:上面的例子没有显式地从service unbind,但所有的客户端都应该在一个适当的时间点 (比如当activity pause时) unbind。
.更多示例代码,请参考ApiDemos中的LocalService.java类和LocalServiceActivities.java类。
使用一个Messenger
与AIDL的比较
当你需要执行IPC时,使用一个Messenger来实现你的接口比通过AIDL来实现它要简单,因为Messenger将所有的调用入队到service,然而,一个纯粹的AIDL接口发送并发的请求给service,这将必须要处理多线程。
对于大多数应用,service不需要执行多线程,因此使用一个Messenger允许service每次只处理一个调用。如果你的service一定要是多线程的,那么你应该使用AIDL来定义你的接口。
如果你需要你的service与远端进程通信,那么你可以使用一个Messenger来为你的service提供接口。这项技术允许你在不需要使用AIDL的情况下执行进程间通信(IPC)。
这里是一个如何使用一个Messenger的总结:
service实现一个Handler,它为来自于客户端的每个调用接收一个回调。
客户端使用IBinder来实例化Messenger (引用了service的Handler),其可被客户端用来给service发送Message发对象。
service在它的Handler中接收每个Message——典型地,在handleMessage()方法中。
以这种方式,客户端不调用service的"方法"。而是,客户端传送"消息" (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并根据what成员决定做什么的地方。
一个客户端所需要做的就是创建一个基于service返回的IBinder的Messenger,并使用send()来发送一个消息。比如,这里是一个简单的activity,它binds到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)来看一下如何提供两路消息的例子。
Binding到一个Service
应用组件(客户端)可以通过调用bindService()来bind到一个service。然后,Android系统调用service的onBind()方法,它会返回一个IBinder用于与service交互。
binding是异步的。bindService()会立即返回,而不给客户端返回IBinder。要接收IBinder,客户端必须创建一个ServiceConnection的实例,并把它传递给bindService()。ServiceConnection包含一个回调方法,系统可以调用它来传送IBinder。
注意:只有activities,services,和content providers可以bind到一个service——你不能在一个broadcast receiver中bind到一个service。
因而,要在客户端中bind到一个series,你必须:
你的实现必须覆写两个回调方法:
系统调用这个方法来传送service的onBind()方法返回的IBinder。
当与service的连接意外断开时,Android系统会调用它。比如当service发生了crash或已 经被杀掉时。当client unbinds时则不会被调到。
调用bindService(),并传入ServiceConnection的实现。
当系统调用了你的onServiceConnected()回调时,你就可以开始调用service了,使用接口所定义的方法。
要从service断开连接,则调用unbindService()。
当你的客户端被销毁时,它将从service unbind,但你应该总是在你完成了与service的交互或者你的activity pause时unbind,以使得service可以在它没有被用到时关闭。(下面有更多关于bind和unbind的合适的时间的讨论。)
例如,下面的代码片段把客户端与上面通过扩展Binder类创建的service连接起来,它所必须做的一切就是把返回的IBinder强制类型转换到LocalService类,请向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()来bind到一个service。比如:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
bindService()的第一个参数是一个Intent,它显式地指定了要bind的service的名字(intent也可以是隐式的)。
第二个参数是ServiceConnection对象。
第三个参数是一个表示binding选项的标记。它通常应该是BIND_AUTO_CREATE,以使service在没有存活的情况下被创建出来。其它的可用的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或0以表示none。
补充说明
这里是关于binding到一个service的一些重要的说明:
你应该总是捕获DeadObjectException一场,当连接破坏时它会被抛出。这是远程方法抛出的仅有的异常。
对象是在进程间引用计数的。
你通常应该在客户端的生命周期中对应的bring-up和tear-down的时刻成对的调用binding和unbinding。比如:
如果你只需要在你的activity可见时与service交互,则你应该在onStart()中bind,并在onStop()中unbind。
如果你想要你的activity即使已经被stopped了,也要在后台接收响应,则你可以在onCreate()中bind,并在onDestroy()中unbind。注意,这意味着你的activity需要在它运行的所有时间都使用service(即使是在后台),因此如果service在另一个进程,则你将增加那个进程的weight,并且它变得更可能被系统杀掉。
注意:通常你不应该在你的activity的onResume()和onPause()中bind和unbind,因为这些回调在每一个生命周期事务中都会发生,你应该把这些事务中发生的处理量降到最低。如果你的应用中的多个activities bind到相同的service,并且在这些activities中的两个之间有一个事务,则在当前的activity unbinds (在pause中)之后,下一个binds (在resume中) 之前可能会发生service的销毁和重建。(这种activities如何协调他们的生命周期的activity事务,在Activities文档中有描述)。
更多演示如何bind到一个service的示例代码,可以参考ApiDemos中的RemoteService.java类。
管理一个Bound Service的生命周期
当所有的客户端都从service unbound时,Android系统会销毁它(除非它也同时通过onStartCommand()被started了)。因此,你不需要管理你的service的生命周期,如果它是纯粹的bound service的话——Android系统会基于它是否被bound到了客户端来为你管理它。
然而,如果你选择实现onStartCommand()回调方法,则你需要显式的来停止service,因为现在service被认为是被started的了。在这种情况下,service会一直运行,直到service通过stopSelf()来终止它自己,或者另一个组件调用stopService(),而不管它是否被bound到任何的客户端。
此外,如果你的service是被started的,并接受binding,那么当系统调用了你的onUnbind()方法,如果你想要在下次客户端binds到service时接收一个对onRebind()的调用(而不是接收一个对onBind()的调用),你可以选择返回true。onRebind()返回void,但是客户端仍然在它的onServiceConnected()回调中接收IBinder。下面的图1描绘了这种生命周期的逻辑。
图1. The lifecycle for a service that is started and also allows binding.
更多关于一个started的service的生命周期的信息,请参考Services文档。
Done。
来源:oschina
链接:https://my.oschina.net/u/919237/blog/211586