IPC机制
进程间通信
- 进程和线程
- 线程是CPU调度的最小单元,同时线程是一种有限的系统资源。
- 进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程。
- 多进程情况
- 一个应用出自某些原因采用多进程模式实现,比如希望通过多进程获取多份内存空间。
- 当前应用需要向其他应用获取数据。
- 某些模块有运行在单独进程中的需要。
- Android中的进程间通信方式
- Binder实现进程间通信
- Socket实现任意两个终端之间的通信或者一个设备上的两个进程间的通信。
Android中的多进程模式
-
开启多进程模式
- 给四大组件在AndroidMenifest中指定android:process属性。
- 非常规:通过JNI在native层去fork一个新的进程。
-
多进程模式的运行机制
多进程绝非仅仅是指定一个android:process属性那么简单。一般来说,使用多进程会造成以下一个方面的问题:
- 静态成员变量和单例模式完全失效。
- 线程同步机制完全失效。
- SharedPreferences的可靠性下降。
- Application会多次创建。
因为Android为每个应用/进程分配了独立的虚拟机,不同的虚拟机在内存分配上占有不同的地址空间,就导致了不同虚拟机中访问同一个对象会产生多分副本。所有运行在不同进程中的四大组件,只要通过内存来共享数据都会失败,这也是多进程带来的主要影响。
为了解决这些问题,系统提供了很多跨进程通信方法。
IPC基础概念
Serializable和Serializable接口都可以完成对象序列化的过程,序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
-
Serializable接口
Java提供的一个序列化接口,在类声明中指定一个private static final long serialVersionUID(序列化后的数据中的serialVersionUID只有和当前类的相同才能够正常的被反序列化),采用ObjectInputStream和ObjectOutputStream可以实现序列化。需要注意的是:序列化恢复的对象和原对象内容完全一样,但是两者并不是同一个对象。
serialVersionUID的工作机制:序列化时系统会把当前类的serialVersionUID写入序列化文件或者其它中介中,当反序列化的时候系统又回去检测文件中的serialVersionUID,看他是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前版本是相同的,这个时候可以成功反序列化。
另外,静态成员变量不属于类不属于对象,所以不会参与序列化过程;其次用transient关键字标记的成员变量也不参与序列化过程。
-
Parcelable接口
实现这个接口,一个类的对象就可以通过Intent和Binder传递。 系统已经为我们提供了许多实现了Parcelable接口的类,可以直接序列化,比如Intent、Bundle、Bitmap等等,同时List和Map也可以序列化,前提是它们里面每个元素都是可以序列化的。
Parcelable和Serializable的比较:
Serializable的作用是为了保持对象的属性到本地文件、数据库、网络流以方便数据传输,当然这种传输可以是进程内或者不同进程之间的,Serializable开销很大,在序列化和反序列化当中需要进行大量的I/O操作,效率不高。主要用于数据持久化和网络传输。
相较而言,Parcelable是Android中的序列化方式,效率很高,内存开销较小,主要用在内存序列上, 是为了在不同组件间以及不同Android应用间(AIDL)高效传输数据而设计,Parcelable是通过IBinder通信的消息的载体 。将对象序列化到存储设备或者网络传输也是可以的,但是过程较Serializable稍微复杂,而且因为Android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化。
-
Binder
直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC的角度看,Binder是Android中一种跨进程通信方式,BInder还可以理解为一种虚拟的物理设备,他的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework的角度说,Binder是ServiceManager连接各种Manager和响应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端可以获取服务端提供的服务或者数据,包括普通服务和基于AIDL的服务。
Android中的IPC方式
-
使用Bundle
四大组件中的三大组件Activity、Service、Receiver都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,可以方便地在不同的进程之间传输。
-
使用文件共享
由于Android系统基于Linux,使其并发读写文件可以没有限制的进行,但是也必须考虑可能出现的线程安全问题,比较适合对于数据同步要求不高的进程之间通信。
其中,对于SharedPreferences存储来说,系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,所以再多进程模式下,系统对它的读写就变得不可靠,在并发读写时有很大几率丢失数据,因此不建议在进程间通信时使用。
-
使用Messenger(略过)
Messenger可以在不同进程中传递Message对象,轻松实现数据的进程间传递。使用方法很简单,对AIDL做了封装,可以很简便地进行进程间通信。
-
使用AIDL
- 服务端创建Service监听客户端的连接请求,然后创建AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
- 客户端首先需要绑定服务端的Service,绑定成功后将服务端返回的Binder对象转换成为AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
- AIDL接口的创建与远程服务端Service的实现和客户端的实现、
-
使用ContentProvider(待补充)
-
使用Socket
可以通过Socket来实现进程间的通信,Socket也称为“套接字”,分为流式套接字和用户数据报套接字两种,分别对应了网络传输控制层中的TCP和UDP协议。通过Socket不仅仅能够实现进程间的通信,还可以实现设备间的通信,前提是这些设备之间的IP地址互相可见。
Binder连接池
Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免重复创建Service的过程,可以大大提高AIDL的开发效率。
选择合适的IPC方式
选择合适的IPC方式完成多进程的开发场景。
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的即时通信 | 无并发访问情景,交换简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 一对多通信而且有RPC(远程过程调用)需求 |
Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好地处理高并发情形,不能支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可以通过Call方法扩展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微繁琐,不支持直接的RPC | 网络数据交换 |