Serializable & Parcelable 原理和区别
Posted 安卓开发-顺
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Serializable & Parcelable 原理和区别相关的知识,希望对你有一定的参考价值。
目录
目录
一、认识 Serializable 和 Parcelable
二、Serializable 和 Parcelable工作原理
前言
Serializable和Parcelable都是序列化相关的接口,所以学习之前,先简单描述下什么是序列化和反序列化。
一个中国人碰到一个美国人,中国人写了个一二三,美国人写了个one two three,结果双方都看不懂,于是中国人写了123,美国人也写了个123,结果双方都看懂了。这个变成123的过程可以理解为序列化,123变成一二三或one two three的过程可以理解为反序列化。
上面的小故事虽然简陋点,但是大概说明了序列化的本质:就是通过一套通用的协议让不同进程、不同系统、不同平台、不同语言之间进行通信。
一、认识 Serializable 和 Parcelable
Serializable
java提供的序列化接口,实现这个接口仅仅是表示当前对象可以被序列化,真正要实现当前对象的序列化和反序列化工作,需要通过ObjectOutputStream(序列化) 和 ObjectInputStream (反序列化)来实现(这步操作我们很少自己去实现,大多数场景系统内部都封装处理了)。android中常见应用场景有:网络传输、数据持久化处理、跨进程通信(性能不如Parcelable)等。
Parcelable
Android提供的序列化接口,实现这个接口并实现writeToParcel、describeContents方法,在定义一个静态内部对象CREATOR(类型是Parcelable.Creator)并实现Creator的createFromParcel、newArray接口,就可以完成序列化和反序列化工作。Android中常见应用场景有:跨进程通信。
二、Serializable 和 Parcelable工作原理
Serializable工作原理
第一步通过ObjectInputStream 的writeObject 进行序列化操作:
/**
* 通过调用 writeObject 方法来把我们的对象序列化成二进制字节流
*/
public final void writeObject(Object obj) throws IOException
if (enableOverride)
writeObjectOverride(obj);
return;
try
//常规情况走这里
writeObject0(obj, false);
catch (IOException ex)
if (depth == 0)
writeFatalException(ex);
throw ex;
... 继续往下
private void writeObject0(Object obj, boolean unshared)
throws IOException
try
... 省略若干代码
for (;;)
//这里很重要,会通过反射来查找外部是否自定义了
//readObject、writeObject、readResolve、writeReplace方法,
//如果有定义,走到后面就会执行外部定义的方法,不在执行默认的这些方法
desc = ObjectStreamClass.lookup(cl, true);
... 省略若干代码
... 省略若干代码
// remaining cases
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)
//关键代码 我们的对象实现了Serializable 就会先走这里
writeOrdinaryObject(obj, desc, unshared);
else
if (extendedDebugInfo)
throw new NotSerializableException(
cl.getName() + "\\n" + debugInfoStack.toString());
else
throw new NotSerializableException(cl.getName());
finally
depth--;
bout.setBlockDataMode(oldMode);
... 继续往下
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc,boolean unshared)
throws IOException
...
try
...
if (desc.isExternalizable() && !desc.isProxy())
//如果我们的对象实现了Externalizable接口,走这里
writeExternalData((Externalizable) obj);
else
//如果我们的对象实现的是Serializable接口,走这里
writeSerialData(obj, desc);
finally
if (extendedDebugInfo)
debugInfoStack.pop();
... 继续往下 看writeSerialData方法
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++)
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod())
...
这里前面说过,会通过反射来查找外部是否自定义了
//readObject、writeObject、readResolve、writeReplace方法,
//如果有定义就走这里了
slotDesc.invokeWriteObject(obj, this);
...
else
//我们自己没定义这四个固定的方法就默认走这里
defaultWriteFields(obj, slotDesc);
... 继续往下 看defaultWriteFields方法
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
...
//正真序列化的地方
bout.write(primVals, 0, primDataSize, false);
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++)
...
try
//如果我们的对象里嵌套了对象 就递归调用
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
finally
if (extendedDebugInfo)
debugInfoStack.pop();
第二步通过ObjectInputStream 的readObject 进行序列化操作: 这一步的源码和第一步源码思路非常像,这里不在分析了。
实战代码演示:
import java.io.Serializable;
/**
* Serializable 测试类
*/
public class SerializableTest implements Serializable
/**
* serialVersionUID 用来表明类的版本,如果反序列化时此版本和序列化时的版本不一致就会报错InvalidCastException
* 如果这里我们不写 系统会自动生成一个serialVersionUID,此时我们的类做任何更改这个uid都会变,这一点有时候不符合我们的需求
* 因为我们有时候希望类修改时会兼容老版本,而不是只要不一样了就不能正常序列化 所以建议显示的自己定义一个serialVersionUID
* 给它一个默认值就好了,当新版本的类无法兼容老版本的时候就主动修改这个值即可
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
public SerializableTest(String name, int age)
this.name = name;
this.age = age;
public String getName()
return name;
public void setName(String name)
this.name = name;
public int getAge()
return age;
public void setAge(int age)
this.age = age;
public class SerializableMain
public static void main(String[] args)
SerializableTest test = new SerializableTest("hello", 10);
try
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(out);
//序列化
outputStream.writeObject(test);
byte[] bytes = out.toByteArray();
//反序列化
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
ObjectInputStream inputStream = new ObjectInputStream(in);
SerializableTest test1 = (SerializableTest) inputStream.readObject();
System.out.println(test1.getName() + " -- " + test1.getAge());
catch (IOException | ClassNotFoundException e)
e.printStackTrace();
日志打印:hello -- 10
serialVersionUID
serialVersionUID 用来表明类的版本,如果反序列化时此版本和序列化时的版本不一致就会报错InvalidCastException。
如果我们不写, 系统会自动生成一个serialVersionUID,此时我们的类做任何更改这个uid都会自动变,这一点有时候不符合我们的需求, 因为我们有时候希望类修改时会兼容老版本,而不是只要不一样了就不能正常序列化 所以建议显示的自己定义一个serialVersionUID,给它一个默认值就好了,当新版本的类无法兼容老版本的时候就主动修改这个值即可
Externalizable 接口
Externalizable接口是Serializable接口的子类。如果你想自己主动控制对象中哪些属性要序列化,就实现这个接口,然后覆盖这个接口的writeExternal()和readExternal()方法,来自定义实现就可以了。
@Override
public void writeExternal(ObjectOutput out) throws IOException
out.writeObject(name);
out.writeInt(age);
@Override
public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException
name = (String) in.readObject();
age = in.readInt();
注意事项
-
单例模式失效问题
由于反序列化过程是一个“深拷贝过程” 把对象反序列化出来后会执行其无参构造方法创建一个示例,因此单例模式会失效。
解决办法:在单例类中定义一个readResolve方法 直接返回当前示例对象即可
private Object readResolve()
return instance;
-
写入过程中更改对象属性值的问题
public static void main(String[] args)
SerializableTest test = new SerializableTest("hello", 10);
try
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(out);
//序列化
outputStream.writeObject(test);
test.setAge(15);
//如果writeObject以后又改变了对象的值 就要调用reset方法 然后重新writeObject
outputStream.reset();
outputStream.writeObject(test);
//或者 直接调用一下writeUnshared
// outputStream.writeUnshared(test);
byte[] bytes = out.toByteArray();
//反序列化
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
ObjectInputStream inputStream = new ObjectInputStream(in);
SerializableTest test1 = (SerializableTest) inputStream.readObject();
System.out.println(test1.getName() + " -- " + test1.getAge());
catch (IOException | ClassNotFoundException e)
e.printStackTrace();
-
不是所有属性都会序列化
(1)用transient关键字标记的成员变量不参与序列化(在被反序列化后,transient 变量的值被
设为初始值,如 int 型的是 0,对象型的是 null)
(2)静态成员变量属于类不属于对象,所以不会参与序列化(对象序列化保存的是对象的“状态”,也就是它的成员变量,因此序列化不会关注静态变量)
Parcelable工作原理
真正完成序列化和反序列化操作的类是Parcel 这个类,Parcel 的核心原理,简单来说就是它提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象,读写过程都是native方法实现的,下图是这个过程的模型。
实战代码
public class ParcelableTest implements Parcelable
private String name;
private int age;
protected ParcelableTest(Parcel in)
name = in.readString();
age = in.readInt();
/**
* 将对象转换成一个 Parcel 对象
* @param dest 表示要写入的 Parcel 对象
* @param flags 示这个对象将如何写入
*/
@Override
public void writeToParcel(Parcel dest, int flags)
dest.writeString(name);
dest.writeInt(age);
/**
* 实现类必须有一个 Creator 属性,用于反序列化,将 Parcel 对象转换为 Parcelable
* @param <T>
*/
public static final Creator<ParcelableTest> CREATOR = new Creator<ParcelableTest>()
//反序列化的方法,将Parcel还原成Java对象
@Override
public ParcelableTest createFromParcel(Parcel in)
return new ParcelableTest(in);
//提供给外部类反序列化这个数组使用。
@Override
public ParcelableTest[] newArray(int size)
return new ParcelableTest[size];
;
/**
* 描述当前 Parcelable 实例的对象类型 目前就两个值 一个 0 一个 1(ParcelFileDescriptor.CONTENTS_FILE_DESCRIPTOR)
* 如果对象中有文件描述符,就返回1
* 我们默认情况 返回 0就可以了
* @return
*/
@Override
public int describeContents()
return 0;
总结
性能对比:
Serializable是Java中的序列化接口,其使用起来简单但开销较大(因为Serializable在序列化过程中使 用了反射机制,故而会产生大量的临时变量,从而导致频繁的GC),并且在读写数据过程中,它是通 过IO流的形式将数据写入到硬盘或者传输到网络上。
Parcelable则是以IBinder作为信息载体,在内存上开销比较小,因此在内存之间进行数据传递时,推荐 使用Parcelable,而Parcelable对数据进行持久化或者网络传输时操作复杂,一般这个时候推荐使用 Serializable。
如何选择:
1、在使用内存方面Parcelable比Serializable性能高,所以推荐使用Parcelable。
2、在数据持久化方面建议使用Serializable,因为Parcelable不能很好的保证数据的持续性,使用相对复杂,也没有明显性能优势。
以上是关于Serializable & Parcelable 原理和区别的主要内容,如果未能解决你的问题,请参考以下文章
Parcelable, Serializable,Cloneable,copyProperties
Android:使用Parcelable接口在跳转Activity时传递数据以及错误信息 Parcel: unable to marshal value 的解决
Android:使用Parcelable接口在跳转Activity时传递数据以及错误信息 Parcel: unable to marshal value 的解决