首页 > 代码库 > Android Api Component---翻译Bound Service
Android Api Component---翻译Bound Service
bound service是一个client-server接口中的server端。bound service允许组件(像activity)绑定service,发送请求,接受响应,并且甚至执行进程间通信(IPC)。典型的bound service寄宿于它服务的另一个应用程序组件中,并且不会无限期的运行于后台。
这篇文档教你如何创建一个bound service,也包含了如何从其他的应用程序组件中绑定service。然而,一般来说,你也应该参考Service文档取得关于service的额外的一些信息,像如何从service中传递通知,设置service运行于前台等等。
基础
一个bound service是一个Service类的实现,并且允许其它应用程序绑定它并与之交互。为了给一个service提供绑定,你必须实现onBind()回调方法。这个方法返回一个定义了一个可以让客户端与service交互的IBinder接口对象。
客户端可以通过bindService()绑定到这个service。当客户端这样做的时候,它必须提供一个监视与service的连接的ServiceConnection的实现。这个bindService()方法在没有值时立即返回,但是当Android系统在客户端和service之间创建连接的时候,它会在ServiceConnection上调用onServiceConnected()传递客户端与service通信的IBinder。
多个客户端可以立刻连接到这个service上。然而,只有当你的第一个客户端绑定的时候系统才会调用你的service的onBind()方法获得IBinder。然后系统在没有再一次调用onBind()的时候会给额外的绑定service的客户端传递相同的IBinder。
当最后一个客户端从service中解开的时候,系统就销毁service(除非这个service又通过startService()被开启)。
当你实现你的bound service的时候,最重要的部分就是定义你的onBind()回调方法返回的接口。你可以用几种不同的方式来定义你的service的IBinder接口并且下面的段落会讨论每一个技术。
创建一个Bound Service
当创建一个提供绑定的service的时候,你必须提供一个可以让客户端与service交互的程序接口IBinder。你可以用三种方式定义这个接口:
继承Binder类
如果你的service对你的应用程序是私有的并且与客户端(它是共有的)一样运行在相同的进程中,那么你就应该通过继承Binder类创建你的接口并且从onBind()返回一个它的实例。客户端接收Binder并且可以使用它在要么是Binder实现中要么是甚至Service中直接访问有效的公共方法。
当你的service仅仅只为你的应用程序作为后台工作者的时候,这是被推荐的技术。你不能用这种方式创建你的接口的唯一原因是因为你的service被其它的应用程序使用或者访问分隔的进程。
使用信号
如果你需要你的接口在工作的时候访问不同的进程,你可以用Messenger给你的service创建一个接口。这种方式是service定义一个句柄来响应不同的Messenge对象的类型。这个可以跟客户端共享一个IBinder以及允许客户端使用Message对象给service发送命令的Handler是Messenger的基础,此外,客户端可以定义一个自己的Messenger以至于service可以来回的发送消息。
这是最简单的进程间通信,因为Messenger让所有的请求排队到一个单个的线程中以至于你不需要把你的service设计为线程安全的。
使用AIDL
AIDL(Android Interface Definition Language)将所有的工作对象分解为操作系统理解的单元并且组织它们访问进程来执行IPC。前面使用Messenger的技术其实是AIDL的底层架构基础。正如上面被提及的,这个Messenger在单个线程中创建了所有的客户端的请求队列,因此service一次接收一个请求。然而如果你想让你的service同事处理多个请求,那么你就可以直接使用AIDL。在这个例子中,你的service一定是多线程的并且被构建成了线程安全的。
为了直接使用AIDL,你必须创建一个.aidl文件来定义程序接口。Android SDKtools使用这个文件生成一个实现接口并且处理IPC的抽象类,你可以在你的service中扩展这个类。
注意:大多数应用程序不应该使用AIDL创建bound service,因为它也许要求多线程性能并且可能导致更多的复杂实现。同样的,AIDL不适用于这篇文档中讨论的教你给你的service如何使用它的。如果你确信你需要直接使用AIDL,那看AIDL文档。
扩展Binder类
如果你的应用程序只在你的本地应用程序中使用并且不需要访问进程工作,那么你可以实现你自己的Binder类,在你的service中直接给你的客户端提供访问公共方法。
注意:这种工作紧紧是如果客户端和service是在相同的应用程序和进程中,它们都是相同的。例如,这种方式很可能会让一个给运行在后台的它自己的service绑定了activity的音乐应用程序运行良好。
这是如何设置的步骤:
1.你的service中创建一个Binder的实例,可选的:
包含客户端可以调用的公共方法
返回当前的有客户端可以调用的公共方法的Service实例
或者通过service返回主体中另一个带有可以让客户端调用的公共方法的类
2.从onBinder()回调方法中返回Binder的实例
3.在这个客户端中,从onServiceConnected()回调方法中接收Binder并且使用被提供的方法回调bound service。
注意:service和客户端必须在相同应用程序中的原因是客户端可以转换返回的对象并且适当的调用它的API。service和客户端也必须在相同的进程中是因为这种技术部执行任何顺序的访问进程。
例如,这是一个提供客户端在service中通过一个Binder实现访问方法的service:
public class LocalService extends Service { //Bind 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 publisc methods return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } /**method for clients*/ public int mgetRandomNumber() { return mGenerator.nextInt(100); } }
这个LocalBinder给客户端提供了getService()方法返回当前的LocalService的实例。这就允许客户端在service中调用公共方法。例如,客户端可以从service中调用getRandomNumber()。
这是一个当一个按钮按下的时候绑定了LocalService并且调用getRandomNumber()的activity:
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(); 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被停止的时候)。
使用Messenger
如果你需要你的service跟远程进程通信,那么你可以使用Messenger给你的service提供接口。这种技术允许你在不需要使用AIDL的情况下执行进程间通信。
这是一个如何使用Messenger的概述:
这个service实现了一个从一个客户端给每一个调用接收一个回调的Handler。
这个Handler用于创建一个Messenger对象(它是对Handler的一个映射)。
这个Messenger创建了service从onBind()返回给客户端的一个IBinder。
客户端使用IBinder初始化一个用于给这个service发送Message对象的Messenger。
这个service在它的在handleMessage()方法中指定的Handler中获取每一个Message。
用这种方式,就给客户端没有方法来调用service。相反,客户端传递service在它的Handler中接收的"messages"(Message对象)。
这是一个service使用一个Messenger接口的简单例子:
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 binging 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 mMessage.getBinder(); } }
注意在Handler中的handleMessage()方法是service基于what member接收输入的Message的并且决定做什么的地方。
所有的客户端需要做的是通过service基于IBinder创建一个Messenger并且使用send()发送一个message。例如,这是一个简单的绑定了service的并且给service传递MSG_SAY_HELLO消息的activity:
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 //unexpectedlly 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()回调的时候,给service在send()方法的replyTo参数中发送一个包含客户端的Messenger的Message。
你可以在MessengerService.java和MessengerServiceActivities.java中看一个如何提供两种方式的messaging的例子。
绑定到service
应用程序组件能够通过调用bindService()绑定一个service。Android系统然后调用service的返回一个跟service交互的IBinder的onBind()方法。
这个绑定是异步的。bindService()立即返回并且不会给客户端返回IBinder。为了获取IBinder,客户端必须创建一个ServiceConnection的实例并且传递给bindService()。ServiceConnection包含一个系统调用传递IBinder的回调方法。
注意:只有activities,services和content providers可以绑定一个service,你不能从一个broadcast receiver中绑定一个service。
因此,为了从你的客户端绑定一个service,你必须:
实现ServiceConnection.
你的实现必须覆盖两个回调方法:
onServiceConnected()
系统调用这个方法来传递通过service的onBind()方法返回的IBinder。
onServiceDisconnected()
当连接到这个service出现未预期的掉线时,Android系统会调用它,比如当service奔溃或者被杀死。当客户端被解开的时候不会被调用。
调用bindService()传递ServiceConnection实现。
当系统调用你的onServiceConnected()回调方法的时候,你能够使用通过这个接口定义的方法开始调用service。
从service中断开连接,调用unbindService()。
当你的客户端被销毁的时候,它将从service中解开,但是当你完成了与service的交互或者当你的activity暂停以至于这个service当它不被使用的时候被关闭的时候你应该总是解开。
例如,下面的片段连接客户端到通过继承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 disconnectes unexpectedly public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "onServiceConnected"); mBound = false; } };
通过ServiceConnection,客户端通过给bindService()传递它可以绑定到一个service。例如:
Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
bindService()的第一个参数是一个绑定给这个service的显式名字的Intent。
第二个参数是ServiceConnection对象。
第三个参数是一个标明绑定的标记。它通常应该是BIND_AUTO_CREATE为的是如果它的已经不是存货的就创建这个service。其它的可能的值时BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND或者是0.
额外的注解
这是一些关于绑定到service的重要的注意点:
你应该总是捕获DeadObjectException异常,当连接断开之后它会被抛出。这仅仅是通过远程方法抛出的异常。
对象是引用技术访问的进程。
通常你应该在开启和拆除客户端生命周期的时刻的时候匹配结对的绑定与解开。例如:
当你的activity是可见的时候如果你只需要跟service交互,你应该在onStart()期间绑定并且在onStop()期间解开。
如果你想让你的activity接收响应甚至是在后台被停止的时候,那么你可以在onCreate()期间绑定并且在onDestroy()期间解开。注意这就意味着你的activity需要使用service运行期的整个时间(甚至在后台),因此如果这个service在另一个进程中,那么你增加了让系统更可能会杀死的进程的权重。
注意:你通常不应该在你的activity的onResume()和onPause()期间绑定和解开,因为这个回调会出现在每个生命周期事件并且你应该让进程出现在事件里的事件最小化。还有,如果在你的应用程序中的对个activity绑定了相同的service并且在那些activity之间有一个事物,这个service在下一个绑定解开以前也许会销毁并且重新被创建作为当前activity。
对于更多的例子代码,看在ApiDemos中的RemoteService.java类如何绑定service。
管理绑定的service的生命周期
当一个service从所有的客户端解开的时候,Android系统销毁它(除非它又用onStartCommand()开启)。当然,如果你的service是一个纯粹的由Android系统基于是否绑定到任何客户端替你管理bound servcie,你不需要管理你的service的生命周期。
然而,如果你选择实现onStartCommand()回调方法,那么你必须显式的停止service,因为这个service现在被认为是开启的。在这个例子中,除非这个service用stopSelf()停止它自己或者另一个组件调用stopService()停止这个service,否则这个service会一直运行。
此外,如果你的service被开启并且接受了绑定,那么当系统调用你的onUnbind()方法的时候,你可以有选择的返回true如果你想获得一个下一次对一个客户端onRebind()的调用。onRebind()返回void,但是客户端任然在它的onServiceConnected()回调中接收IBinder。下面的图是这个逻辑的生命周期类型:
Android Api Component---翻译Bound Service