关键词:IPC 方式 / 共享文件 / ContentProvider / Messenger / Intent + extras / Socket / Bundle
本次笔记梳理了所有可以进行跨进程通信的常见方式,包括使用 Bundle、使用文件共享、使用 Messenger、使用 AIDL、使用 ContentProvider、使用 Socket。
1. 使用 Bundle #
- 是一种最简单的进程间通信;
- 用来直接传递数据,在 Bundle 中附加我们需要传输给远程进程的信息并通过 Intent 发送出去,我们传输的数据也应该被序列化;
- 四大组件中的 Activity / Service / Receiver 都支持在 Intent 中传递 Bundle 数据;
- Bundle 实现了 Parcelable 接口,可以方便地在不同进程间传输;
- A 进程在进行一个计算,计算完成之后要启动 B 进程中的一个组件并把计算结果传递给 B 进程,但是这个计算结果不支持写入 Bundle 中,无法通过 Intent 来传输怎么办?
最简单方案:将需要在 A 中计算的任务转移到 B 进程的后台 Service 中去执行,这样不断避免了进程间通信的问题,还只用了很小的代价。
2. 使用文件共享 #
- 两个进程通过读 / 写同一个文件来交换数据,A 把数据写入文件,B 通过读取这个文件来获取数据;
- Android 系统基于 Linux,使得其并发读 / 写文件可以没有限制的进行,甚至两个线程同时对同一个文件进行写操作都可以,尽管会出现问题;
- 可以序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象;
- 文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且妥善处理并发读写的问题;
- 不建议在进程间通信中使用 SharedPreferences,SharedPreferences 是 Android 中提供的轻量级存储方案,通过键值对的方式来存储数据,在底层上采用 XML 文件来存储键值对,目录位于 /data/data/package_name/shared_prefs 目录下,属于文件的一种,在多线程模式下,面对高并发的读 / 写访问会丢失数据,因为有缓存策略,读写变得不可靠;
3. 使用 Messenger #
- 是一种轻量级的 IPC 方案,底层实现是 AIDL;
- 可以在不同的进程中传递 Message 对象,信使;
- 对 AIDL 进行了封装
- 一次只处理一个请求,在服务端不用考虑线程同步的问题,服务端不存在并发执行的情形;
- 在 Messenger 中进行数据传递必须将数据放到 Message 中,而 Messenger 和 Message 实现了 Parcelable 接口,因此可以跨进程传输;
- Message 中能能使用的载体只有 what、arg1、arg2、Bundle 以及 replyTo;
- Messenger 以串行的方式处理客户端发来的消息;服务端一个一个的处理,不适合大量的并发请求;
- 实际上作用主要是为了传递消息,不适合跨进程调用服务端的方法(这时候需要考虑使用 AIDL)
4. 使用 AIDL #
- 服务端
首先创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL 接口即可; - 客户端
首先绑定服务端的 Service,绑定成功之后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,接着就可以调用 AIDL 中的方法了; - AIDL 接口的创建
创建一个 .aidl 文件,里面声明一个接口与若干个接口方法,AIDL 的接口只支持方法,不支持声明静态变量,有别于传统的接口 - 远程服务端 Service 的实现
实现 AIDL 的接口,首先要创建一个 Service,创建一个 Binder 对象,并在 onBinder 中返回它。AIDL 方法是在服务端的 Binder 线程池中执行的,在 AIDL 方法中处理线程同步; - 客户端的实现
首先绑定远程服务,绑定成功之后将服务端返回的 Binder 对象转换成 AIDL 接口,然后就可以通过这个接口去调用服务端的远程方法了;(AIDL 中无法使用普通接口)
[ 还有几点需要知道 ]
- 对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么 AIDL 中自定义对象都必须要实现 Parcelable 接口的原因;
- 虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们底层的 Binder 对象是同一个;
- 客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端线程会被挂起,服务器端方法不能执行耗时的方法(如果是客户端是 UI 线程的话),防止 ANR;
- 客户端的 onServiceConnected 和 onServiceDisconnected 方法都运行在 UI 线程中,所以也不可以在它们里面直接调用服务端的耗时方法;
5. 使用 ContentProvider #
- 专门用于不同应用之间进行数据共享的方式,天生适合进程间通信;
- 和 Messenger 一样,ContentProvider 的底层实现同样也是 Binder,由此可见,Binder 在 Android 系统中很重要;
- 系统封装,无需关心底层细节即可轻松实现 IPC;
- 系统内置了很多 ContentProvider 比如通信录信息、日程表信息等,跨进程访问只需要通过 ContentResolver 的 query、update、insert 和 delete 方法即可;
- 六个抽象方法:onCreate / query / update / insert / delete / getType ;
- ContentProvider 主要以表格的形式来组织数据;也支持文件数据比如,图片、视频等;
- 系统提供的 MediaStore 功能就是文件类型的 ContentProvider;
- query / update / insert / delete 四大方法存在多线程并发访问,此方法内要做好线程同步;
- ContentProvider 支持自定义调用,通过 ContentProvider 的 Call 方法和 ContentResolver 的 Call 方法;
6. 使用 Socket #
- Socket 被称为 “套接字” ,分为
流式套接字
和用户数据报套接字
; - 流式套接字对应着网络中传输控制层的 TCP 协议;
- 用户数据报套接字对应着网络中传输控制层的 UDP 协议;
- Socket 本身可以支持传输任意字节流,实现信息的传输,是一种 IPC 方式;
- 实际上,通过 Socket 不仅仅能实现进程间的通信,还可以实现设备之间的通信(前提是这些设备之间的 IP 地址相互可见);
7. IPC 各方式的优缺点和适用场景
/ 名称 / | / 优点 / | / 缺点 / | / 适用场景 / |
---|---|---|---|
/ Bundle / | / 简单易用 / | / 只能支持 Bundle 支持的数据类型 / | / 四大组件之间的进程间通信 / |
/ 文件共享 / | / 简单易用 / | / 不适合高并发场景,并且无法做到进程间的即时通信 / | / 无并发访问情形,交换简单的数据,实时性不高的场景 / |
/ AIDL / | / 功能强大,支持一对多并发通信,支持实时通信 / | / 使用稍复杂,需要处理好线程同步 / | 一对多通信且有 RPC 需求 / |
/ Messenger / | / 功能一般,支持一对多串行通信,支持实时通信 / | / 不能很好地处理高并发情形,不支持 RPC,数据通过 Message 进行传输,因此只能传输 Bundle 支持的数据类型 / | / 低并发的一对多即时通信,无 RPC 需求,或者无要返回结果的 RPC 需求 / |
/ ContentProvider / | / 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作 / | / 可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作 / | 一对多的进程间的数据共享 |
/ Socket / | / 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 / | / 实现细节稍微有点繁琐,不支持直接的 RPC / | / 网络数据交换 / |
End.
Note by HF.
Learn from 《Android 开发艺术探索》