android序列化异常,Android序列化问题与思考

今天再来谈谈Android中的对象序列化,你了解多少呢?java

序列化指的是什么?有什么用

序列化指的是讲对象变成有序的字节流,变成字节流以后才能进行传输存储等一系列操做。

反序列化就是序列化的相反操做,也就是把序列化生成的字节流转为咱们内存的对象。android

介绍下Android中两种序列化接口

Serializable

是Java提供的一个序列化接口,是一个空接口,专门为对象提供序列化和反序列化操做。具体使用以下:数组

public class User implements Serializable {

private static final long serialVersionUID=519067123721561165l;

private int id;

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

}

实现Serializable接口,声明一个serialVersionUID。缓存

到这里可能有人就问了,不对啊,平时没有这个serialVersionUID啊。没错,serialVersionUID不是必须的,由于不写的话,系统会自动生成这个变量。它有什么用呢?当序列化的时候,系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候会去检测这个serialVersionUID,看他是否和当前类的serialVersionUID一致,同样则能够正常反序列化,若是不同就会报错了。网络

因此这个serialVersionUID就是序列化和反序列化过程当中的一个标识,表明一致性。不加的话会有什么影响?若是咱们序列化后,改动了这个类的某些成员变量,那么serialVersionUID就会改变,这时候再拿以前序列化的数据来反序列化就会报错。因此若是咱们手动指定serialVersionUID就能保证最大限度来恢复数据。ide

Serializable的实质实际上是是把Java对象序列化为二进制文件,而后就能在进程之间传递,而且用于网络传输或者本地存储等一系列操做,由于他的本质就存储了文件。能够看看源码:函数

private void writeObject0(Object obj, boolean unshared)

throws IOException

{

...

try {

Object orig = obj;

Class> cl = obj.getClass();

ObjectStreamClass desc;

desc = ObjectStreamClass.lookup(cl, true);

if (obj instanceof Class) {

writeClass((Class) obj, unshared);

} else if (obj instanceof ObjectStreamClass) {

writeClassDesc((ObjectStreamClass) obj, unshared);

// END Android-changed: Make Class and ObjectStreamClass replaceable.

} else if (obj instanceof String) {

writeString((String) obj, unshared);

} else if (cl.isArray()) {

writeArray(obj, desc, unshared);

} else if (obj instanceof Enum) {

writeEnum((Enum>) obj, desc, unshared);

} else if (obj instanceof Serializable) {

writeOrdinaryObject(obj, desc, unshared);

} else {

if (extendedDebugInfo) {

throw new NotSerializableException(

cl.getName() + "\n" + debugInfoStack.toString());

} else {

throw new NotSerializableException(cl.getName());

}

}

}

...

}

private void writeOrdinaryObject(Object obj,

ObjectStreamClass desc,

boolean unshared)

throws IOException

{

...

try {

desc.checkSerialize();

//写入二进制文件,普通对象开头的魔数0x73

bout.writeByte(TC_OBJECT);

//写入对应的类的描述符,见底下源码

writeClassDesc(desc, false);

handles.assign(unshared ? null : obj);

if (desc.isExternalizable() && !desc.isProxy()) {

writeExternalData((Externalizable) obj);

} else {

writeSerialData(obj, desc);

}

} finally {

if (extendedDebugInfo) {

debugInfoStack.pop();

}

}

}

public long getSerialVersionUID() {

// 若是没有定义serialVersionUID,序列化机制就会调用一个函数根据类内部的属性等计算出一个hash值

if (suid == null) {

suid = AccessController.doPrivileged(

new PrivilegedAction() {

public Long run() {

return computeDefaultSUID(cl);

}

}

);

}

return suid.longValue();

}

能够看到是经过反射获取对象以及对象属性的相关信息,而后将数据写到了一个二进制文件,而且写入了序列化协议版本等等。

而获取·serialVersionUID·的逻辑也体现出来,若是id为空则会生成计算一个hash值。post

Parcelable

Android自带的接口,使用起来要麻烦不少:须要实现Parcelable接口,重写describeContents(),writeToParcel(Parcel dest, @WriteFlags int flags),并添加一个静态成员变量CREATOR并实现Parcelable.Creator接口学习

public class User implements Parcelable {

private int id;

protected User(Parcel in) {

id = in.readInt();

}

@Override

public void writeToParcel(Parcel dest, int flags) {

dest.writeInt(id);

}

@Override

public int describeContents() {

return 0;

}

public static final Creator CREATOR = new Creator() {

@Override

public User createFromParcel(Parcel in) {

return new User(in);

}

@Override

public User[] newArray(int size) {

return new User[size];

}

};

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

}

createFromParcel,User(Parcel in) ,表明从序列化的对象中建立原始对象

newArray,表明建立指定长度的原始对象数组

writeToParcel,表明将当前对象写入到序列化结构中。

describeContents,表明返回当前对象的内容描述。若是还有文件描述符,返回1,不然返回0。

Parcelable的存储是经过Parcel存储到内存的,简单地说,Parcel提供了一套机制,能够将序列化以后的数据写入到一个共享内存中,其余进程经过Parcel能够从这块共享内存中读出字节流,并反序列化成对象。ui

这其中实际又是经过native方法实现的。具体逻辑我就没有去分析了,若是有大神朋友能够在评论区解析下。

固然,Parcelable也是能够持久化的,涉及到Parcel中的unmarshall和marshall方法。 这里简单贴一下代码:

protected void saveParce() {

FileOutputStream fos;

try {

fos = getApplicationContext().openFileOutput(TAG,

Context.MODE_PRIVATE);

BufferedOutputStream bos = new BufferedOutputStream(fos);

Parcel parcel = Parcel.obtain();

parcel.writeParcelable(new ParceData(), 0);

bos.write(parcel.marshall());

bos.flush();

bos.close();

fos.flush();

fos.close();

} catch (Exception e) {

e.printStackTrace();

}

}

protected void loadParce() {

FileInputStream fis;

try {

fis = getApplicationContext().openFileInput(TAG);

byte[] bytes = new byte[fis.available()];

fis.read(bytes);

Parcel parcel = Parcel.obtain();

parcel.unmarshall(bytes, 0, bytes.length);

parcel.setDataPosition(0);

ParceData data = parcel.readParcelable(ParceData.class.getClassLoader());

fis.close();

} catch (Exception e) {

e.printStackTrace();

}

}

总结

0)二者的区别,咱们该怎么选择?

Serializable是Java提供的序列化接口,使用简单可是开销很大,序列化和反序列化过程都须要大量I/O操做。

Parcelable是Android中提供的,也是Android中推荐的序列化方式。虽然使用麻烦,可是效率很高。

因此,若是是内存序列化层面,那么仍是建议Parcelable,由于他效率会比较高。

若是是网络传输和存储磁盘状况,就推荐Serializable,由于序列化方式比较简单,并且Parcelable不能保证,当外部条件发生变化时数据的连续性。

1)对于内存序列化方面建议用Parcelable,为何呢?

由于Serializable是存储了一个二进制文件,因此会有频繁的IO操做,消耗也比较大,并且用到了大量反射,反射操做也是耗时的。相比之下Parcelable就要效率高不少。

2)对于数据持久化仍是建议用Serializable,为何呢?

首先,Serializable自己就是存储到二进制文件,因此用于持久化比较方便。而Parcelable序列化是在内存中操做,若是进程关闭或者重启的时候,内存中的数据就会消失,那么Parcelable序列化用来持久化就有可能会失败,也就是数据不会连续完整。并且Parcelable还有一个问题是兼容性,每一个Android版本可能内部实现都不同,知识用于内存中也就是传递数据的话是不影响的,可是若是持久化可能就会有问题了,低版本的数据拿到高版本可能会出现兼容性问题。 因此仍是建议用Serializable进行持久化。

3)Parcelable必定比Serializable快吗?

有个比较有趣的例子是:当序列化一个超级大的对象图表(表示经过一个对象,拥有经过某路径能访问到其余不少的对象),而且每一个对象有10个以上属性时,而且Serializable实现了writeObject()以及readObject(),在平均每台安卓设备上,Serializable序列化速度大于Parcelable 3.6倍,反序列化速度大于1.6倍.

具体缘由就是由于Serilazable的实现方式中,是有缓存的概念的,当一个对象被解析事后,将会缓存在HandleTable中,当下一次解析到同一种类型的对象后,即可以向二进制流中,写入对应的缓存索引便可。可是对于Parcel来讲,没有这种概念,每一次的序列化都是独立的,每个对象,都看成一种新的对象以及新的类型的方式来处理。

参考

拜拜

有一块儿学习的小伙伴能够关注下❤️个人公众号——码上积木,天天剖析一个知识点,咱们一块儿积累知识。

5b7cfaee03154a70af18dd47.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值