IPC机制(一)——IPC基础概念
Android中的多进程
IPC学过操作系统的都应该知道,就是进程间通信或者跨进程通信。Android是基于Linux内核的移动操作系统,它的进程间通信方式并不是完全继承于Linux,它有自己的进程间通信方式。
多进程通信的主要方式
- Binder
- Bundle
- 文件共享
- AIDL
- Messenger
- ContentProvider
- Socket
开启多进程模式
Android中使用多进程只有一种方法,就是给四大组件在AndroidMenifest中指定android:process属性,除此之外没有其他办法。但有一种非常规的办法就是通过JNI在native层去fork一个新的进程。fork在linux系统上就可以尝试。
<activity>
<android:process=":remote"/>
或者
<android:process="com.lxy.ipc.remote"/>
</activity>
两种方式的区别:
以“:”开头的是一种简写的方法,完整的进程名称是com.lxy.ipc:remote。也就是在当前进程名前加上了当前的包名。且以“:”开头的进程属于当前进程的私有进程,其它应用组件不可以和它跑在同一进程中。
第二中命名方式是完整的命名方式,不会附加包名信息,属于全局进程,其它应用可以通过sharedUserID和它跑在同一进程中。
//第一个程序的menifest文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lxy.client"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="com.lxy.share">
//第二个程序的menifest文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lxy.service"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="com.lxy.share">
多进程模式的运行机制
在学操作系统的时候我们知道每一个进程的都有自己的进程控制块(PCB),android在多进程模式中,不同进程的组件有自己独立的虚拟机、Application、内存空间。
举个例子来说:
UserManager类中有一个静态变量
public static int userId = 1;
按理来说静态变量应该是被所有地方共享的,在一个进程中进行加一操作,在其他进程打印,发现结果为1。这说明多进程不是添加了一个android:process属性这么简单。每一个进程都有一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这会导致在不同的虚拟机中访问同一个类对象(静态变量属于类对象)会产生多份副本。
不同进程中的组件几乎都需要共享数据。使用多进程会造成如下几方面的问题:
- 静态成员和单例模式完全失效
分配不同的虚拟机,产生多个对象副本
- 进程同步机制完全失效
不在同一块内存中,所以锁对象和锁全局类都达不到效果
- SharedPreferences的可靠性下降
SP不支持两个进程同时执行写操作,否则会导致数据一定几率的丢失。SP的底层是通过读写XML来文件实现的,并发写/读都可能出现问题。
- Application会多次创建
创建新的进程就是要分配独立的虚拟机,所以就是启动一个新的应用的过程,重新启动就会创建新的Application。
Serializable
Serializable是java提供的一个序列化接口,是一个空接口。
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
、、、
}
序列化和反序列化过程:
//序列化
User user = new User("ldd",20);
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();
序列化就是:将对象转化为字节序列的过程
反序列化就是:将字节序列恢复为java对象
在User类中有一个变量serialVersionUID是用来辅助序列化的过程。原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够被反序列化。当我们不指定它的值时,可以让编译器根据当前类的结构去生成它的值,但是这样容易出现错误。比如,当前对象序列化后,更改了类中的变量,再反序列化的时候就会出现错误。因为更改类后会重新生成一个新值。所以一般serialVersionUID的值手动设置,类改变了反序列化也不会出错。
注意:
- 静态变量属于类不属于对象,不会参与序列化过程
- 用transient关键字标记的成员变量不参与序列化过程
Parcelable接口
实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。
public class User implements Parcelable {
private String name;
private int age;
private Book book;
public User(String name,int age){
this.name = name;
this.age = age;
}
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
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];
}
};
@Override
public int describeContents() {
return 0;
}
/**
*Parcel内部包装了可序列化的数据
*将当前对象写入序列化结构中
*flags == 1:表示当前对象需要作为返回值返回
* flags == 0:几乎所有情况都为0
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeParcelable(book,0);
}
}
序列化过程实现的工能:
- 序列化
writeToParcel(Parcel out,int flags)
- 反序列化
由CREATOR完成,内部创建了序列化对象和数组。
- 内容描述
describeContents
返回当前对象的内容描述符(FileDescriptor)。若有返回1,若无返回0。一般都返回0。
Serializable和Parcelable比较
Serializable | Parcelable |
---|---|
使用简单但开销大 | 使用复杂但效率高 |
需要大量I/O操作 | 用在内存序列化上(存储设备、网络传输) |