序列化之Parcelable和Serializable

作者: Jdqm | 来源:发表于2017-12-31 13:35 被阅读0次
序列化与反序列化

1.Parcelable和Serializable有什么用,它们有什么差别?

Parcelable和Serializable是两个接口,它们的作用是让实现了其中一个接口的类的对象能够被序列化和反序列化。
(1)Serializable是java提供的序列化接口,它是一个空的接口,仅标识该类型可序列化的,具体的序列化/反序列化工作由ObjectInputStream(readObject)/ObjectOutputStream(writeObject)完成,这个过程包含大量的I/O操作,使用比较简单,但需要考量性能的影响。使用场景:将对象持久化到存储介质或者通过网络传输。

(2)Parcelable接口是Android平台下的序列化接口,通常跨进程传递的数据都要正确实现这个接口,比如Intent,Bitmap等。Parcelable实现起来比Serializable复杂,但性能较好。使用场景:在内存中实现序列化,例如跨进程传递。若某个字段不需要序列化,在实现writeToParcel方法中忽略此字段即可。

2.自定义一个类让其实现Parcelable,大致流程是什么?

(1)首先实现Parcelable接口,并实现接口中的方法。

/**
 * 返回当前对象的内容描述,如果有文件描述符返回1,否则返回0。
 */
@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(userId);
    dest.writeString(userName);
    dest.writeInt(isMan ? 1 : 0);
}

(2)接着创建一个Parcelable接口内部的接口类型Creator的一个成员,内部需要用到一个带一个Parcel参数的构造方法。

protected User(Parcel in) {
    userId = in.readInt();
    userName = in.readString();
    isMan = in.readByte() != 0;
}

/**
 * 在aidl中,参数使用in或者inout来修饰时,服务端的onTransact()会调用CREATOR中方法来反序列化客户端传过来的参数
 */
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];
    }
};

(3)如果在aidl文件中使用out或者inout定向tag来修饰参数,还必须实现一个readFromParcel(Parcel)方法,这是因为使用这两个定向tag修饰的参数,在服务端onTransact()返回后,客户端会调用readFromParcel()来读取(反序列化)_reply中的数据。该方法与writeToParcel是对应的,实现如下:

public void readFromParcel(Parcel in) {
    userId = in.readInt();
    userName = in.readString();
    isMan = in.readInt() == 1 ? true : false;
}

3.实现Serializable接口

通过实现Serializable接口来实现序列化比较简单,只需要实现该接口,并指定serialVersionUID即可,当然这个ID是可选的,可以不手动指定。通过ObjectIntputStream /ObjectOutputStream来序列化和反序列化。

ObjectOutputStream oos = null;
try {
    oos = new ObjectOutputStream(new FileOutputStream(new File("sdcard/user.cache")));
    User user = new User(111, "Jdqm", true);
    oos.writeObject(user);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        oos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
ObjectInputStream ois = null;
try {
    ois = new ObjectInputStream(new FileInputStream(new File("sdcard/user.cache")));
    User user = (User) ois.readObject();
    Log.d(TAG, "user: " + user);
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
} finally {
    try {
        ois.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

输出结果

User{userId=111, userName='Jdqm', isMan=true}

serialVersionUID的意义:辅助完成序列化和反序列化,当一个类实现SerSerializable接口,没有添加serialVersionUID的字段时,IDE会发出警告,这个字段可以手动指定一个值,比如1L,也可指定为IED根据类的结构生成一个long值,它们的效果是一样的。在序列化时会将这个值写入存储介质,反序列化时就校验本地类的serialVersionUID和序列化介质中的是否一致,不一致将抛出异常java.io.InvalidClassException

(1)若不指定:系统会根据类的结构计算出一个serialVersionUID,一旦类的结构发生改变这个值就会改变,将导致反序列化失败;
(2)指定一个值:当类的结构发生改变时,也可以不修改serialVersionUID的值,这种情况下能最大程度上通过反序列化回复数据,若类的结构发生毁灭性的改变,例如字段数据类型改变了,也会导致反序列失败。

Note: 类的结构发生改变指的是类的成员变量的改变,添加一个普通的方法是不会导致计算得到的serialVersionUID改变的。构造方法、toString()、getter/setter改变会引起serialVersionUID改变。

  • transient修饰的成员变量不参与序列化,反序列化时改成员为该数据类型的默认值
  • 静态成员不参与序列化
  • 反序列化得到的一个新对象的过程并没有调用构造方法

相关文章

网友评论

    本文标题:序列化之Parcelable和Serializable

    本文链接:https://www.haomeiwen.com/subject/kgwogxtx.html