Android多进程机制(一)IPC基础概念
Android IPC 基础概念
多进程应用场景
- 应用自身需要采用多进程模式来实现。
- 有些模块需要运行在单独的线程中。
- 需要加大一个应用可使用的内存,通过多进程来获取多份内存空间。
//获取应用限制的内存大小和进程限制的内存大小
//我设备测试的是 heapGrowthLimit: 256m ,heapSize: 512m
ActivityManager activityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapGrowthLimit = activityManager.getMemoryClass();// 单个应用可用最大内存
int heapSize = activityManager.getLargeMemoryClass();//单个进程可用的最大内存
Log.d(TAG, "heapGrowthLimit: " + heapGrowthLimit + "\nheapSize: " + heapSize);
- 需要向其他应用获取数据,也就是跨进程访问数据。
如何跑在同一进程
- 两个应用能跑在同一进程的前提条件是具有相同的ShareUID和签名。
- 具有相同ShareUID并且签名相同的两个应用,可以共享对方私有数据,比如data目录、组件信息等。
- 如果ShareUID相等、签名相同且跑在同一进程中,那么除了能共享data目录、组件信息等,还能共享内存数据。
开启多进程的方式
使一个应用开启多进程模式,有以下两种方式:
- AndroidMenifest中指定android:process属性。
- 通过JNI在native层去fork一个新进程。
第二种不常用,暂时只看第一种。AndroidMenifest中没有给某个组件指定process属性时默认运行默认进程,默认进程的名字为程序包名。如果要指定,有两种方式。
android:process=":jtt"
android:process="com.utte.test.jtt"
两种方式还是有区别的:
- 第一种分号的意思是进程名前面加上当前包名,进程名为"com.utte.test:jtt"。
- 第二种的进程名直接就是属性值,进程名为"com.utte.test.jtt"。
- 第一种属于当前应用的私有进程,其他应用的组件不能与其跑在同一进程中。
- 第二种为全局进程,其他应用可通过ShareUID的方式与其跑在同一进程中。
多进程运行机制
Android为每个应用也就是说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,就导致在不同的虚拟机中访问同一个类的对象时会产生多份副本。
所有运行在不同进程中的四大组件都不能通过共享内存来共享数据,所以会造成以下影响:
- 静态成员和单例模式完全失效
会分配不同虚拟机,所以会产生多个对象副本。
- 线程同步机制完全失效
不同进程中的对象不是同一个对象,所以加锁达不到效果。
- SharePreferences的可靠性降低
SharePreferences不支持两个进程同时进行写操作,会导致一定几率数据丢失,SharePreferences底层是通过读写XML来实现的,并且内存中会有缓存。
- Application会多次创建
当一个组件跑在新进程中时,系统会在创建新进程时同时分配独立的虚拟机,这个过程是一个应用启动的过程,相当于把应用重新启动了一次,自然Application也会重新创建。
运行在两个进程中的两个组件会拥有独立的虚拟机、独立的Application、独立的内存空间。
它们也就相当于是两个应用拥有相同的ShareUID和签名,但不跑在同一进程中,它们可以访问对方数据,但是不能通过共享内存的方式。虽然不能共享内存了,但是还有很多方式可以实现跨进程的数据交互。
多进程通信的主要方式
- Binder
- Bundle
- 文件共享
- AIDL
- Messenger
- ContentProvider
- Socket
Bundle、文件共享、Socket比较简单,也基本都使用过,其他的都与Binder有关,包括ContentProvider,它的底层其实也是Binder。
序列化
我们传输的数据必须能够被序列化,比如基本类型、实现了Parcelable接口的对象、实现Serializable接口的对象以及Android支持的一些特殊对象。
Serializable接口
通过Serializable使一个对象实现序列化:
- 类实现Serializable接口
- 声明serialVersionUID (非必需)
类定义:
public class Book implements Serializable {
private static final long serialVersionUID = 199898L;
\\...
}
序列化使用ObjectOutputStream.writeObject(),反序列化使用ObjectInputStream.read()。
序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才可以被正常的反序列化。如果不手动指定serialVersionUID,系统就会根据类信息计算hash值赋值给serialVersionUID。这样的话如果在序列化后对类进行了一些更改,在没有指定serialVersionUID的情况下,会反序列化失败报异常,如果指定了,在非毁灭性改变的前提下,程序会尽可能最大限度的恢复数据。
Parcelable接口
更详细的内容可以看这篇博客,Android中Serializable和Parcelable序列化对象详解。
Parcelable的用法如下,需要实现Parcelable序列化、反序列化、描述三个逻辑。
public class User implements Parcelable {
private boolean isMale;
private Book mBook; //Serializable对象
private Bag mBag; //Parcelable对象
public User(boolean isMale, Book book, Bag bag) {
this.isMale = isMale;
mBook = book;
mBag = bag;
}
//描述
@Override
public int describeContents() {
return 0;
}
//序列化
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(isMale ? 1 : 0);
out.writeSerializable(mBook);
out.writeParcelable(mBag, 0);
}
//反序列化
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];
}
};
//给反序列化创建对象调用
private User(Parcel in) {
isMale = in.readInt() == 1;
mBook = (Book) in.readSerializable();
mBag = in.readParcelable(Bag.class.getClassLoader());
}
}
- Parcel为内部包装了可序列化的数据。
- writeToParcel()的flag如果为1就表示当前对象需要作为返回值返回,不能立即释放,0表示不需要,几乎所有情况都是0。
- describeContents()返回0表示不含文件描述符,存在文件描述符时返回1,几乎所有时候都应该返回0。
获取到文件描述符可以完成所有文件相关的操作,因为作用大,所以为了防止泄露,需要禁止在Bundle传输Parcel时包含文件描述符,如果通过Parcel中包含ParcelFileDescriptor的Bundle使用时就会抛出IllegalArgumentException。这个值是在系统内部进行安全保护所使用的,其他情况下填0即可。
Serializable和Parcelable比较
Serializable | Parcelable |
---|---|
使用比较简单 | 使用稍微麻烦 |
IO流形操作,性能较低 | 基于内存操作,效率高 |
Parcelable主要用于内存序列化。Serializable主要用于序列化到存储设备或网络传输。