第 2 章 IPC 机制
本意主要讲解 Android 中的 IPC 机制。
2.1 IPC 简介
IPC 是 Inter-Process Communication 的缩写,意为进程间通信,虽然 Android 是基于 Linux 内核的操作系统,但是它的进程通信方式并不完全继承自 Linux,在 Android 中,最有特色的进程间通信方式就是 Binder 了,通过 Binder 我们可以轻松地实现进程间的通信。
2.2 Android 中的多进程模式
正常情况下,在 Android 中多进程是指一个应用中存在多个进程的情况,因此这里不讨论多个应用之间的多进程情况。
首先,在 Android 中使用多进程只有一种方法,那就是给四大组件(Activity、Service、Receiver、ContentPrivoder)在 AndroidManifest.xml 中指定 android:proces 属性。另外一种非常规的方法是通过 JNI 在 native 层 fork 一个新的进程,但这种方法属于特殊情况,这里暂时不讨论了。
下面来看一个多进程的示例:
<activity
android:name=".SecondActivity"
android:process=":remote" />
<activity
android:name=".ThirdActivity"
android:process="com.test.remote" />
这里使用了两种方式指定 android:process 的值,这两种方式是有区别的,通过“:”指定进程名是一种简单的写法,其完整名称应该为应用的包名加上 android:process 的名,在本例中为“com.test:remote”,而且这种写法表示进程属于当前应用的私有进程;ThirdActivity 中指定的进程名为完整的名称,对于不以“:”开头的进程来说,它们属于全局进程,其他应用可以通过 ShareUID 和它们跑在同一个进程中。
在 Android 中,系统会为第个应用分配一个唯一的 UID,只有具有相同 UID 且签名相同的应用才能共享数据,在这种情况下,两个应用可以互相访问对方的私有数据,如 data 目录、组件信息等。
同时,如果两个应用的 UID 和签名都相同,那么这两个应用就可以通过 ShareUID 跑在同一个进程中,这时两个应用不但可以共享私有数据,还可以共享内存数据,就像它们是同一个应用一样。
在 Android 中使用多进程会造成如下几个问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences 的可靠性下降
- Application 会多次创建
第 1 个问题是因为 Android 系统会为每个进程分配一个独立的虚拟机,这就导致了在多个进程中访问同一个类对象时会产生多个副本;第 2 个问题本质上和第 1 个问题是类似的;第 3 个问题是因为 SharedPreferences 不是线程安全的,所以并发读写 SharedPerferences 有可能会出问题;第 4 个问题还是因为系统要在创建新进程的同时分配一个独立的虚拟机,这个过程其实就是启动一个应用的过程,所以就会创建新的 Application。
针对多进程中存在的这些问题,Android 系统提供了很多跨进程通信的方法,比如通过 Intent、共享文件和 SharedPreferences 来传递数据,基于 Binder 和 Messager 和 AIDL 以及Socket 实现进程通信等。为了更好地理解 IPC 方式,我们先来熟悉一下 IPC 的基础概念。
2.3 IPC 基础概念介绍
本节主要包含三个内容:Serializable 接口、Parcelable 接口和 Binder。
如果要使用 Serializable 来实现序列化,只需要让类实现该接口并在类的声明中指定如下字段即可:
private static final long serialVersionUID = 42L;
serialVersionUID 不是必须的,但如果序列化后的数据中有这个字段,那么只有这个 serialVersionUID 和当前类中的值相同才能正常地反序列化,下面是一个简单的序列化和反序列化的示例:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
public int userId;
public String userName;
public User(int userId, String userName) {
this.userId = userId;
this.userName = userName;
}
...
private void test() throws Exception{
// 序列化
User user = new User(0, "jake");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();
}
}
Parcelable 也是一个接口,通过实现这个接口,我们可以让一个类的对象实现序列化并可以通过 Intent 和 Binder 传递,Parcelaable 和 Serializable 的区别在于,前者效率较高,但后者使用更简单,通常我们在内存序列化时使用 Parcelaable,如果要将对象持久化保存或者通过网络传输,则推荐使用 Serializable,下面是一个典型的用法:
public class User implements Parcelable {
public int userId;
public String userName;
// 反序列化
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
public User(int userId, String userName) {
this.userId = userId;
this.userName = userName;
}
protected User(Parcel in) {
userId = in.readInt();
userName = in.readString();
}
// 仅当对象中存在文件描述符时才返回 1,其他情况下都返回 0
@Override
public int describeContents() {
return 0;
}
// 序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
}
}
最后再来说说 Binder,Binder 本身很复杂,它是 Android 中的一个类,且实现了 IBinder 接口。从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式,也可以将 Binder 理解为一种虚拟的物理设备,它的设备驱动是 /dev/binder,该通信方式在 Linux 中是没有的;从 Android Framework 角度来说,Binder 是 ServiceManager 连接各种 Manager(ActivityMangager、WindowManager 等)和相应 ManagerService的桥梁;从 Androiid 应用层来说,Binder 是客户端和服务端进行通信的媒介,当 bindService() 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个对象,客户端可以获取服务端提供的服务或数据,这里的服务包括普通服务和基于 AIDL 的服务。
上面一段是书中的介绍,感觉 Binder 确实很复杂,或者说 IPC 很复杂,这一章有点看不太懂了,暂时先跳过吧,以后慢慢学。。