Android进程间通信

一、常见通信方式梳理
进程间常见的通信方式有以下几种:
1、Socket:通用接口,传输效率低,主要用在跨网络通信和本机进程间通信,传输过程需要拷贝2次数据;
2、共享内存:虽然无需拷贝,但控制复杂;
3、Binder:基于C/S模式,只需1次拷贝,安全性高。

不同的通信方式使用场景也不同:

  •    Socket:适合网络间的通信,或者效率要求不高的本机进程间通信;
  •    共享内存:适合效率要求较高的底层进程间通信;
  •    Binder:兼顾效率和安全性的进程间通信,需要返回结果;
  •    Messenger:低并发的、一对多的进程间通信,无需返回结果。

二、 Binder和AIDL
1、Binder机制浅析

Binder机制,整体上基于C/S架构实现,客户端先获取远程服务端的对象引用,通过该引用调用远程函数,然后由Binder驱动找到对应的服务端函数再执行。其设计初衷是,降低使用复杂度、改善安全性、并提高传输效率。

其中的Binder驱动,是作为一个特殊字符型设备存在,设备节点为/dev/binder,遵循Linux设备驱动模型。在驱动实现过程中,系统主要通过 binder_ioctl 函数与用户空间的进程交换数据。binder_thread_write函数用于发送请求或返回结果,而binder_thread_read函数用于读取结果。

2、AIDL
AIDL本质是系统提供的一套可快速实现Binder调用的工具/语言,其中工具也就是aidl.exe,可将aidl文件转为java文件,语言就是aidl文件使用的接口描述语言,用于.aidl文件。

1)关键类和方法

  • asInterface():AIDL接口中的一个方法,提供客户端调用。该方法可以将服务端的返回的Binder对象或BinderProxy对象,转换成客户端所需要的AIDL接口类型对象(一个Stub.Proxy实例)。
  • Proxy类:服务端在客户端进程的代理类。客户端通过Binder对象的asInterface方法获取Proxy实例,再通过该实例调用服务端的自定义方法。
  • transact()方法:运行在客户端。客户端调用具体功能方法时,需要在接口类中,通过BinderProxy类型的mRemote对象的transact方法发起远程请求,同时将当前线程挂起,直到远程请求返回才继续执行。
  • AIDL接口:继承android.os.IInterface,由客户端的aidl文件通过编译自动生成。例如IActivityManager这个接口就是由IActivityManager.aidl自动生成的(客户端和服务端都有)。
  • Stub类:Binder的实现类。服务端通过这个类来提供服务。注意是抽象类,其static方法asInterface给客户端调用,其他方法给系统调用,最终会调用到具体实现(一般在Stub的实例对象中)
  • onTransact()方法:运行在服务端的Binder线程池中,当客户端发起transact跨进程请求时,系统回调此方法处理请求。

补充,transact调用中的code参数,一般对应具体的功能,类似index,一个Binder对象使用的code值,在应用内部必须能够区分,但不同的Binder对象使用的code值可以重复。

在服务端进程中,有个Stub类,这个是Binder的实现类,服务端通过这个类来提供服务。注意该类是抽象类,其static方法asInterface给客户端调用,其他方法给系统调用,最终会调用到具体实现(一般在Stub的实例对象中), 其onTransact()方法运行在服务端的Binder线程池中,当客户端发起transact跨进程请求时,系统回调此方法处理请求)。

在客户端进程中,asInterface()是AIDL接口中的一个方法,提供客户端调用。该方法可以将服务端的返回的Binder对象或BinderProxy对象,转换成客户端所需要的AIDL接口类型对象(一个Stub.Proxy实例)。

2)代理类的设计
BinderProxy是Android系统用于跨进程通信、访问远程服务中的Binder实例对象而设计的,侧重系统调用,对用户不透明。实际上它是在native层javaObjectForIBinder方法中创建的,属于客户端进程中的对象,和Native层的BpBinder有对应关系。
形如IxxxService.Stub.Proxy这种代理类,用于跨进程通信、访问远程服务中的用户自定义方法而设计的,侧重应用层,实际上该类基本都是通过其实现的AIDL接口例如IActivityManager来调用。
对于Service组件的远程绑定,BinderProxy与Binder实例(比如IxxxService.Stub类型的变量mBinder)的映射关系,是通过BinderProxy.ProxyMap这个类维护的。而IxxxService.Stub.Proxy实例与Binder实例(IxxxService.Stub类型的变量mBinder)的映射关系,是通过Proxy类的构造函数及其成员变量mRemote维护的,这里mRemote就是mBinder在客户端进程的代理也就是BinderProxy对象。
如果我们获取到Proxy类的实例,可以调用其asBinder方法获取该实例维护的BinderProxy对象,反过来也可以通过BinderProxy实例的asInterface方法来获取Proxy对象。

三、 融合与思考
1、Binder常见使用场景解析
场景1:绑定服务,例如WallpaperService,其代理对象所需的BinderProxy实例(远程服务时),是onServiceConnected回调参数中IBinder类型的service。

场景2:系统服务,例如AMS、WMS等,其代理对象所需的BinderProxy实例,是通过ServiceManager.getService方法调用返回的对象。

场景3:间接访问核心组件,例如IWindowSession。IWindowSession的实现类是Session,不属于服务。客户端例如WindowManagerGlobal,先通过ServiceManager获取WMS的本地代理也就是IWindowManager的实例,再利用此代理的openSession方法得到Session的本地代理IWindowSession。

Binder跨进程通信的关键,是一个实现了IBinder接口的对象,该对象可用于调用远程服务的本地Binder类的asInterface方法,获得远程类的本地代理。如前所述,获得IBinder对象的途径有3种以上,包括利用ServiceConnection回调(绑定服务)直接获得;或者利用ServiceManager(系统服务)直接获得;或者通过别的代理(基于系统服务或绑定服务)对象的某些方法间接获得。

除了上面3种场景,还有一种特殊情况:IServiceManger。该类派生于android.os.IInterface,定义了用于实现具体功能的接口,例如addService、getService。

ServiceManagerNative派生于Binder,作为远程ServiceManager的本地Binder类,提供asInterface方法(对应工具自动生成的java类中的IxxService.Stub)。ServiceManagerProxy是远程ServiceManager的本地代理类,提供了各项具体功能方法,利用Parcel、IBinder和远程ServiceManager通信并返回结果(对应于工具生成的IxxService.Stub.Proxy)。

获取本地代理,首先需要得到一个IBinder接口实例,这里主要是通过BinderInternal.getContextObject()完成的,属于系统级的一个特殊处理。实际上是native层创建的。远程ServiceManager,也就是addService等功能具体的实现,最终是service_manager.c中完成。

这里可以看出,Binder通信的灵活性,不一定需要aidl文件,远端服务也不一定要包含一个Stub对象或者派生于Stub类,可以直接在native层。

AIDL机制既可以实现跨进程通信,也支持在同一进程内的调用;在同一进程时,onServiceConnected回调的入参Binder对象,就是服务端返回的Binder实例,无需转换(对于进程的判断,以及Binder对象的对应区分,例如是Binder还是BinderProxy,是在驱动里面判断的,详见binder.c中的binder_translate_handle)。

2、Binder和Parcelable接口
Binder进程通信机制帮我们解决的最大问题,就是能够通过一个代理来访问其他进程中的实例对象及其方法,其中的数据传输,大量使用了Parcel以及Parcelable接口。

如果是跨进程传输自定义数据类(可序列化的),可以使用实现了Parcelable接口的自定义数据类,同样需要对应的aidl文件。如果一个类在创建实例时,必须调用无法序列化的组件,则需要给客户端提供代理才能访问该类,也就是说,服务端的实现必须派生于Ixxx.Stub(Binder的派生类),使得客户端通过某种方式可以获取其代理。

对于功能简单的调用,例如数据访问相关功能,使用一个实现了Parcelable接口的类基本就可以了,最多是引用其他aidl接口。例如KeyEvent,用于序列化的构造函数KeyEvent(Parcel in)里面都是可以可以序列化的数据类型。但如果自定义类的功能复杂,依赖大量组件,重点是构造函数参数涉及到不属于aidl可以序列化的类时,则无法实现Parcelable接口,只能给调用者/客户端提供一个代理来访问这些功能,这时候就需要定义aidl,并且让功能实现类派生于Stub(实质上是派生于Binder类)。例如Session类,其构造函数的几个参数都不能序列化,这个是其没有实现Parcelable接口却却实现了IWindowSession.Stub的关键原因。

(相关完整且成体系的文章,可参见本人原创的开源电子书《Android系统与性能优化》,地址:https://github.com/carylake/androidnotes)

posted @ 2019-12-21 22:24  星禾  阅读(543)  评论(0编辑  收藏  举报