在最近的一次项目中,从服务器中请求得到了登录用户的相关信息,显然的,需要把其中的信息保存下来。在Android中,持久化保存信息的方式有很多种。比如数据库,首选项,又或者内部存储。但是,这个需要保存的信息字段有些多,目的是方便没网络时也能过显示一些相关的信息。我想到了对象序列化技术,把整个对象保存到磁盘中(内部存储)。
于是,结合网上的文章,及书本上的对应章节,把该知识点记录一下。
想要把一个对象能够被直接保存到数据库或磁盘中,有以下的步骤:
①实现Serializable接口,这是一个声明接口,没有需要实现的方法
public class Person implements Serializable {
public static final long serialVersionUID = 1L;
private int age;
private String password;
//other fields
//setter and getter
}
②保存对象
public void saveObject(Object obj) {
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("person.dat");
oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
} catch(Excepton e) {
e.printStackTrace();
} finally {
//close stream
}
}
通过上面简单的几句代码,我们就把对象保存下来了。
③读取对象
public Object readObject() {
FileInputStream fis = null;
ObjectInputStream ois = null;
Object obj = null;
try {
fis = new FileInputStream("person.dat");
ois = new ObjectInputStream(fis);
obj = ois.readObject();
} catch(Exception e) {
//...
} finally {
//close stream
}
return obj;
}
通过上面的方法调用我们就把保存的对象从物理媒介中读取了出来
简单的序列化调用这样即可。当然,还需要理解关键字 transient
的意思,它表示当序列化时,请把我忽略;同时,静态字段static
也不会被序列化。
如果我们想保存某个对象时,需要修改,加密某些字段,应该要如果做?我们当然可以额外的在在保存时加密,取出来时再解密。但序列化机制提供了这样的方法截点。即,如果声明了Serializable
的类中有实现private void writeObject(ObjectOutputStream oos);
则保存时会调用该方法,而不是默认的调用ObjectOutputStream#defaultWriteObject()
方法;如果实现了private void readObject(ObjectInputStream ois);
方法,则读取时会调用该方法,而不是默认的ObjectInputStream#defaultReadObject()
方法。当然推荐的是,当我们自己实现这两个方法时,首要的是调用默认的实现。我们举个例子,我们要对Person中的password字段加密后再保存,因为当对象保存到本地时,是可以轻易的读取出来的。
例子:
public class Person implements Serializable {
public static final long serialVersionUID = 1L;
private String password;
//other fields
//other methods
private void writeObject(ObjectOutputStream oos) {
this.password = encrypt(this.password);
try {
oos.defaultWriteObject();
} catch(Exception e) {}
}
private void readObject(ObjectInputStream ois) {
try {
ois.defaultReadObject();
this.password = decrypt(this.password);
} catch(Exception e) {}
}
}
.
嗯,解决完这个问题。再来看一个序列化对象版本的问题。当读取一个序列号对象时,如果没有设置静态常量字段serialVersionUID
,则虚拟机会根据类的相关信息动态生成一个新的版本号,这可能会导致类不兼容,引发InvalidClassException
异常抛出。serialVersionUID
的值可以自己随意设置,或者使用serialver工具生成,或者Eclipse中有智能提示,或者设置Android Studio自动生成。
再来看一个问题,假如我们这个对象的实例是Singleton单例模式,但是我们读取对象时是新建了一个对象,显然是不符合我们的需求的。所以,我们再看一下另一个方法private Object readResolve();
这个方法也是写在声明了Serializable
的类里边。该方法的特性是:
对于一个正在被反序列化的对象,如果它的类定义了一个readObject方法,并且具备正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用。然后,该放方法返回的对象引用将被返回,取代新对象。
所以,我们可以在该方法中返回单例对象。
.
另外,这里引申出一个简单的用法,即深度克隆一个对象:
public Object cloneObject(Object srcObj) {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout);
out.writeObject(srcObj);
out.close;
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin);
Object ret = in.readObject();
return ret;
}
.
对于简单的序列化用法,有几点总结:
①显示声明序列化版本号serialVersionUID
②保护性地编写readObject方法,比如某些字段不能为null