最近在看《Android开发艺术探索》,其中第二章讲的是 Android 中的 IPC 相关的知识。因此,把书上的知识整理一下,记录下来,同时加深学习。
IPC 是 Inner-Process Communication 的缩写,意为进程间通信或者跨进程通信,是指两个进程间进行数据交换的过程。说到进程间通信,首先要理解什么是进程,什么是线程,这两个是截然不同的概念。
线程:CPU最小的调度单元,同时也是一种有限的系统资源。
进程:一般指一个执行单元,在PC和移动设备上指一个程序或一个应用。
一个进程可以包含多个线程,所以二者是包含关系。
IPC 机制是很多操作系统中都有的,比如 Windows可以通过剪贴板、管道和油槽等来进行进程间通信,Linux 上可以通过命名管道、共享内存、信号量等来进行进程间通信。
对于 Android 来说,最有特色的进程间通信就是 Binder,通过 Binder 可以轻松实现进程间通信。除了 Binder,Android 还支持 Socket,通过 Socket 也可以实现一个设备上的两个进程或任意两个终端之间的通信。
多进程通信的情况分为两种:
1.应用本身需要采用多进程模式来实现,或者为了加大应用可使用的内存所以需要通过多进程来获取多份内存空间;
2.当前应用需要和其他应用进行通信,实现数据交互。由于是两个应用,所以必须采用多进程通信。
开启多进程模式:
常规的:在AndroidMenifest 中通过给四大组件指定 android:process 属性即可开启。
非常规:通过 JNI 在 native 层去 fork 一个新的进程
指定 android:process 属性方式也有不同:
1.android:process=":remote"
2.android:process="com.test.ipc.remote"
第一种方式是简写,完整的进程名是 包名+":remote",这个进程属于当前应用私有的进程,其他应用的组件不能和它跑在同一进程,第二种方式开启的多进程是属于全局进程,其他应用可以通过 ShareUID 方式可以和它跑在同一进程中。
Android系统会为每个应用分配一个唯一的 UID,具有相同 UID 的应用才能共享数据。两个应用通过 ShareUID 跑在同一个进程中是有要求的,需要两个应用具有相同的 ShareUID 并且签名相同才可以。在这种情况下,他们可以互相访问对方的私有数据,比如 data 目录、组件信息等,不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,还可以共享内存数据,或者说它们看起来就是一个应用的两个部分。
通过常规方法开启的多进程,需要注意:
1.静态成员和单例模式完全失效
2.线程同步机制完全失效
3.SharedPreference 的可靠性下降
4.Application 会多次创建
原因如下:
Android 为每一个应用分配了一个独立的虚拟机,或者说为每一个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多个副本,在一个类中修改成员的值只在当前进程有效,对其他进程不会造成影响,所以静态成员和单例模式完全失效;同理,锁对象和锁全局类也无法保证线程同步,因为类都不在同一块内存,锁的都不是同一个对象;第三个是因为 SharedPreference 不支持同时去执行读写操作,否则会造成一定几率的数据丢失;第四个也是显而易见的,因为组件在一个新的进程中启动,系统要先创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程,所以 application 会多次创建。
Android 中的 IPC
要了解 Android 中的 IPC,首先要了解 Android 中的 IPC 的一些基础概念:Serializable 接口、Parcelable 接口以及 Binder。
Serializable接口和Parcelable接口完成对象的序列化过程,这样才可以通过 Intent 和 Binder 传输数据。有时需要将对象持久化存储在设备上或者通过网络传输给其他客户端,也需要使用 Serializable 接口完成对象的持久化。
使用 Serializable 接口序列化的对象需要制定 serialVersionUID,否则,反序列化时可能失败。另外,用 transient 关键字标记的成员变量不参与序列化过程。
使用 Parceable 接口序列化的对象需要实现一些方法,其中 describeContents是指内容描述功能,一般情况下这个方法都返回 0,只有当对象中存在文件描述符时,该方法才返回 1.
系统实现了 Parceable 接口的常见类有 Intent、Bundle、Bitmap 等,同时 List 和 Map 也可以序列化,前提是它们里面的每个元素都是可序列化的。
序列化接口如何选取?
Serializable 接口是 Java 中的,使用简单,但开销大,序列化和反序列化过程都需要大量 I/O 操作;Parceable 接口是 Android 中的序列化方式,更适合用在 Android 平台,缺点是使用麻烦,但效率高,是 Android 推荐的序列化方式,所以首选它。 Parceable 主要用在内存序列化上,Serializable 主要用在将对象序列化后存储到设备或通过网络传输。
Binder
Binder 实现了 IBinder 接口,从 IPC 角度说,Binder 是 Android 中的一种跨进程通信方式,可以理解为一种虚拟的物理设备,其驱动是 /dev/binder ,这在 Linux 中是没有的;从 Android Framework 角度说,Binder 是 ServiceManager 连接各种 Manager 和相应 ManagerService 的桥梁;从 Android 应用层说,Binder 是客户端和服务端通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个对象,客户端可以获取服务端提供的服务(普通服务和基于 AIDL 的服务)或数据。