一、Binder架构 在Android中,Binder用于完成进程间通信(IPC),即把多个进程关联在一起。比如,普通应用程序可以调用音乐播放服务提供的播放、暂停、停止等功能。 Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块。
设计Service端很简单,从代码的角度来讲,只要基于Binder类新建一个Servier类即可。以下以设计一个Service类为例。 class MyService extends Binder { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { // 接收客户端传过来的消息参数 return super.onTransact(code, data, reply, flags); } public void start(String name) { } public void stop() { } } 当要启动该服务时,只需要初始化一个MyService对象即可。之后可以在DDMS中看到多了一个线程 定义了服务类后,接下来需要重载onTrasact()方法,并从data变量中读出客户端传递的参数,比如start()方法所需要的name变量。然而,服务端如何知道这个参数在data变量中的位置?因此,这就需要调用者和服务者双方有个约定。假定客户端在传入的包裹data中放入的第一个数据就是filePath变量,则onTransact()的代码可以如下所示: switch (code) { case 0x100: data.enforceInterface("MyService"); String name = data.readString(); start(name); break; } code变量用于标识客户端期望调用服务端的哪个函数,因此,双方需要约定一组int值,不同的值代表不同的服务端函数,该值和客户端的transact()函数中第一个参数code的值是一致的。这里假定0x100是双方约定要调用start()函数的值。 三、Binder客户端设计 要想使用服务端,首先要获取服务端在Binder驱动中对应的mRemote变量的引用,获取的方法后面将介绍。获得该变量的引用后,就可以调用该变量的transact()方法。该方法的函数原型: IBinder mRemote = null; String name = "Livingstone"; int code = 0x100; Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("MyService"); data.writeString(name); mRemote.transact(code, data, reply, 0); IBinder binder = reply.readStrongBinder(); reply.recycle(); data.recycle(); 现在来分析以上代码。首先,包裹不是客户端自己创建的,而是调用Parcel.obtain()申请的,这正如生活中的邮局一样,用户一般只能用邮局提供的信封(尤其是EMS)。其中data和reply变量都由客户端提供,reply变量用户服务端把返回的结果放入其中。 接着调用transact()方法。调用该方法后,客户端线程进入Binder驱动,Binder驱动就会挂起当前线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹。服务端拿到包裹后,会对包裹进行拆解,然后执行指定的服务函数,执行完毕后,再把执行结果放入客户端提供的reply包裹中。然后服务端向Binder驱动发送一个notify的消息,从而使得客户端线程从Binder驱动代码区返回到客户端代码区。 四、使用Service类 以上手工编写Binder服务端和客户端的过程存在两个重要问题。 1、获取Binder对象 事实上,对于有创造力的程序员来讲,可以完全不使用Service类,而仅仅基于Binder类编写服务程序,但只是一部分。具体来讲,可以仅使用Binder类扩展系统服务,而对于客户端服务则必须基于Service类来编写。所谓的系统服务是指可以使用getSystemService()方法获取的服务,所谓的客户端服务是指应用程序提供的自定义服务。 public ComponentName startService(Intent intent); @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { IServiceConnection sd; if (mPackageInfo != null) { sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), mMainThread.getHandler(), flags); } // ......int res = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), sd, flags); } 该函数用于绑定一个服务,这就是第一个重要问题的关键所在。其中第二个参数是一个interface类,该interface的定义如以下代码所示: public interface ServiceConnection { public void onServiceConnected(ComponentName name, IBinder service); public void onServiceDisconnected(ComponentName name); } 此interface中的onServiceConnected()方法的第二个变量Service。当客户端请求AmS启动某个Service后, 该Service如果正常启动,那么AmS就会远程调用ActivityThread类中的ApplicationThread对象,调用的参数中会包含Service的Binder引用,然后在ApplicationThread中会回调bindService中的conn接口。因此,在客户端中,可以在onServiceConnected()方法中将其参数Service保存为一个全局变量,从而在客户端的任何地方都可以随时调用该远程服务。这就解决了第一个重要问题,即客户端如何获取远程服务的Binder引用。
public final void scheduleBindService(IBinder token, Intent intent, boolean rebind) { BindServiceData s = new BindServiceData(); s.token = token; s.intent = intent; s.rebind = rebind; queueOrSendMessage(H.BIND_SERVICE, s); } // if the thread hasn't started yet, we don't have the handler, so just // save the messages until we're ready. private final void queueOrSendMessage(int what, Object obj) { queueOrSendMessage(what, obj, 0, 0); } private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) { synchronized (this) { // ... Message msg = Message.obtain(); msg.what = what; msg.obj = obj; msg.arg1 = arg1; msg.arg2 = arg2; mH.sendMessage(msg); } } 2、AIDL工具对象使用 关于第二个问题,Android的SDK中提供了一个aidl工具,该工具可以把一个aidl文件转换为一个Java类文件,在该Java类文件中,同时重载了transact和onTransact()方法,统一了存入包裹和读取包裹参数,从而使设计者可以把注意力放到服务代码本身上。aidl工具不是必需的,对于有经验的程序员来讲,手工编写一个参数统一的包裹存入和包裹读出代码并不是一件复杂的事情。 package com.androidstudy; interface IMyService{ boolean start(String path); void stop(); } 该文件的名称必须遵循一定的规范,第一个字母“I”不是必需的,但是,为了程序风格的统一,“I”的含义是IInterface类,即这是一个可以提供访问远程服务的类。服务的类名可以是任意的,但是,aidl工具会以该名称命名输出Java类。这些规则都只是Eclipse下ADT插件的默认规则,aidl本身只是一个命令行程序,借助命令行的话,则可以灵活指定输出文件的名称及位置。 aidl文件的语法基本类似于Java,package指定输出后的Java文件对应的包名。如果该文件需要引用其他Java类,则可以使用import关键字,但需要注意的是,包裹内只能写入以下三个类型的内容。
因此,基本上来讲,import所引用的Java类也只能是以上三个类型。 public interface IMyService extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.androidstudy.IMyService { private static final java.lang.String DESCRIPTOR = "com.androidstudy.IMyService"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.androidstudy.IMyService * interface, generating a proxy if needed. */ public static com.androidstudy.IMyService asInterface( android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.androidstudy.IMyService))) { return ((com.androidstudy.IMyService) iin); } return new com.androidstudy.IMyService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_start: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); boolean _result = this.start(_arg0); reply.writeNoException(); reply.writeInt(((_result) (1) : (0))); return true; } case TRANSACTION_stop: { data.enforceInterface(DESCRIPTOR); this.stop(); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.androidstudy.IMyService { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public boolean start(java.lang.String path) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); boolean _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(path); mRemote.transact(Stub.TRANSACTION_start, _data, _reply, 0); _reply.readException(); _result = (0 != _reply.readInt()); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void stop() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_start = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public boolean start(java.lang.String path) throws android.os.RemoteException; public void stop() throws android.os.RemoteException; } 这些代码主要完成以下三个任务。
在Stub类中还定义了一些int常量,比如TRANSACTION_start,这些常量与服务函数对应, transact()和onTransact()方法的第一个参数code的值即来源于此。 因此,asInterface()函数正是利用了queryLocalInterface()方法,提供了一个统一的接口。无论是远程客户端还是本地端,当获取Binder对象后,可以把获取的Binder对象作为asInterface()的参数,从而返回一个IMyService 接口,该接口要么使用Proxy类,要么直接使用Stub所实现的相应服务函数。 五、系统服务中的Binder对象 在应用程序中,经常使用getSystemService(String serviceName)方法获取一个系统服务,那么,这些系统服务的Binder引用是如何传递给客户端的呢?须知系统服务并不是通过startService()启动的。getSystemService()函数的实现是在ContextImpl类中,该函数所返回的Service比较多,具体可参照源码。这些Service一般都由ServiceManager管理。 ServiceManager本身也是一个Service,Framework提供了一个系统函数,可以获取该Service对应的Binder引用,那就是BinderInternal.getContextObject()。该静态函数返回ServiceManager后,就可以通过ServiceManager提供的方法获取其他系统Service的Binder引用。这种设计模式在日常生活中到处可见,ServiceManager就像是一个公司的总机,这个总机号码是公开的,系统中任何进程都可以使用BinderInternal.getContextObject()获取该总机的Binder对象,而当用户想联系公司中的其他人(服务)时,则要经过总机再获得分机号码。这种设计的好处是系统中仅暴露一个全局Binder引用,那就是ServiceManager,而其他系统服务则可以隐藏起来,从而有助于系统服务的扩展,以及调用系统服务的安全检查。其他系统服务在启动时,首先把自己的Binder对象传递给ServiceManager,即所谓的注册(addService)。 if (INPUT_METHOD_SERVICE.equals(name)) { return InputMethodManager.getInstance(this); static public InputMethodManager getInstance(Looper mainLooper) { synchronized (mInstanceSync) { if (mInstance != null) { return mInstance; } IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); IInputMethodManager service = IInputMethodManager.Stub.asInterface(b); mInstance = new InputMethodManager(service, mainLooper); } return mInstance; } 即通过ServiceManager获取InputMethod Service对应的Binder对象b,然后再将该Binder对象作为IInputMethodManager.Stub.asInterface()的参数,返回一个IInputMethodManager的统一接口。 public static IBinder getService(String name) { try { IBinder service = sCache.get(name); if (service != null) { return service; } else { return getIServiceManager().getService(name); } } catch (RemoteException e) { Log.e(TAG, "error in getService", e); } return null; } 即首先从sCache 缓存中查看是否有对应的Binder 对象,有则返回,没有则调用getIServiceManager().getService(name),函数getIServiceManager()即用于返回系统中唯一的ServiceManager对应的Binder,其代码如下: private static IServiceManager getIServiceManager() { if (sServiceManager != null) { return sServiceManager; } // Find the service manager sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject()); return sServiceManager; } BinderInternal.getContextObject()静态函数即用于返回ServiceManager对应的全局Binder对象,该函数不需要任何参数,因为它的作用是固定的。其他所有通过ServiceManager获取的系统服务的过程与以上基本类似,所不同的就是传递给ServiceManager的服务名称不同,因为ServiceManager正是按照服务的名称(String类型)来保存不同的Binder对象的。 2、理解Manger ServiceManager所管理的所有Service都是以相应的Manager返回给客户端,因此,这里简述一下Framework中关于Manager的语义。在我们中国的企业里,Manager一般指经理,比如项目经理、人事经理、部门经理。经理本身的含义比较模糊,其角色有些是给我们分配任务,比如项目经理;有些是给我们提供某种服务,比如人事经理;有些则是监督我们的工作等。而在Android中,Manager的含义更应该翻译为经纪人,Manager所manage的对象是服务本身,因为每个具体的服务一般都会提供多个API接口 ,而Manager所manage的正是这些API。客户端一般不能直接通过Binder引用去访问具体的服务,而是要经过一个Manager,相应 的Manager类对客户端是可见的,而远程的服务类对客户端则是隐藏的。而这些Manager的类内部都会有一个远程服务Binder的变量,而且在一般情况下,这些Manager的构造函数参数中会包含这个Binder对象。简单地讲,即先通过ServiceManager获取远程服务的Binder引用,然后使用这个Binder引用构造一个客户端本地可以访问的经纪人,然后客户端就可以通过该经纪人访问远程的服务。这种设计的作用是屏蔽直接访问远程服务,从而可以给应用程序提供灵活的、可控的API接口,比如AmS。系统不希望用户直接去访问AmS,而是经过ActivityManager类去访问,而ActivityManager内部提供了一些更具可操作性的数据结构,比如RecentTaskInfo数据类封装了最近访问过的Task列表,MemoryInfo数据类封装了和内存相关的信息。 通过本地Manger访问远程服务的模型图如下 |
|