对象序列化

Serializable 和 Parcelable

Posted by DH on July 12, 2019

概述

在Android中,IPC需要先将对象进行序列化,在完成对象序列化之后,通过Intent和Binder等传输数据。序列化方式主要有Serializable和Parcelable。 其中Serializable是Java提供的一个对象序列化接口,Parcelable是Android提供的对象序列化接口。

Serializable

Serializable的使用很简单,只需要实现接口,就能完成对象的序列化和反序列化。例如,有一个类Person,只要User实现Serializable接口就能实现 对象的序列化和反序列化。

import java.io.Serializable;
public class User implements Serializable{
	private static final long seriaVersionUID = 1L;
	private String name;
	private int age;
	private String male;
	public User(String name, int age, String male) {
		super();
		this.name = name;
		this.age = age;
		this.male = male;
	}
	
}

我们通过ObjectOutputStream和ObjectInputSttream实现对象的序列化过程:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TextMain {

	public static void main(String[] args) throws ClassNotFoundException {
		// 序列化
		User user = new User("david",22,"male");
		try {
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.txt"));
			out.writeObject(user);
			out.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		// 反序列化
		try {
			ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.txt"));
			User newUser = (User)in.readObject();
			System.out.println("姓名:" + newUser.getName());
			System.out.println("年龄:" + newUser.getAge());
			System.out.println("性别:" + newUser.getMale());
			in.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

查看输出:

反序列化成功,说明序列化也成功了。但是这里要注意的是,序列化前后两个对象并不是同一个对象,仅仅是对象的值相同。

我们注意到User对象中有一个属性:private static final long seriaVersionUID = 10086L;

我们手动指定了这个属性的值为10086L,其实没有这个值,在上面的代码中也会成功。这个类的作用主要是:

在序列化时,系统会将seriaVersionUID写入到序列化文件中,反序列化时,会检测当前类的seriaVersionUID和序列化文件中的seriaVersionUID 是否一致,如果相同,说明序列化和反序列化成功,否则说明当前的类和序列化的类不同,序列化失败。

seriaVersionUID的指定有两种方式,一种是手动指定,一种是让编译器根据当前类的结构生成hash值。

如果要尽量保持序列化的成功,应该使用手动。如果使用编译器生成的seriaVersionUID,只要类发生了变化,hash值就会变灰,序列化就会一定失败。 而使用手动指定,在序列化前后,seriaVersionUID是一定的,即使因为版本升级等原因,类的属性增加或者减少,都会尽最大努力保持序列化成功。但是 即使手动指定了seriaVersionUID,也不能保证序列化和反序列化一定就成功,如果发生了类的结构性变化,比如改变类名,还是会失败的。

另外:

(1)静态成员变量属于类而不属于对象,不会参加序列化和反序列化;

(2)被transient修饰的成员变量不参与序列化过程。

需要注意transient关键字,被它修饰的变量不会参与serializable序列化,但是会被Externalizable序列化。

Parcelable

Parcelable是Android专用的序列化和反序列化方法。和serializable一样,需要实现接口,对象就能够通过Intent和Binder传递。

首先定义两个类:

package com.example.daihao.servicetest;

import android.os.Parcel;
import android.os.Parcelable;

public class Car  implements Parcelable {
    private String holder;
    private int price;

    public Car(String holder, int price) {
        this.holder = holder;
        this.price = price;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(holder);
        dest.writeInt(price);
    }

    public static final Parcelable.Creator<Car> CREATOR = new Parcelable.Creator<Car>(){
        @Override
        public Car createFromParcel(Parcel source) {
            return null;
        }

        @Override
        public Car[] newArray(int size) {
            return new Car[size];
        }
    };

    private Car(Parcel in){
        holder = in.readString();
        price = in.readInt();
    }

}

Car作为User的属性,也需要完成序列化。

package com.example.daihao.servicetest;
import android.os.Parcel;
import android.os.Parcelable;

public class User implements Parcelable {
    private String userName;
    private int age;
    private String male;
    private Car car;


    /**
     * 序列化
     *
     * @param dest
     * @param flags 绝大多数情况下是0.为1时,当前对象作为返回值使用,不能立即释放
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(userName);
        dest.writeInt(age);
        dest.writeString(male);
        dest.writeParcelable(car, 0);
    }

    /**
     * 反序列化
     */
    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){
        @Override
        public User createFromParcel(Parcel source) {
            return new  User(source);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    private User(Parcel in){
        userName = in.readString();
        age = in.readInt();
        male = in.readString();
        car = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }

    /**
     * 当前对象的内容描述。
     * 绝大多数情况下返回0;
     * 返回1表示该对象含有文件描述符
     * @return
     */
    @Override

    public int describeContents() {
        return 0;
    }


    public User(String userName, int age, String male, Car car) {
        this.userName = userName;
        this.age = age;
        this.male = male;
        this.car = car;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getMale() {
        return male;
    }

    public void setMale(String male) {
        this.male = male;
    }
}

使用Parcelable进行序列化,需要复写如下方法:

(1)public void writeToParcel(Parcel dest, int flags):序列化方法,参数flags,绝大多数情况下是0。为1时,当前对象作为返回值使用,不能立即释放;

(2)public static final Parcelable.Creator CREATOR = new Parcelable.Creator():反序列化,其中包含两个方法,一个是public User createFromParcel(Parcel source),一个是public User[] newArray(int size)。第一个是反序列化,返回对象,第二个是创建原始对象数组。

(3)public int describeContents():当前对象的内容描述。绝大多数情况下返回0;返回1表示该对象含有文件描述符

区别

在Android记住使用Parcelable就OK了。区别在性能上:Parcelable更好。Serializable是Java提供的接口,需要大量IO操作,开销较大。