Andorid系统中的一种进程间通信方式
Android IPC
序列化
Serializeble 是 java 的序列化方式,Parcelable 是 Android 特有的序列化方式;
Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。
Parcel 提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel 可以从这块共享内存中读出字节流,并反序列化成对象
一般在保存数据到本地或者网络传输时建议使用 Serializable 即可,虽然效率差一些,适合数据持久化。
而在运行时数据传递时建议使用 Parcelable,比如 Intent,Bundle 等,Android 底层做了优化处理,效率很高。
Messenger
底层是AIDL,对AIDL做了简单的封装,串行处理数据,因为用到了Handler
Bundle
Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输。Acitivity、Service、Receiver 都是在Intent中通过Bundle来进行数据传递。
文件共享
读写文件,SharedPreference 底层也是文件XML,线程不安全,进程不安全
ContentProvider
ContentProvider是Android中提供的专门用于不同应用间数据交互和共享的组件。ContentProvider实际上是对SQLiteOpenHelper的进一步封装,以一个或多个表的形式将数据呈现给外部应用,通过Uri映射来选择需要操作数据库中的哪个表,并对表中的数据进行增删改查处理。ContentProvider其底层使用了Binder来完成APP进程之间的通信,同时使用匿名共享内存来作为共享数据的载体。ContentProvider支持访问权限管理机制,以控制数据的访问者及访问方式,保证数据访问的安全性。
Linux IPC
copy_from_user:将用户空间的数据拷贝到内核空间。
copy_to_user:将内核空间的数据拷贝到用户空间。
用户空间 –> 内核缓存区 –> 用户空间,需要2次数据拷贝,这样效率不高。
Binder通信原理
- Binder驱动在内核空间创建一个数据接收缓存区B
- 内核空间创建内核缓存区A,接收数据的进程有用户空间地址C
- 建立A与B,B与C的映射关系,相当于A与C有映射关系,所以A中数据更新会直接反映到C
整个过程只使用了一次拷贝
Android Binder 原理
在性能(mmap一次拷贝),稳定性(C/S架构),安全性(识别UID,只暴露Client),符合面向对象等特征,所以android采用了Binder
1 | Java Binder (fw层) |
基于CS
Client
—查询服务—> ServiceManager
<—注册服务— Server
即:Client
—间接使用服务—> Server
以MediaPlayer举例
1 | MediaPlayer.java (java fw层) |
从MediaServer的入口函数开始:
1 | //frameworks/av/media/mediaserver/main_mediaserver.cpp |
1 | //frameworks/native/libs/binder/ProcessState.cpp |
ProcessState 进程状态,进程唯一,使用了单例模式,ProcessState构造函数中 进行了open_driver和mmap
- 打开binder设备,内部调用open函数(打开Binder设备)和ioctl函数(向Binder设备传递参数),最终返回一个文件描述符fd
- mmap函数,它会在内核虚拟地址空间中申请一块与用户虚拟内存相同大小的内存,然后再申请物理内存,将同一块物理内存分别映射到内核虚拟地址空间和用户虚拟内存空间,实现了内核虚拟地址空间和用户虚拟内存空间的数据同步操作,也就是内存映射。
1 | //frameworks/native/libs/binder/IServiceManager.cpp |
同样使用了单例模式
1 | //frameworks/native/libs/binder/ProcessState.cpp |
handle=0,创建了BpBinder,BpBinder和BBinder,都继承了IBinder。BpBinder是 Client端与Server交互的代理类,而BBinder则代表了Server端。BpBinder和BBinder是一一对应的, BpBinder会通过handle来找到对应的BBinder。
1 | //frameworks/native/libs/binder/include/binder/IInterface.h |
回到interface_cast,这里的INTERFACE是IServiceManager
IServiceManager
BpBinder和BBinder负责Binder的通信,而IServiceManager用于处理ServiceManager的业务
1 | //frameworks/native/libs/binder/include/binder/IServiceManager.h |
可以看出创建了BpServiceManager,其中obj是之前创建的BpBinder,最后得出defaultServiceManager()返回的就是BpServiceManager(定义在frameworks/native/libs/binder/IServiceManager.cpp中)
1 | //frameworks/native/libs/binder/IServiceManager.cpp |
可以看出最后BpBinder存于mRemote内
得出BpServiceManager的mRemote变量里面存放了BpBinder
得出BpServiceManager实现了IServiceManager,或者说BpServiceManager派生自IServiceManager,且BpBinder可以用来通信
- BpServiceManager 继承 BpInterface
且构造函数需要 BpBinder - BpServiceManager —-> BpInterface —-> IServiceManager + BpRefBase
- IServiceManager —-> IInterface
- BpBinder —-> IBinder / BBinder —-> IBinder
1 | 小结: |
关键点总结
由于Binder内存映射的空间最大只允许4M,binder的两个进程间需要传输大量的数据时就只能使用传递文件句柄fd方式。例如:图像声音数据、或者是一个对象。可以在匿名共享内存(Ashmem)中创建一块区域,源进程会得到一个相应的fd,再把这个fd使用binder传递给目的进程,就可以共享数据了。
binder驱动给每个进程分配最多4M的buffer空间(一般从Zygote孵化出来的APP默认分配 1M-8K大小,servicemanager默认分配128K),当然可以突破这个 1M-8K 的限制,可以自己手动调用open和mmap即可:(但是还是无法突破 binder_mmap() 中 SM_4M 的限制)
1 | int main(int argc,char **argv){ |
binder驱动负责远程引用和本地实体服务的转换:
binder对象即binder_node和handle即binder_ref的映射关系,由binder驱动负责
发消息需要拷贝数据,收消息使用mmap
不论是client端还是server端,实例化ProcessState之后,都会开启一个binder线程用于循环监听binder驱动发来的消息!
即startThreadPool
和joinThreadPool
方法
Client发消息:
- BpBinder::transact
- IPCThreadState::transact
//准备数据,写入Parcel类型的mOut - writeTransactionData
- IPCThreadState::waitForResponse
//发起通信,通信的数据来自全局变量 mOut,如果有收到回复,回复数据将存在 mIn 中。 - IPCThreadState::talkWithDriver
//用于包装传入的数据,把mOut的数据存入bwr(其实内部是保存的数据的地址,不是数据本身) - binder_write_read bwr;
- ioctl(bwr)
- binder_ioctl(内核层)
其实在 binder 通信中,会有多次的 copy_from_user() 和多次的 copy_to_user(),之所以说 binder只有“一次拷贝”,实际指的是只有“一次数据拷贝”。其他的拷贝都只是小数据的拷贝,例如控制命令、数据地址指针等信息。
binder_ioctl(内核层)
//APP或者Server进程开启后,会开启一个binder线程,循环获取从binder驱动发来的消息,就是通过binder_ioctl_write_read()中的binder_thread_read()来获取消息
1 | //binder_ioctl_write_read |
copy_from_user
从用户空间将 bwr 拷贝进内核空间,包装在内核空间的 bwr 中,这里的拷贝是命令拷贝,并不是真正的传输数据拷贝,这里 bwr 指向的数据只包含了:一些传入协议,相关控制命令、请求端user data数据的地址指针。所以此时的拷贝并不是真正对user data数据做拷贝,即还没有和服务端被映射的内存关联上。
binder_thread_write内部:
- copy_from_user//不过也没有真正的数据拷贝
- binder_transaction
- 在binder_transaction内又进行了copy_from_user//即真正的数据拷贝
总结:
- 从进程角度来看,可以把MPS看成是客户端Client,SM看成是服务端Server
- MPS客户端 —-BC_TRANSACTION—-> BinderDriver —-BR_TRANSACTION—-> SM服务端
- MPS客户端 <——-BR_REPLY——- BinderDriver <——-BC_REPLY——- SM服务端
- Client端和Server端分别运行在两个进程中,通过向Binder来进行通信
- BC_TRANSACTION和BR_TRANSACTION过程是一个完整的事务,BC_REPLY和BR_REPLY是一个完整的事务。
- 客户端发送完之后线程会阻塞等待返回的结果,binder驱动唤醒SM的阻塞线程,进行服务注册,拿到结果后,唤醒客户端线程最终拿到结果