Case: /Users/lucas/AndroidStudioProjects/aidldemo-master
一:操作系统
从操作系统原理去看,进程通信主要有三个方法:共享存储、消息传递、管道通信。
二:安卓中的IPC
进程间通信的几种方式:Intent(Bundle),文件共享,Messenger,AIDL,ContentProvider,Socket等。
- Bundle实现了Parcelable接口
- 优点:简单易用
- 缺点:只能传递Bundle支持的数据类型
- 使用场景:四大组件间的进程通讯
- 文件共享
- 优点:简单易用
- 缺点:不适合高并发的场景,不能做到即使通讯
- 使用场景:无并发访问的情景,简单的交换数据,实时性要求不高
- AIDL
- 优点:功能强大,支持一对多并发通讯,支持实时通信
- 缺点:一定要处理好线程同步的问题
- 使用场景:一对多进行通讯,有RPC(远程过程调用协议)的需求
- Message
- 优点:功能一般,支持一对多串行通讯,支持实时通信
- 缺点:不能更好的处理高并发场景,不支持RPC,数据通过Message进行传输,因此只能支持Bundle支持的数据类型
- 使用场景:低并发的一对多的实时通讯,没有RPC的需求或者说没有返回结果的RPC
- ContentProvider
- 优点:主要用于数据访问,支持一对多的并发数据共享
- 缺点:受约束,主要支队数据源的曾删改查
- 使用场景:一对多的数据共享
- Socket(套接字)
- 优点:功能强大,通过读写网络传输字节流,支持一对多的并打的实时通讯
- 缺点:不支持直接的RPC
- 使用场景:万络数据的交换
- BroadcastReceiver
- 优点:操作简单,支持一对多实时通信
- 缺点:只支持数据单向传递,效率低且安全性不高
- 使用场景:一对多的低频单向通信
Android之所以选择Binder,有2个方面原因
- 安全,每个进程都会被Android系统分配UID和PID,不想传统的在数据里加入UID,这就让那些恶意进程无法直接和其他进程通信,进程间通信的安全性得到提升
- 高效,像Socket之类的IPC每次数据拷贝都需要2次,而Binder只需要一次,在手机上这种资源紧张的情况下很重要
2.1 AIDL
AIDL
是Android
中实现跨进程通信(Inter-Process Communication
)的一种方式。AIDL
的传输数据机制基于Binder
,Binder
对传输数据大小有限制, 传输超过1M的文件就会报android.os.TransactionTooLargeException
异常(注1),一种解决办法就是使用匿名共享内存进行大文件传输。
2.1:共享内存简介
共享内存是进程间通信的一种方式,通过映射一块公共内存到各自的进程空间来达到共享内存的目的。
对于进程间需要传递大量数据的场景下,这种通信方式是十分高效的,但是共享内存并未提供同步机制,一个进程写共享内存时,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们需要类似信号量机制来同步对共享内存的访问。
Android
中的匿名共享内存(Ashmem)是基于Linux
共享内存的,借助Binder
+文件描述符(FileDescriptor
)实现了共享内存的传递。它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制。相对于Linux
的共享内存,Ashmem对内存的管理更加精细化,并且添加了互斥锁。Java
层在使用时需要用到MemoryFile
,它封装了native
代码。Android
平台上共享内存通常的做法如下:
- 进程A通过
MemoryFile
创建共享内存,得到fd(FileDescriptor
) - 进程A通过fd将数据写入共享内存
- 进程A将fd封装成实现
Parcelable
接口的ParcelFileDescriptor
对象,通过Binder
将ParcelFileDescriptor
对象发送给进程B - 进程B获从
ParcelFileDescriptor
对象中获取fd,从fd中读取数据
2.2:AIDL使用方法
服务端:1.创建接口,2.定义binder,实现接口,3.创建服务,返回binder;
客户端:1.绑定服务,2.实现ServiceConnection绑定监听,3.在绑定成功的回调中,将IBinder转换成AIDL的接口代理对象。
客户端和服务端绑定成功后,就可以通过AIDL的接口代理对象,就像直接调用本地方法一样,调用服务端的方法了。需要注意的是,AIDL间传递的对象要实现Parcelable接口。
2.3:AIDL的回调
原理:和常用的callback接口是一样的,只不过aidl的callback回调是在两个进程内。
服务器:ITaskBinder.Stub() + callback接口
客户端:定义一个calback的具体实现,拿服务端service ,里面register这个callback,然后服务端ITaskBinder.Stub() ->里面callback.xx(),于是调用到客户端定义的callback实现
在aidl文件中,通过oneway修饰,申明方法为异步
2.4:AIDL的底层:binder
两者关系:
AIDL定义的接口,它除了是一个接口以外,它还是一个Binder对象,支持在接口和Binder之间相互转换(asBinder(), asInterface())。创建了AIDL接口后,系统会自动生成其对应的Binder类,它继承了IInterface, 内部有一个静态抽象类Stub和Stub内部的Proxy类。其中Stub继承了Binder类,所以AIDL中的Stub即为一个Binder对象。
binder:
从代码的角度来说,Binder 是 Android 系统源码中的一个类,它实现了 IBinder 接口;
从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式;
从 Android Framework 角度来讲,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager 等等)和相应 ManagerService 的桥梁;
从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以和服务端进行通信,这里的服务包括普通服务和基于 AIDL 的服务。
AIDL的作用是实现跨进程通讯使用方法也非常的简单,他的设计模式是典型的C/S架构。客户端通过绑定服务端,获取了服务端的IBinder对象,这个IBinder对象,就是代理模式中的那个代理(或者说是c语音中的指针,指向了服务端),然后客户端通过这个代理对象,就可以通过transact方法,向服务端发送数据,实现了进程间的通讯。
2.1 Intent
使用例子:
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.putData... //放一些你需要传递的数据
ComponentName componentName = new ComponentName("com.lucas.test", "com.lucas.test.Main2Activity");//要跳转的包名以及类名
intent.setComponent(componentName);
startActivity(intent);
弊端:Intent 只能传递简单的对象,对象必须实现Pacelable接口序列化。
三:(补充)Binder数据限制
Binder传输数据限制原因:
- 1.每个进程中会有多个Binder线程,而这些Binder线程又共享创建进程的时候开辟的1M-8K的内存空间,所以在每个Binder线程传输数据的时候,不可能传递1M-8K;
- 2.另外开辟的这部分内存空间,还会有一些堆栈以及代码块等占用部分内存。
注意
- 注1:普通的由 Zygote fork 而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
这个限制定义在frameworks/native/+/4d2f4bb/libs/binder/ProcessState.cpp
类中,如果传输数据超过这个大小,系统就会报错,因为Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用的。
参考
一道面试题:使用AIDL实现跨进程传输一个2M大小的文件:https://juejin.cn/post/6990379493235884062
Android intent 传递数据的大小限制:https://blog.csdn.net/u011033906/article/details/117276922
Android 跨进程通信-(十)Binder机制传输数据限制—罪魁祸首Binder线程池:https://blog.csdn.net/nihaomabmt/article/details/116701810