1 Android IPC 简介
IPC是Inter-Process Communication 的缩写,意为进程间通讯或跨进程通信,指两个进程之间进行数据交换的过程。一个进程可以包含多个线程,最简单的情况下,一个进程可以只有一个线程,即主线程(Android的UI线程)。
IPC机制并不是Android独有的,任何一个操作系统都需要有相应的IPC机制,比如Windows上可以通过剪贴板、管道和邮槽等来进行进程间通信;Linux上可以通过命名管道、共享内容、信号量等来进行进行进程间通信。Android中最具特色的通信方式就是Binder,还支持Socket,通过Socket可以实现任意两个终端之间的通信,同一个设备的两个进程也可以通过Socket实现。
IPC的使用场景:只有在多进程的时候才会进行进程间通信
- 一个进程需要多进程模式实现,比如有些模块由于特殊原因需要运行在单独的进程中、为了加大一个应用可使用的内存所以需要通过多线程来获取多份内存空间。
- 当前应用需要向其他应用获取数据。
2 Android的多进程模式
Android 通过给四大组件指定android:process属性,即可以开启多线程。
2.1 开启多线程模式
在Android中使用多进程只有一种方法:给四大组件在AndroidManifest中指定android:process属性,value值就是指定的进程名称,无法为一个线程或类等指定其运行时所在的进程。
通过shell命令查看:adb shell ps或者adb shell ps | grep com.gqq.xxx.xxx(包名) 查看运行的进程信息:
注意: android:process值::remote
指在当前进程名前加上当前包名,可以理解为相对名称,属于当前应用的私有进程,其他应用组件不可以和它跑在同一个进程中。不以:
开头的,是一种完整的命名方式,此进程属于全局进程,其他应用通过ShareUID的方式可以和它跑在同一个进程中。
Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据,如果两个应用通过ShareUID跑在同一个线程中,需要应用有相同的ShareUID并且签名相同,跑在同一个线程,除了共享data目录、组件信息,还可以共享内存数据。相当于一个应用的两部分。
2.2 多进程模式的运行机制
Android 为每一个应用分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致在不同的虚拟机上访问同一个类的对象会产生多个副本。比如不同进程对静态变量等的修改,不会影响其他进程,那么运行在不同进程的四大组件通过内存共享数据,就会共享失败。
总体,多进程会带来的几方面的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
不在一块内存,不同进程锁的不是同一个对象。 - SharePreference的可靠性下降
SharePreference的底层实现时通过读/写XML文件来实现的,并发读/写都有可能出现问题,所以不支持两个进程同时执行读写操作,否则会导致一定机率的数据丢失。 - Application会多次创建
创建一个进程会创建一个虚拟机,也就是启动一个应用,自然会创建新的Application。运行在同一个进程中的组件属于同一个虚拟机和同一个Application以及分配同一块内存。
通过跨进程通信,可以实现数据交互,实现跨进程通讯的方法:
- Intent传递数据
- 共享文件和SharePreference
- 基于Binder的Messager和AIDL
- Socket
3. IPC 基本概念介绍
主要三个概念,帮助更好的理解进程间通信。
- Serializable 接口
- Parcelable 接口
- Binder
Serializable 和 Parcelable接口可以完成对象的序列化,用于通过Intent和Binder传递数据,需要把对象持久化到存储设备上或通过网络传输给其他客户端,可需要通过Serializable完成对象的持久化。
3.1 Serializable 接口
Serializable是java的一个序列化的空接口,提供标准的序列化和反序列化操作,序列化的类需要实现Serializable接口及声明serialVersionUID,而serialVersionUID不是必需的,但是会影响反序列化的结果。serialVersionUID是静态成员变量,不参与序列化过程。反序列化时serialVersionUID与序列化时的serialVersionUID一致,则可以正常序列化。不指定serialVersionUID,如果反序列化时当前类发生了变化,比如增删了某些成员变量,会重新计算当前类hash值并赋与serialVersionUID,导致与序列化时的serialVersionUID不一样,会造成反序列化失败。指定serialVersionUID会在极大程度上避免反序列化的失败,但是如果类发生了结构性的变化,比如类名和成员变量的类型等,还是会反序列化失败,因为无法还原新结构的对象。
// 序列化过程
User user = new User(1, "gqq", false);
ObjectOutputStream out = ObjectOutputStream(new FileOutputStream(file));
out.writeObject();
out.close();
// 反序列化过程
ObjectInputStream in = ObjectInputStream(new FileInputStream(file));
User user = in.readObject();
in.close();
注意:静态成员变量属于类不属于对象,不参与序列化;用transient关键字标记的成员变量不参与序列化。
继承自Serializable的默认序列化过程是可以改变的,可以重写系统的序列化writeObject()
和反序列化方法readObject()
。
3.2 Parcelable接口
Parcelable 是Android提供的一种序列化接口,接口需要实现序列化、反序列化、内容描述。
public class UserModelParcel implements Parcelable{
private int userId;
private String userName;
private boolean isMale;
private UserParcel userParcel;
// 反序列化方法
protected UserModelParcel(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 0;
userParcel = in.readParcelable(UserParcel.class.getClassLoader());
}
public UserModelParcel(int userId, String userName, boolean isMale, UserParcel userParcel) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
this.userParcel = userParcel;
}
// 序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeByte((byte) (isMale ? 1 : 0));
dest.writeParcelable(userParcel, flags);
}
// 内容描述
@Override
public int describeContents() {
return 0;
}
// 反序列化
public static final Creator<UserModelParcel> CREATOR = new Creator<UserModelParcel>() {
@Override
public UserModelParcel createFromParcel(Parcel in) {
return new UserModelParcel(in);
}
@Override
public UserModelParcel[] newArray(int size) {
return new UserModelParcel[size];
}
};
总结: Serializable 是java的序列化接口,使用简单但是开销比较大,序列化和反序列化都涉及到大量的I/O操作,效率相对较低。Parcelable是Android提供的序列化方法,更适用于Android平台,效率很高,但是使用起来比较麻烦。Parcelable主要用在内存序列化上,序列化存储设备或将序列化后的对象通过网络传输建议使用Serializable。
再贴一遍
参考 Demo