基础篇-Binder机制和AIDL使用介绍

简介:
        我们知道,Android系统是基于Linux内核的,而Linux内核继承和兼容了丰富的Unix系统进程间通信(IPC)机制。例如 管道(Pipe), 报文队列(Message)、共享内存(Share Memory)和信号量(Semaphore)等等。但是 Android系统没有采用上述提到的各种进程间通信机制,而是采用Binder机制。 在Android系统的Binder机制中,由一系列系统组件组成,分别是Client、Server、Service Manager和Binder驱动程序,其中Client、Server和Service Manager运行在用户空间,Binder驱动程序运行内核空间。Binder就是一种把这四个组件粘合在一起的粘结剂了,其中,核心组件便是Binder驱动程序了,Service Manager提供了辅助管理的功能,Client和Server正是在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。Service Manager和Binder驱动已经在Android平台中实现好,开发者只要按照规范实现自己的Client和Server组件就可以了。说起来简单,做起难,对初学者来说,Android系统的Binder机制是最难理解的了,而Binder机制无论从系统开发还是应用开发的角度来看,都是Android系统中最重要的组成,因此,还是有必要了解一下,楼主没有阅读过源码,只能浅谈一下自己的认识。


一.Binder机制关系图:



        1. Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中
        2. Binder驱动程序和Service Manager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server
        3. Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信
        4. Client和Server之间的进程间通信通过Binder驱动程序间接实现
        5. Service Manager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力
  ServiceManager ,这是 Android OS 的整个服务的管理程序, 任何service在被使用之前,均要向SM(Service Manager)注册,同时客户端需要访问某个service时,应该首先向SM查询是否存在该服务。如果SM存在这个service,那么会将该service的handle( 每个service的唯一标识符 )返回给client,client初始化binder代理接口( 打开/dev/binder设备;在内存中为binder映射128K字节空间 ),对对应service直接调用,代理接口中定义的方法与 与server中定义的方法是一一对应的, 代理接口的方法会将client传递的参数打包成为Parcel对象( Parcel是binder IPC中的最基本的通信单元,它存储Client-Server间函数调用的参数.但是Parcel只能存储基本的数据类型,如果是复杂的数据类型的话,在存储时,需要将其拆分为基本的数据类型来存储 ), 发送给内核中的binder driver ;其中维护了一个 死循环,不停地去读内核中binder driver,查看是否有可读的内容;即是否有对service的操作要求, 如果有,则调用svcmgr_handler回调来处理请求的操作, 整个的调用过程是一个同步过程,在server处理的时候,client会block住。

如果对于Binder机制的原理和源码实现感兴趣的,可以去看看老罗的bolg介绍,这里暂时介绍下java层实现的方式吧,主要以调用远程服务的过程来解析。


二.远程服务


1.远程服务介绍

由于每个应用程序都运行在自己的进程空间,应用程序之间不能共享内存,访问其他应用的服务,关键就在于 跨进程通信。
通过代码来实现这个数据传输过程是冗长乏味的, Android 提供了 AIDL 工具来处理这项工作, Binder是Android系统进程间通信(IPC)方式之一 AIDL(Android Interface Definition Language) 的 IPC机制是面向对象的,轻量级的。通过AIDL定义的接口可以实现服务器端与客户端的IPC通信。 它是使用代理类在客户端和实现端传递数据

2.远程服务使用场景介绍

(1)如果不需要进行不同应用程序间的并发通信(IPC)通过实现一个Binder对象来创建接口就行,比如你和这个远程服务是同一个进程中
(2)你想进行IPC,简单Message消息传递,不涉及多线程处理,则使用Messenger对象来创建接口
(3)AIDL: 只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service中多线程处理时使用这种方式实现。

3.这里就先简单介绍下Messenger的方式


Messenger简介
看一下Messenger的源码实现,就知道了,其 就是依赖了一个默认设计的Imessenger.aidl文件,继承了IMessenger.Stub类,实现了send方法,send方法中参数会通过客户端传递过来,最终发送给handler进行处理。
而Messenger 是和 Handler 以及 IBinder 绑定在一起的,因此 Messenger 的构造函数有两种:
a. 本地Messenger 是传入一个 Hanlder ,根据传入的 Handler 创建 Messenger ,且该 Messenger 指向该 Handler ,当我们向 Messenger 发送信息的时候, Handler 会受到信息并处理消息,该构造函数往往是在某个类中构建该类自身的 Messenger ,比如在 MyService 中用 ServiceHandler 的实例初始化了自身的 serviceMessenger 以及在客户端中用 ClientHandler 的实例初始化了其自身的 clientMessenger 。这种 Messenger 可以看做是本地的 Messenger 。创建完的 Messenger 可以通过 getBinder() 方法得到对应的 IBinder 类型的实例,简单来说,就是包含本地应用的Handler。
b. 远程messenger,是传入一个IBinder,根据传入的IBinder实例创建一个远程的Messenger。这种构造函数往往是在客户端中,通过得到ServiceonBind方法返回的IBinder,然后基于此IBinder初始化一个远程的Messenger。该Messenger指向的是Service,而不是客户端,所以该Messenger就是一种远程的Messenger。比如客户端中的serviceMessenger就是一种远程的Messenger,指向的是MyService,简单来说就是包含远程应用的Handler

通信步骤

(1). 这个Service中需要实现一个Hanlder,用以处理从所有客户端收到的消息,阻塞处理,一般不耗时
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private class ServiceHandler extends Handler {  
  2.         @Override  
  3.         public void handleMessage(Message msg) {  
  4.             if(msg.what == RECEIVE_MESSAGE){  
  5.                 Bundle data = msg.getData();  
  6.                 if(data != null){  
  7.                     String str = data.getString("msg");  
  8.                     Log.i("DemoLog""收到客户端如下信息: " + str);  
  9.                 }  
  10.                 //通过Message的replyTo获取到客户端自身的Messenger,  
  11.                 //Service可以通过它向客户端发送消息  
  12.                 clientMessenger = msg.replyTo;  
  13.                 if(clientMessenger != null){  
  14.                     Log.i("DemoLog", “服务端向客户端回信");  
  15.                     Message msgToClient = Message.obtain();  
  16.                     msgToClient.what = SEND_MESSAGE;  
  17.                     //可以通过Bundle发送跨进程的信息  
  18.                     Bundle bundle = new Bundle();  
  19.                     bundle.putString("msg", "你好,客户端,我是服务端”);  
  20.                     msgToClient.setData(bundle);  
  21.                     try{  
  22.                         clientMessenger.send(msgToClient);  
  23.                     }catch (RemoteException e){  
  24.                         e.printStackTrace();  
  25.                         Log.e("DemoLog""thisservice向客户端发送信息失败: " + e.getMessage());  
  26.                     }  
  27.                 }  
  28.             }  
  29.         }  
  30.     }  


(2). 用该 Handler 创建一个 Messenger 对象, Messenger 对象内部会引用该 Handler 对象
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Messenger serviceMessenger =new Messenger(newServiceHandler())  


(3). 用创建好的Messenger对象获得一个IBinder实例,并且将该IBinder通过ServiceonBind方法返回给各个客户端
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.  publicIBinder onBind(Intent intent) {  
  2. //获取Service自身Messenger所对应的IBinder,并将其发送共享给所有客户端  
  3. returnserviceMessenger.getBinder();  
  4. }  


(4). 客户端通过bindService(intent,serviceConnection,autocreate),连接成功后获取 IBinder 对象实例化一个 Messenger 对象,该 Messenger 内部指向指向外部 Service 中的 Handler 。客户端通过该 Messenger 对象就可以向 Service 中的 Hanlder 发送消息。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private ServiceConnection conn = new ServiceConnection() {  
  2.         @Override  
  3.         public void onServiceConnected(ComponentName name, IBinder binder) {  
  4.             serviceMessenger = new Messenger(binder);  
  5.             Message msg = Message.obtain();  
  6.             msg.what = SEND_MESSAGE;  
  7.             Bundle data = new Bundle();  
  8.             data.putString("msg""你好,我是客户端");  
  9.             msg.setData(data);  
  10.             //需要将Message的replyTo设置为客户端的clientMessenger,  
  11.             //以便Service可以通过它向客户端发送消息  
  12.             msg.replyTo = clientMessenger;  
  13.             try {  
  14.                 serviceMessenger.send(msg);  
  15.             } catch (RemoteException e) {  
  16.                 e.printStackTrace();  
  17.                 Log.i("DemoLog""客户端向service发送消息失败: " + e.getMessage());  
  18.             }  
  19.         }  
  20.   
  21.         @Override  
  22.         public void onServiceDisconnected(ComponentName name) {  
  23.             //客户端与Service失去连接  
  24.             serviceMessenger = null;        }  
  25.     };  


(5). Service 中的 Hanlder 收到消息后,在 Handler 中的 handleMessage 方法中处理消息。
(6).通过上面的第4步与第5步,就完成了客户端向Service发送消息并且Service接收到消息的单向通信过程,即客户端-> Service。如果要实现Service向客户端回消息的通信过程,即Service ->客户端,那么前提是在客户端中也需要像Service一样内部维护有一个指向HandlerMessenger。当在第四步中客户端向Service发送信息时,将MessagereplyTo属性设置为客户端自己的Messenger。这样在第5ServiceHandlerhandleMessage中处理收到的消息时,可以通过MessageMessenger再向客户端发送Message,这样客户端内维护的Handler对象就会收到来自于ServiceMessage,从而完成Service向客户端发送消息且客户端接收到消息的通信过程。


其他注意点: 


(1).当客户端发起的bindService操作后,如果服务没启动,我们通过intent启动Service,Service开始执行其生命周期,先执行onCreate回调方法,然后执行onBind回调方法,在执行onBind方法的时候,该方法返回了Service中本地serviceMessenger所对应的binder,将其返回给客户端。如果服务启动,继续onRebind(),返回service中 本地serviceMessenger所对应的binder。

(2).远程应用的Service的onBind方法返回之后,会将IBinder传入客户端的ServiceConnection对象的onServiceConnected回调方法中,该方法的执行表明客户端与Service建立了连接。此时,我们会根据来自于Service的IBinder初始化一个指向Service的serviceMessenger,serviceMessenger是一个远程Messenger,我们通过对其操作,发送消息,得到处理。

(3). 当用Messenger在两个进程之间传递Message时, Message的obj不能设置为设置为non-Parcelable的对象, 在跨进程的时候可以不使用Message的obj,用Bundle传递数据,setData设置Bundle数据,getData获取Bundle数据

(4). 当通过执行bindService(intent, conn, BIND_AUTO_CREATE)代码的时候,如果intent只设置了action和category,没有明确指明要启动的组件,那么该intent就是是隐式的。在Android 5.0及以上的版本中,必须使用显式的intent去执行启动服务,如果使用隐式的intent,则会报错误。
解决办法是我们先构建一个隐式的Intent,然后通过PackageManager的resolveService获取可能会被启动的Service的信息。如果ResolveInfo不为空,说明我们能通过上面隐式的Intent找到对应的Service,并且我们还可以获取将要启动的Service的package信息以及类型。然后我们需要将根据得到的Service的包名和类名,构建一个ComponentName,从而设置intent要启动的具体的组件信息,这样intent就从隐式变成了一个显式的intent。然后我们可以将该显式的intent传递给bindService方法去启动服务。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private Intent getExplicitIapIntent() {  
  2.         PackageManager pm = mContext.getPackageManager();  
  3.         Intent implicitIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");  
  4.         List<ResolveInfo> resolveInfos = pm.queryIntentServices(implicitIntent, 0);  
  5.   
  6.         // Is somebody else trying to intercept our IAP call?  
  7.         if (resolveInfos == null || resolveInfos.size() != 1) {  
  8.             return null;  
  9.         }  
  10.   
  11.         ResolveInfo serviceInfo = resolveInfos.get(0);  
  12.         String packageName = serviceInfo.serviceInfo.packageName;  
  13.         String className = serviceInfo.serviceInfo.name;  
  14.         ComponentName component = new ComponentName(packageName, className);  
  15.         Intent iapIntent = new Intent();  
  16.         iapIntent.setComponent(component);  
  17.         return iapIntent;  
  18.     }  




4.AIDL(Android Interface Definition Language)


  (1).使用AIDL设计远程接口(Designing a Remote Interface Using AIDL)

由于每个应用程序都运行在自己的进程空间,并且可以从应用程序UI运行另一个服务进程,而且经常会在不同的进程间传递对象。在Android平台,一个进程通常不能访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的通过进程边界。

通过代码来实现这个数据传输过程是冗长乏味的,Android提供了AIDL工具来处理这项工作。

AIDL (Android Interface Definition Language)是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。

AIDL IPC机制是面向接口的,像COMCorba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。



(2).使用AIDL实现IPC(Implementing IPC Using AIDL)


使用AIDL实现IPC服务的步骤是:

1.           创建.aidl 文件- 该文件(YourInterface.aidl )定义了客户端可用的方法和数据的接口。
2.       项目中加入你所定义的YourInterface .aidl文件
3.         实现接口-AIDL编译器从AIDL接口文件中利用Java语言创建接口, 该接口有一个继承的命名为Stub的内部抽象类(并且实现了一些IPC调用的附加方法),我们要做的就是创建一个继承于YourInterface.Stub的类并且实现在.aidl文件中声明的方法。
4.         向客户端公开接口-如果是编写服务,应该继承Service并且重载Service.onBind(Intent) 以返回实现了接口的对象实例


创建.aidl文件(Create an .aidl File)

AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。
a. 必须导入所有非内置类型,哪怕是这些类型是在与接口相同的包中。
b. 如果有自定类需要使用,则需要实现 Parcelable protocol 
c. 对于非基本数据类型,(即Parcelable类型)需要有方向指示,包括in、out和inout
最终判断标准是:server是否在此变量中返回处理数据。
如果client不需要传输数据给server,client只需要处理经过server处理过后的数据, 那么 client 和 server 都为 out 
如果client只需要传输数据给server,而不需要处理返回的数据, 那么client和server都为 in
如果client需要传输数据给server,而且需要处理返回的数据, 则client和server都为 inout

实现接口(Implementing the Interface)
       a.AIDL 生成了与.aidl 文件同名的接口,如果使用Eclipse 插件,AIDL 会做为编译过程的一部分自动运行(不需要先运行AIDL 再编译项目),如果没有插件,就要先运行AIDL
      b.生成的接口包含一个名为Stub的抽象的内部类,该类声明了所有.aidl中描述的方法,Stub还定义了少量的辅助方法,尤其是asInterface(),通过它或以获得IBinder(当applicationContext.bindService()成功调用时传递到客户端的onServiceConnected())并且返回用于调用IPC方法的接口实例
       c.要实现自己的接口,就从 YourInterface.Stub 类继承,然后实现相关的方法.
       d.这里是实现接口的几条说明:
       不会有返回给调用方的异常
       *  默认IPC 调用是同步的。如果已知IPC 服务端会花费很多毫秒才能完成,那就不要在Activity View 线程中调用,否则会引起应用程序挂起(Android 可能会显示“应用程序未响应”对话框),可以试着在独立的线程中调用。
       * AIDL 接口中只支持方法,不能声明静态成员。

向客户端暴露接口(Exposing Your Interface to Clients)
在完成了接口的实现后需要向客户端暴露接口了,也就是发布服务,实现的方法是继承  Service ,然后实现以 Service.onBind(Intent) 返回一个实现了接口的类对象。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class RemoteService extends Service {  
  2. ...  
  3. @Override  
  4.     public IBinder onBind(Intent intent) {  
  5.           
  6.         return mBinder;  
  7.     }  
  8.   
  9.     /** 
  10.      * The IRemoteInterface is defined through IDL 
  11.      */  
  12.     private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {  
  13.         public void registerCallback(IRemoteServiceCallback cb) {  
  14.             //注册操作  
  15.         }  
  16.     };  
  17.   
  18. }  


使用可打包接口传递参数Pass by value Parameters using Parcelables

如果有自定义类想要能过AIDL 在进程之间传递,这一想法是可以实现的,必须确保这个类在IPC 的两端的有效性(同包同名),通常的情形是与一个启动的服务通信。
这里列出了使类能够支持Parcelable 4 个步骤:

1.         使该类实现Parcelabel接口。

2.         实现public void writeToParcel(Parcel out) 方法,以便可以将对象的当前状态写入包装对象中。

3.         增加名为CREATOR的构造器到类中,并实现Parcelable.Creator接口。

4.           最后,但同样重要的是,创建AIDL 文件声明这个可打包的类(见下文),如果使用的是自定义的编译过程,那么不要编译此AIDL 文件,它像C 语言的头文件一样不需要编译。 AIDL 会使用这些方法的成员序列化和反序列化对象。
 


     客户端访问服务,调用对应接口,实现操作

 
这里给出了调用远端接口的步骤:
1.           加入.aidl文件,声明明.aidl 文件中定义的接口类型的变量。

2.         实现ServiceConnection

3.         调用Context.bindService(),传递ServiceConnection的实现

4.         ServiceConnection.onServiceConnected()方法中会接收到IBinder对象,调用YourInterfaceName.Stub.asInterface((IBinder)service)将返回值转换为YourInterface类型

5.         调用接口中定义的方法。应该总是捕获连接被打断时抛出的DeadObjectException异常,这是远端方法唯一的异常。

6.         调用Context.unbindService()断开连接

priv
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.  ServiceConnection mConnection = new ServiceConnection() {  
  2. ublic void onServiceConnected(ComponentName className, IBinder service) {  
  3.       
  4.     IRemoteService mService = IRemoteService.Stub.asInterface(service);  
  5.             try {  
  6.             mService.registerCallback(mCallback);  
  7.     } catch (RemoteException e) {  
  8.         // so there is no need to do anything here.  
  9.     }  
  10. }  
  11.   
  12. public void onServiceDisconnected(ComponentName className) {  
  13.     mService = null;  
  14. }}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值