[安卓API guides翻译]2.3.1 Bound Services
bound service是客户端-服务器接口的服务端。这种service允许组件(如activity)与之绑定,发送请求,接收回应,甚至进行进程间通信(IPC)。bound service仅在为其他组件提供服务时运行,而不是在后台一直运行。
本文将向你介绍如何创建一个bound service,包括其他组件如何绑定到该service。不过,你也应该参看上节Services一文,了解service的其他知识,例如从service发送通知的方法,还有设置service在前台运行,等等。
The Basics
bound service是Service类的实现类,这类service允许其他程序与之绑定和交互。要提供这种绑定功能,必须实现onBind()回调方法。该方法返回一个IBinder对象,这个对象定义了客户端与service交互的程序接口。
客户端可以调用bindService()来与service绑定。如果这么做,bindService()必须提供一个ServiceConnection对象来监听与service的连接。bindService()无返回值,并且会立即返回,当android系统创建客户端与service之间的连接时,系统将调用ServiceConnection对象的onServiceConnected()方法来传递IBinder对象,客户端就是使用该对象与service通信。
多个客户端可同时与一个service建立连接。然而,只有第一个客户端绑定service时,系统才调用service的onBind()方法来索取IBinder对象。然后,系统会把相同的IBinder对象传递给其他绑定的客户端,并不会再次调用onBind()方法。
当最后一个客户端与service解除绑定,系统就销毁service(除非该service是被startService()方法启动的)。
实现bound service时,最重要的部分是定义onBind()回调方法返回的接口。有几种不同的方式来定义service的IBinder接口,下面将讨论每种方式。
Creating a Bound Service
要创建一个service并提供绑定功能,必须提供一个IBinder对象,这个对象定义了客户端与service交互的程序接口。有三种方式来定义这个接口。
继承Binder类
如果service是你的程序私有(private)并且与客户端在同一进程运行(这是最常见的情况),你应该继承Binder类来创建接口,并从onBind()方法返回该接口的实例。客户端接收该Binder对象,并通过它来直接使用Binder对象甚至Service的公共方法。
当你的service仅仅在后台为你自己的程序工作,这是最优先的方式。只有当你的service被别的程序使用或者被跨进程使用,你才不使用这种方式创建接口。
使用Messager
如果希望接口被跨进程使用,你可以为service创建一个带Messager的接口。通过这种方式,service定义一个Handler来处理不同的Message对象。这个Handler是Messager的基础,可以给客户端一个IBinder对象,允许客户端使用Message对象来向service发送请求。另外,客户端可以自己定义一个Messager,这样service就可以把消息传回来。
这是执行进程间通信(IPC)最简单的方式,由于Messager队列在一个单独的线程里,所以不用把service设计成线程安全。
使用AIDL
AIDL执行所有的工作,把对象分解成操作系统能理解的原语,并把它们编组以跨进程通信。前面提到的使用Messenger的技术,实际上是基于AIDL做为它的底层架构的。就上面所提到的,Messenger在一个线程中,为他的客户端所有的请求创建一个队列,所以服务一次只能接收一个请求。然而,如果你想要你的服务同时处理多个服务,那么你可以直接使用AIDL.这样的话,你的服务必须能同时处理多线程并且被设计成多线程安全的。
要直接使用AIDL,必须创建一个.aidl文件,该文件定义了程序接口。android SDK tools使用该文件生成一个抽象类,这个抽象类继承了该接口并控制IPC,你可以在service中继承这个抽象类。
注意:大多数程序不应该使用AIDL来创建bound service,因为它要求实现多线程,这将导致程序变得复杂。同样,AIDL并不适合大多数程序,本文也不讨论如何为service使用AIDL。如果你确定你需要直接使用AIDL,参看AIDL指导。
(额外)Binding to a Started Service
像Service一文中讨论的那样,可以创建一个既是started又是bound的service。就是说,service是被startService()方法启动的,它可以一直运行,并且允许客户端调用bindService()方法来绑定它。
如果你允许你的service既是started又是bound,那么当service被启动,系统不会在所有客户端解除绑定之后销毁service。因此,必须通过调用stopSelf()或stopService()显示地终止service。
尽管通常情况下,你应该仅实现onBind()或onStartCommand()其中之一,但有时同时实现这两个方法是必要的。例如,音乐播放器需要service在后台一直运行,也需要提供绑定。这样,activity才能启动service来播放音乐,而当用户离开该应用音乐也能继续播放。然后,当用户回到该应用,activity可以绑定到service来取得控制权。
请阅读Managing the Lifecycle of a Bound Service,来获取更多关于started service增加绑定时的service生命周期的信息。
Extending the Binder class
如果service仅被本地程序使用且不需要做跨进程的工作,就可以通过实现Binder类来使客户端能够直接使用service的公共方法。
注意:这仅仅在客户端和service在同一程序和进程中才有效,也最常见。例如,音乐应用程序需要绑定一个activity到它的service来后台播放音乐,这种方式就很合适。
下面讲如何设置:
1.在service中创建一个Binder实例,且满足下面之一:
包含客户端能调用的公共方法;
返回当前Service类的实例,该实例包含客户端能调用的公共方法;
或者,返回service内部类的实例,该实例包含客户端能调用的公共方法。
2.在onBind()回调方法中返回该Binder实例。
3.在客户端,从onServiceConnected()回调中接收Binder实例,并使用bound service提供的方法。
注意:service和客户端必须在同一程序中,只有这样客户端才能转型返回的对象并正确调用其接口。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进入pauses状态时)与service解除绑定。
Using a Messager
如果需要service与外部进程通信,可以使用Messager类为你的service提供接口。这种方式允许你在不使用AIDL的情况下完成进程间通信。
Compared to AIDL
需要执行IPC时,使用Messenger比使用AIDL更简单,因为Messenger把所有对service的调用排成队列,与此相反,纯AIDL接口向service同时发送多个请求,所以必须控制多线程。
对于大多数程序,service不需要执行多线程工作,所以使用Messenger,让service一次处理一个请求。如果service必须实现多线程,那就应该使用AIDL来定义你的接口了。
下面概述如何使用Messager:
* 在service里创建一个Handler实例,该Handler实例从各个客户端接收回调。
* 该Handler被用来创建一个Massenger对象(是Handler的引用)。
* Massager对象创建一个IBinder对象,该IBinder对象被service的onBind()方法返回给客户端。
* 客户端使用IBinder对象来初始化Messager(that references the service's Handler),客户端使用该Messager来发送Message对象给service。
* service在它的Handler中接收每个Message——确切地说,是在handleMessage()方法中。
在这种方式中,service里并没有可供客户端调用的方法。取而代之的是,客户端传递“消息”(Message对象)而service在它的Handler里接收这些消息。
下面是使用Messager接口的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对象创建一个Messager,并使用send()方法发送消息。例如,下面是一个简单的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作出回应,就在客户端也创建一个Messager。然后当客户端接收到onServiceConnected()回调,它就发送一个Massage给service,该Massage包含了客户端的Massenger——在send()方法的replyTo参数中。
关于双向消息通信的例子可以参看MessengerService.java(service)和MessengerServiceActivity.java(client)的例子。
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对象。
创建ServiceConnection对象必须重写两个方法:
onServiceConnected():系统调用该方法来传递onBind()方法返回的IBinder对象。
onServiceDisconnected():Android系统在service意外失去连接时调用该方法,例如service已经crashed或者被kill掉。当客户端解除绑定时该方法不会被调用。
2.调用bindService(),传递ServiceConnection对象。
3.当系统调用你的onServiceConnected()回调方法,就可以使用定义在接口中的方法向service发送请求。
4.要解除绑定,调用unbindService()。
当你的客户端被销毁,它会与service解除绑定,但是你还是应该在与service完成交互时,或者你的activity进入暂停状态(pauses)就解除绑定,这样service就能在它不被使用时及时停止。(绑定或解除绑定的时机将在下面作更多讨论。)
例如,下面的程序片段把客户端与service连接(该service是extending the Binder class部分的那个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()方法来绑定service。例如:
Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
* bindService()方法的第一个参数是一个Intent对象,显示指定要绑定的service名字。
* 第二个参数是ServiceConnection对象。
* 第三个参数是一个flag,指定绑定的方式。通常情况下该参数的值应该是BIND_AUTO_CREATE,表示service没存活时自动创建它。其他可能的值为BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或者0。
Additional notes
下面是当绑定service时一些需要注意的地方:
* 要时刻追踪DeadObjectException异常,该异常在连接被破坏时抛出。只是运程方法能抛出的唯一异常。
* 总是应将邦定和解除邦定,成对的出现在客户端生命周期相对应的时刻。比如:
* 如果只有当你的activity可见时才需要与service交互,那么你应该在onStart()方法中邦定,在onStop()方法中解除邦定。
* 如果你希望你的activity即使停止在后台,也能接收响应,那么你可以在onCreate()方法中邦定,在onDestroy()方法中解除绑定。注意,这意味着,在你的activity的整个运行时间都要使用该service(即使activity在后台),所以如果service在其他的进程中,那么将增加你进程的比重(weight),并且它可能变得极有可能被杀掉.
注意:不应该在activity的onResume()和onPause()方法中执行绑定和解绑的工作,因为这些回调方法经常在activity的生命周期转换时调用,并且你应保持这些转换使用的时间最少化.还有,如果在你的应用中有多个activity,邦定到这一个service上,并在某两个activity之间有转换,该service可能在当前activity解除邦定,下一个邦定前,销毁并重创建了。(activity是怎样在转换中定位生命周期,在Activities文档中描述)
Managing the Lifecycle of a Bound Service
当一个服务从所有客户端解除邦定,Android系统销毁它(除非它也是用onStartCommand()方法启动的)。因此,如果你的服务纯粹是一个邦定服务,你不需要管理它的生命周期---系统根据它是否邦定到任何客户端来管理它的生命周期.
但是,如果你实现了onStartCommand()回调方法,那么你必须显式的停止服务,因为该服务是经过考虑被开启的。在这种情况下,服务一直运行,除非它调用stopSelf()停止自己,或者其他组件调用stopService()停止它,而不管它是否邦定到客户端无关。
另外,如果你的服务已开启并且接收了邦定,如你想客户下次邦定到服务时,接收到onRebind()调用,那么当系统调用你的onUnbind()方法时,你可以选择返回true.(而不是从onBind()接收一个调用)onRebind()方法返回void.但客户端仍在它的回调中onServiceConnected()接收IBinder。下图展示了这种生命周期的逻辑。

关于启动服务的生命周期更多信息,看Services文档

浙公网安备 33010602011771号