网上其实有很多博客和论坛都解释了序列化是什么,但是看完解释还是很懵逼,可能是境界还不够吧。无奈.png
这个序列化到底是什么?我为什么要用它?用它有什么好处?什么情况下用它?一脸懵逼.jpg
我收集了一些资料,这里做个总结:
1.序列化到底是什么?
百度百科的解释是:对象的状态信息转换为可以存储或传输的形式的过程
android中主要存在2种序列化方法:Parcelable 和 Serializable
- Serializable
将对象信息转换为字节流、文件或数据库等形式来存储(反序列化就是反过程) - Parcelable
将对象信息进行一次选择性的深拷贝,拷贝的对象存放在内存中(反序列化就是再复制拷贝对象)
Serializable 通过反射将信息存在磁盘中(转为字节流除外),即使程序再次启动也能找回数据
Parcelable 将信息存在内存中,程序再次启动之前拷贝的数据就消失了
2.我为什么要用它?
这里举个例子: Activity A 传递一个对象user 给 Activity B
user 是A中的一个局部变量,在B中无法直接访问,这时候系统提供2种方法:Bundle.putSerializable(Key,Object)和Bundle.putParcelable(Key,Object)
疑问一, 为什么我不用静态对象User,这样直接在B上获取对象
静态对象是个好主意,可是当我在B上修改了user,其他地方使用的user也会发生变化,这就可能存在问题(Parcelable其实也算静态对象的方案,只是更巧妙些)
疑问二, 为什么我不直接将User转化为json(xml/protobuf等)格式,再通过String(Byte等)格式传递?
json是一个很好的解决方案,在效率上会优于Serializable(Serializable可以存储在磁盘),但低于Parcelable(Parcelable占内存)。各有优缺点吧
疑问三,为什么我不使用sp存储User对象,然后在B中还原?
sp可以存储轻量级的数据,而且可以解决intent 传递数据大小限制(binder缓冲池大小限制(有些手机为2M)),缺点是sp不支持跨app进程使用。
数据少这么操作麻烦,数据多的话就挺好的(FileProvider存储文件也和这个一样的效果)
至于说用上数据库或者先传到网上,然后再下载下来,就有些大炮打蚊子了。
疑问四,为什么我不使用handler或rxBus等骚操作来做?
并没有阻止你用,骚操作要用的骚才行(只是这2种方式使用更简单些)
3. 用它有什么好处?
固有思维: 数据持久化
4. 什么情况下用它?
进程间通信用的比较多(基本和binder有关)
5.扫盲
是否实现了对应的接口就实现了序列化
错误
- Serializable
public class Person implements Serializable{
//建议自己定义,不要用系统自动生成的
private static final long serialVersionUID = 1L;
private String name;
public Person(String name) {
this.name = name;
}
}
//储存
Person p = new Person("张三");
//不只是能使用FileOutputStream
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/person.txt")));
oos.writeObject(flyPig);
oos.close();
//获取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/person.txt")));
Person p2 = (Person) ois.readObject();
- Parcelable
//person
public class Person implements Parcelable {
private String name;
public Person(String name) {
this.name = name;
}
public Person(Parcel in) {
name = in.readString();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
}
//存储
Person p = new Person("张三");
Parcel parcel = Parcel.obtain();
p.writeToParcel(parcel,0);
//获取
Parcel parcel = Parcel.obtain();
Person p2 = Person.CREATOR.createFromParcel(parcel);
android源码其实很多地方都已经运用了序列化
//String
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence { ... }
//number
public abstract class Number implements java.io.Serializable { ... }
//file
public class File
implements Serializable, Comparable<File> { ... }
public final class Bitmap implements Parcelable { ... }
父类实现了接口,子类是否可以序列化
- Serializable
子类可以序列化
如果想要序列化一个类,必须要实现Serializable接口,包括他的属性
子类只可以序列化父类的可见属性,例如public,protected,或者其他情况,并且必须提供一个无参构造方法,否则会在运行时报错 - Parcelable
子类只可以序列化父类的属性,不可以序列化自己的属性,除非重写createFromParcel和writeToParcel方法
源码流程简析
这里copy下大佬的分析吧(具体源码分析可以去看下面推荐的文章,反正这个文字描述源码流程我不想写了╮(╯▽╰)╭):
Serializable原理:
writeObject
方法流程大致如下:
-
借助ObjectStreamClass记录目标对象的类型,类名等信息,这个类里面还有个ObjectStreamField数组,用来记录
目标对象
的内部变量; -
在
defaultWriteFields
方法中,会先通过ObjectStreamClass的getPrimFieldValues
方法,把基本数据类型的值都复制到一个叫primVals
的byte数组上; -
接着通过
getPrimFieldValues
方法来获取所有成员变量的值,出乎意料的是:这两个获取值的方法,里面都不是我们常规的反射操作(Field.get),而是通过操作Unsafe
类来完成的; -
遍历剩下不是基本数据类型的成员变量,然后递归调用
writeObject
方法(也就是一层层地剥开目标对象,直到找到基本数据类型为止);
readObject
流程差不多:先通过readClassDescriptor
方法(里面会把InputStream里面的数据读出来)获取到ObjectStreamClass 的实例,再根据这个实例来把基本数据类型和引用数据类型分别赋值到用反射创建的目标对象实例中。
Parcel原理:
它的各种writeXXX
方法,在native层都是会调用Parcel.cpp的write方法,它是直接复制内存( memcpy),地址由一个叫mData
的uint8_t来保存,mDataCapacity
来表示parcel缓存容量(大小),mDataPos
指向parcel缓存中空闲区域的首地址(有偏移量)
read方法同理,它也是通过 memcpy函数来把mData
上的某部分数据复制出来。
6. 总结
Serializable 反射多性能低,但存储磁盘,能持久保存
Parcelable 性能更高,但占内存,无法持久保存
网友评论