[翻译]API Guides - Bound Services
官方文档原文地址:http://developer.android.com/guide/components/bound-services.html
一个Bound Service是一个客户端-服务器接口的服务。一个Bound Service允许组件(像activity)绑定一个service,发送请求,接受结果,甚至进行进程间通信。一个Bound Service通常只在他向其它组件提供服务的时候运行,不会无法确定的运行在后台的。
这篇文档将展示你如何去创建一个Bound service,包括如何绑定一个bound service到其它的应用程序组件上。然而,你也应该关注Service的文档来获取更多关于常规service的额外的内容,比如如何通过service发送一条通知啦,让service在前台运行啦等等。
基础
一个Bound service是Service类的一个实现,它允许其它的应用程序组件去绑定它,并且与它交互。为了能提供与service的绑定,你必须实现onBind()回调方法。这个方法返回一个IBinder对象,这个对象定义了一个客户端可以与service进行交互的接口。
一个客户端可以通过调用bindService()来与service进行绑定。当这个方法调用的时候,它必须提供一个ServiceConnection的实现,这个对象监控着service的连接。bindService()马上就调用完成,并且没有任何返回值,但是当Android系统创建客户端与服务之间的连接时,它会调用ServiceConnection的onServiceConnected()方法,去传递一个用于客户端与service交互的IBinder对象。
多个客户端可以同时连接service。系统只在第一个客户端绑定的时候会调用你的service的onBind()方法去获取一个IBinder对象。然后系统会传递同一个IBinder给其它的客户端,不会再调用onBind()了。
当最后一个客户端解绑了service,系统就会摧毁这个service(除非service也通过startService()启动)。
当你要实现自己的Bound Service,最重要的部分是你要定义onBind()返回的接口。这里有几种不同的方式去定义service的IBinder接口,下面的部分将慢慢讨论技术细节。
创建一个Bound Service
当创建一个绑定的service的时候,你必须提供一个IBinder用于客户端与service进行交互。这里有三种定义这个接口的方式:
Extending the Binder class
如果你的service对你的应用来说是私有的,并且与客户端运行在相同的进程里,你应该通过扩展Binder类来实现你的接口,然后通过onBind()返回一个实例即可。客户端接收Binder,并且可以直接使用它的公有方法,这些方法可能是在Binder里面,甚至可以是service里面的。
当你的service仅仅是在后台完成一些工作的时候,这种方式是最优先的选择。你不应该使用这种方式的唯一原因就是你的service同时需要被其他的应用程序使用。
Using a Messenger
如果你需要你的接口跨进程工作,你可以通过Messenger来为你的service创建接口。在这种方式里,Service需要定义一个Handler来响应不同类型的Message对象。这个Handler是Meesager能把IBinder对象分享给客户端,允许客户端通过使用Message来发送指明的基础。另外,客户端可以定义自己的Messenger,所以service能够发送返回消息。
这是最简单的进程间通信的方式,因为Messenger对垒总是一个单线程的,所以你不得不去更好的设计service让它线程安全。
Using AIDL
AIDL(Android Interface Definition Language)执行了把对象分解成能让操作系统理解的原子功能的所有工作,这样操作系统便可以安排它们完成进程间通信。前一个技术方案,使用Messenger,实际上是基于AIDL的。如上面所提到的,Messenger在单线程里创建一个处理所有客户端发送的请求的队列,所以service一次只能接受一个请求。如果你想同时处理多个请求,你可以直接使用AIDL。这种情况下,你的service必须是多线程的,并且线程安全。
想直接使用AIDL,你必须创建一个.aidl文件用来定义程序的接口。Android SDK工具使用这个文件,把它生成为一个实现了接口并且处理进程间通信的抽象类,然后你可以在你的service中扩展它。
笔记:大多数应用不应该使用AIDL来创建Bound Service的,因为它可能要求多线程的处理能力,这直接的结果就是导致非常复杂的实现。所以,AIDL不适合大多数应用,这篇文档不会讨论如何在你的service里使用它。如果你想要使用它的话,请参考AIDL的文档。
Extending the Binder class
如果你的service只是在本地应用上使用,不需要跨进程工作,你可以实现你自己的Binder类来为客户端提供直接访问service里面公有方法的接口。
笔记:这样的工作只在客户端与service是在同一个应用,同一个进程里,这也是最常见的情况。比如说,这种方式对于一个音乐应用来说将会很好用,它需要绑定一个service到它activity的上用来在后台播放音乐。
接下来是如何完成它:
1.在你的service里创建一个Binder的实例:
包含允许客户端调用的公有方法。
返回当前service的实例,它有允许客户端调用的公有方法。
或者返回service所持有的其它类的实例,里面有允许客户端调用的公有方法。
2.在onBind()方法里返回Binder的实例。
3.在客户端里,从onServiceConnected()方法里接收Binder对象,然后调用Bound Service所提供的方法。
笔记:service与客户端必须在同一个应用里的原因就是客户端可以强制转型返回过来的对象与属性,这样就可以调用它的API了。service与客户端必须在同一个进程里,这是因为技术上不允许执行任何跨进程的通信。
接下的例子,service提供了让客户端直接使用的方法。
publicclassLocalServiceextendsService{ // Binder given to clients privatefinalIBinder mBinder =newLocalBinder(); // Random number generator privatefinalRandom mGenerator =newRandom(); /** * 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. */ publicclassLocalBinderextendsBinder{ LocalService getService(){ // Return this instance of LocalService so clients can call public methods returnLocalService.this; } } @Override publicIBinder onBind(Intent intent){ return mBinder; } /** method for clients */ publicint getRandomNumber(){ return mGenerator.nextInt(100); } }
LocalBinder给客户端提供了getService()方法,用来获取当前LocalService的实例。这就允许了客户端直接调用service里的公有方法。上面的例子里,客户端可以调用service里面的getRandomNumber()方法。
接下来是一个绑定了LocalService的Activity,当按钮按下的时候,调用getRandomNumber()。
publicclassBindingActivityextendsActivity{ LocalService mService; boolean mBound =false; @Override protectedvoid onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protectedvoid onStart(){ super.onStart(); // Bind to LocalService Intent intent =newIntent(this,LocalService.class); bindService(intent, mConnection,Context.BIND_AUTO_CREATE); } @Override protectedvoid 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) */ publicvoid 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() */ privateServiceConnection mConnection =newServiceConnection(){ @Override publicvoid 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 publicvoid onServiceDisconnected(ComponentName arg0){ mBound =false; } }; }
上面的例子展示了客户端如何绑定service,使用ServiceConnection的实现和onServiceConnected()回调方法。接下来的部分,提供了更多的关于绑定service的信息。
笔记:上面的例子并没有明确的从service那里解绑,但是所有的客户端都应该在合适的时候解绑(比如当Activity pause的时候)。
想要查看更多的代码,在ApiDemos里查看LocalService.java类与LocalServiceActivity.java类。
Using Messenger
如果你需要你的service与其他的进程进行通信,你可以使用Messenger来为你的service提供接口。这项技术允许你不使用AIDL来完成进程间通信。
下面是使用Messenger的几个步骤:
- service实现一个Handler为从客户端的每一个调用接收回调。
- Hander用来创建Messenger对象。
- Messenger创建IBinder对象,用于客户端从service的onBind()方法了获取IBinder。
- 客户端使用IBinder来初始化Messenger,这样客户端可以使用它给service发送Message对象。
- service从它的handle里接收每一个Message,准确的说,是在handleMessage()方法里。
接下来是一个使用Messenger的简单例子:
publicclassMessengerServiceextendsService{ /** Command to the service to display a message */ staticfinalint MSG_SAY_HELLO =1; /** * Handler of incoming messages from clients. */ classIncomingHandlerextendsHandler{ @Override publicvoid 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. */ finalMessenger mMessenger =newMessenger(newIncomingHandler()); /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override publicIBinder onBind(Intent intent){ Toast.makeText(getApplicationContext(),"binding",Toast.LENGTH_SHORT).show(); return mMessenger.getBinder(); } }
注意Handler里面的handleMessage()方法,servie用它来获取消息对象,并且基于what成员属性来决定做什么。
客户端所要做的就是基于IBinder对象创建一个Messenger,使用这个对象通过send()方法来发送消息。下面是个简单的例子,绑定了service的activity发送MSG_SAY_HELLO消息给service:
publicclassActivityMessengerextendsActivity{ /** 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. */ privateServiceConnection mConnection =newServiceConnection(){ publicvoid 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 =newMessenger(service); mBound =true; } publicvoid 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; } }; publicvoid 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 protectedvoid onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protectedvoid onStart(){ super.onStart(); // Bind to the service bindService(newIntent(this,MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); } @Override protectedvoid onStop(){ super.onStop(); // Unbind from the service if(mBound){ unbindService(mConnection); mBound =false; } } }
注意这个例子并没有展示service如何回应客户端。如果你想要service去响应客户端,那么你需要在客户端里也创建一个Messenger。当客户端接收onServiceConnected()回调的时候,它向service发送一个消息,它包括了客户端的send()方法中的replyTo参数的Messenger。
你能在MessengerService.java和MessengerServiceActivities.java中看到如何创建一个双向的消息机制。
绑定Service
应用程序组件(客户端)可以通过调用bindService()方法来绑定一个service。Android系统会调用service的onBind()方法,这个方法会返回一个IBinder对象用于和客户端进行交互。
绑定的过程是异步的。bindService()很快就运行结束,并且不会返回一个IBinder对象给客户端。为了接受IBinder,客户端必须创建一个ServiceConnection的实例,然后把它传给bindService()。ServiceConnection包含了供系统调用的传递IBinder的回调方法。
笔记:只有activities,services和content providers可以绑定service,你不可以给一个broadcast receiver绑定一个service。
所以,想要给你的客户端绑定service,你必须:
1.实现ServiceConnection
你的实现必须覆写两个回调方法:
onServiceConnected()
系统调用这个方法来传递由service的onBind()方法返回的IBinder对象。
onServiceDisconnected()
Android系统调用这个方法,是当service被意外的终止了,比如service卡死了,或者被系统杀掉了。当客户端解绑的时候,这个方法是不会被调用的。
2.调用bindService(),并传一个ServiceConnection对象。
3.当系统调用你的onServiceConnected()方法的时候,你可以开始通过在接口中定义的方法来使用service。
4.要断开与service的连接,调用unbindService()。
当你的客户端被销毁的时候,它将会解绑service,但是,你应该在你完成于service的交互或者你的activity暂停的时候去解绑service,因为这样当service没有被使用的时候可以停下来(更多关于何时去绑定与解绑的讨论在下面)。
下面的例子展示了客户端连接到上面extending the Binder class所创建的service,所必须做的就是把返回的IBinder对象强制转型成LocalService的实例:
LocalService mService; privateServiceConnection mConnection =newServiceConnection(){ // Called when the connection with the service is established publicvoid 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 publicvoid onServiceDisconnected(ComponentName className){ Log.e(TAG,"onServiceDisconnected"); mBound =false; } };
客户端可以通过给bindService()方法放一个ServiceConnection参数来绑定一个service:
Intent intent =newIntent(this,LocalService.class); bindService(intent, mConnection,Context.BIND_AUTO_CREATE);
- bindService()的第一个参数是一个指定了绑定哪一个service的Intent对象(虽然intent也可以是隐式的)。
- 第二个参数是ServiceConnection对象。
- 第三个参数是一个绑定可选的标示符。通常为了去创建一个不存在的service,应该使用BIND_AUTO_CREATE。其它的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或者0。
额外的提示
关于绑定service,这里有一些重要的提示:
- 你应该总是捕获DeadObjectException异常,这个异常将在连接被破坏的时候抛出,也是唯一一个通过远程方法抛出的异常。
- 在进程之间,对象的引用被计数。( Objects are reference counted across processes)
- 你应该总是在你客户端的生命周期里成对的去绑定和解绑,匹配与连接与断开的时候。
-
- 如果你仅仅是希望service与你的activity交互在可见的时候,你应该在onStart()方法中绑定,onStop()方法中解绑。
- 如果你希望你的activity能够在自己停止前一直能接收到反馈结果,你应该在onCreate()里绑定,在onDestroy()里解绑。注意,这意味着你的activity需要一直使用service(甚至是在后台。)所以,如果service在另一个进程里,所以你应该提高你进程的权重,这样系统就不太可能会杀掉它了。
笔记:你不应该在onResume()和onPause()方法里绑定和解绑service,因为这些回调发生在每一次生命周期的转换的时候,你应该把工作的过程放在转换较少的地方。如果你的应用程序里有多个activity要绑定同一个service,这个转换就会发生在两个activity之间,service将会在当前activity解绑的时候(onPause())被摧毁,然后又在下一次绑定之前(onResume())被重新创建。(关于activity是如何协调生命周期来进行activity之间的转换的更多内容,请查看Activiies的文档。)
管理Bound Service的生命周期
当service从所有的客户端上解绑,系统就会销毁它(除非它同时通过了onStartCommand()方法来启动)。所以,你不必要去管理一个仅仅是Bound Service的生命周期。
然后,如果你选择去实现onStartCommand()方法,你就必须明确的停止service,这时service被认为是started。在这种情况下,service会一直运行,直到service通过stopSelf()或者其他组件通过stopService()来停止它。
另外,如果你的service是started的,并且接受了绑定。那么当系统调用你的onBind()方法的时候,你可以选择返回true,这样你可以在下次需要绑定的时候调用onRebind()方法。onRebind()方法什么都不反悔,但是客户端仍然可以在onServiceConnected()方法接受到IBinder对象,下面的图就展示了这种service的生命周期的逻辑。
关于更多started service的信息,请参考Services文档。