一、前言:
本节主要讲解 IPC 中的基本概念,Serializable 接口与 Parcelable 接口。
这两个接口可以完成对象的序列化过程,当我们需要通过 Intent 和 Binder 传输数据时就需要使用 Serializable 与 Parcelable 。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,这个时候也需要使用这两个接口来完成对象的持久化,下面我们一一来介绍。
啥叫对象的持久化?不知道的看下面一句话:
持久化一般指将数据持久化保存,如存到数据库中或写到文件中
二、Serializable 接口
Serializable 是 Java所提供的一个序列化接口,这里提到了序列化,那什么叫序列化和反序列化呢?
序列化就是将 Java 对象转换成二进制文件数据的过程。这样说可能不是很明白,举个例子:如果把一个房子比作一个对象,要把这个做房子搬到另外一个地方的话,直接挪动是不现实的,但是可以将房子拆成一个个小零件运输,到了目的地在组装成房子,这样就容易多了,这其中 “拆解” 的过程就是序列化,“重新组装” 就是反序列化。
说到这里,怎么实现序列化呢?
只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。
private static final long serialVersionUID = 51906712373232662L;
全部代码:
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 51906712373232662L;
public int userId;
public String userName;
public boolean isMale;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
}
对象的序列化和反序列化只需要采用 ObjectOutputStream 和 ObjectInputStream 即可轻松实现,如下:
// 序列化过程,就是写的过程
User user = new User(0, "Jack", true);
ObjectOutputStream out;
{
try {
out = new ObjectOutputStream(new FileOutputStream("user.txt"));
out.writeObject(user);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 反序列化过程,就是读的过程
ObjectInputStream in;
User newUser;
{
try {
in = new ObjectInputStream(new FileInputStream("user.txt"));
newUser = (User) in.readObject();
in.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
现在有个问题,serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中 serialVersionUID 只有和当前类的 serialVersionUID 相同才能够正常地被反序列化。不用它行不行,答案:不行。
serialVersionUID 的详细工作机制是这样的:序列化的时候系统会把当前的 serialVersionUID 写入序列化的文件中(也可能是其他中介),当反序列化的时候系统回去检测文件中的 serialVersionUID ,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化类的版本和当前的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了变化,这个时候是无法反序列化的,会报错。
再简单的说就是:反序列化时如果当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的 hash 值并把它赋值给 serialVersionUID ,这个时候当前类的 serialVersionUID 就和序列化的数据中心的 serialVersionUID 不一致,于是反序列化失败,程序 crash。所以,我们可以明显感觉到 serialVersionUID 的作用。
Serializable 就介绍到这里了,再加一点比较重要的知识点:静态成员变量属于类不属于对象。所以不会参与序列化的过程,其次使用 transient 关键字编辑的成员变量不参与序列化过程。
三、Parcelable 接口
我们在 Android Studio 中写了一个类 User 实现 Parcelable 接口
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
}
}
这个时候就会爆红,我们把鼠标放到 User 上面然后按住 "Alt + Enter"键会出现下图的情况,点击第一个 Add Parcelable Implementation
点击之后编辑器自动给我们写了序列化和反序列化过程,非常省事
我们看看全部代码:
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
protected User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 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];
}
};
// 内容描述
@Override
public int describeContents() {
return 0;
}
// 序列化
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(userId);
parcel.writeString(userName);
parcel.writeByte((byte) (isMale ? 1 : 0));
}
}
Parcelable 的方法说明
方法 | 功能 | 标记位 |
---|---|---|
createFromParcel(Parcel in) | 从序列化后的对象中创建原始对象 | |
newArray(int size) | 创建指定长度的原始对象数组 | |
User(Parcel in) | 从序列化后的对象中创建原始对象 | |
writeToParcel (Parcel out, int flags) | 将当前对象写入序列化结构中,其中flags标识有两种值:0或者1(参见右侧标记位)。为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0 | PARCELABLE_WEITE_RETURN VALUE |
describeContents | 返回当前对象的内容描述。如果含有文件描述符,返回1(参见右侧标记位),否则返回0, 几乎所有情况都返回0 | CONTENTS_FILE_DESCRIPTOR |
四、两种接口怎么选择
既然 Parcelable 和 Serializable 都能实现序列化并且都可以用于 Intent 间的数据传递,通过二和三我们也知道他们之间的区别了,那么二者该怎么选择呢?
1.Serializable 是 Java 中的序列化接口,其使用起来开销比较大,序列化过程有很多 I/O 操作,所以在Java 中我们就采用 Serializable 接口。
2.Parcelable 是 Android 中的序列化方式,因此更适合用在 Android 平台上,它的缺点就是使用起来比较麻烦,但是它的效率很高,这是 Android 推荐的序列化方式,因此我们要首选 Parcelable。
3.Parcelable 将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍微复杂,在 Java 平台建议用 Serializable,在 Android 平台建议用 Parcelable 。
部分内容来自安卓开发艺术探索