Android开发之漫漫长途 X——Android序列化

时间:2022-09-07 14:25:35

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。


前言

上一篇中我们比较详尽的分析了ServiceManager。那么本篇我们来讲一下Android序列化的相关知识。为什么跨度那么大,因为“任性”?其实不是的,同志们还记得上两篇出现的Parcel吗,Parcel是一个容器,他可以包含数据或者是对象引用,并且能够用于Binder的传输。同时支持序列化以及跨进程之后进行反序列化,同时其提供了很多方法帮助开发者完成这些功能。从上面的描述可以看出Parcel是进程间通信的数据载体。我们常常需要持久化一些对象,除了数据库等持久化方案之外,把对象转换成字节数组并通过流的方式存储在本地也是一个不错的方法,另外当我们需要通过Intent和Binder传输数据是就需要使用序列化后的数据。

Java中的Serializable#

Serializable 是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,只需要在需要序列化的类实现Serializable接口并在其中声明一个类似下面的标识即可自动实现默认的序列化过程。

public class Person extends PersonParent implements Serializable {
private static final long serialVersionUID = 1L; //静态域
public static int static_field;
//transient域
public transient int transient_field;
//一个普通的域
public String desc; public Person(String desc) {
this.desc = desc;
} static class PersonSerializableProxy implements Serializable{
private String desc; private PersonSerializableProxy(Person s) {
this.desc = s.desc;
} /**
* 与writeReplace相同,ObjectInputStream会通过反射调用 readResolve()这个方法,
* 决定是否替换反序列化出来的对象。
* @return
*/
private Object readResolve() {
return new Person(desc);
} } /**
*
* 在序列化一个对象时,ObjectOutputStream会通过反射首先调用writeReplace这个方法,
* 在这里我们可以替换真正送去序列的对象,
* 如果我们没有重写,那序列化的对象就是最开始的对象。
* @return
*/
private Object writeReplace() {
//序列化Person的时候我们并没有直接写入Person对象,而是写入了PersonSerializableProxy对象
return new PersonSerializableProxy(this);
} /**
* 这里主要是为了防止攻击,任何以Person声明的对象字节流都是流氓!!
* 因为我在writeReplace中已经把序列化的实例指向了SerializableProxy
* @param stream
* @throws InvalidObjectException
*/
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("proxy requied!");
} public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person("desc");
person.transient_field = 100;
person.static_field = 10086; ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt"));
outputStream.writeObject(person);
outputStream.flush();
outputStream.close(); person.static_field = 10087; ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cache.txt"));
Person deserialObj = (Person) objectInputStream.readObject();
System.out.println(deserialObj);
} }
class PersonParent{
private String name;
//PersonParent类要么继承自Serializable,要么需要提供一个无参构造器。
public PersonParent() {
} public PersonParent(String name) {
this.name = name;
}
}

不过在使用中也需要注意以下几个问题:

  1. serialVersionUID用来标识当前序列化对象的类版本,建议每一个实现Serialization的类都指定该域。当然如果我们没有指定,JVM会根据类的信息自动生成一个UID。
  2. 被transient描述的域和类的静态变量是不会被序列化的,序列化是针对类实例。
  3. 需要进行序列化的对象所有的域都必须实现Serializable接口,不然会直接报错NotSerializableException。当然,有两个例外:域为空 或者域被transient描述是不会报错的。
  4. 如果一个实现了Serializable类的对象继承自另外一个类,那么这个类要么需要继承自Serializable,要么需要提供一个无参构造器。
  5. 反序列化产生的对象并不是通过构造器创建的,那么很多依赖于构造器保证的约束条件在对象反序列化时都无法保证。比如一个设计成单例的类如果能够被序列化就可以分分钟克隆出多个实例...

Android中的Parcelable#

相对于Serializable而言,Parcelable的使用要复杂一些

public class Book implements Parcelable {
private String name; public Book(String name) {
this.name = name;
} protected Book(Parcel in) {
name = in.readString();
} //反序列化功能由CREATOR完成,在CREATOR的内部标明的如何创建序列对象和数组
public static final Creator<Book> CREATOR = new Creator<Book>() {
//从Parcel中反序列化对象
@Override
public Book createFromParcel(Parcel in) {
//其内部调用Parcel的一系列readXXX方法实现反序列化过程
return new Book(in);
}
//创建序列化数组
@Override
public Book[] newArray(int size) {
return new Book[size];
}
}; @Override
public int describeContents() {
return 0;
}
//序列化过程:
//重写writeToParcel方法,我们要在这里逐一对需要序列化的属性用Parcel的一系列writeXXX方法写入
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
}

从上述代码注释可以看出,写一个实现Parcelable接口的类还是比较麻烦的,和Serailable相比,我们需要在writeToParcel中按序写入各个域到流中,同样,在createFromParcel中我们需要自己返回一个Book对象。

Parcelable在使用上也与Serializable稍有不同

public class TestActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test2); //获取一个Parcel容器
Parcel parcel = Parcel.obtain();
//需要序列化的对象
Book book = new Book("c++"); //把对象写入Parcel
parcel.writeParcelable(book,0);
//Parcel读写共用一个位置计数,这里一定要重置一下当前的位置
parcel.setDataPosition(0);
//读取Parcel
Book book1 = parcel.readParcelable(Book.class.getClassLoader());
Log.d("TestActivity",book1.toString());
}
}

Parcelable的写##

我们来看一下writeParcelable方法

[Parcel.java]

public final void writeParcelable(Parcelable p, int parcelableFlags) {
//判断p是否为空
if (p == null) {
writeString(null);
return;
}
//① 先写入p的类名
writeParcelableCreator(p);
//② 调用我们重写的writeToParcel方法,按顺序写入域
p.writeToParcel(this, parcelableFlags);
} public final void writeParcelableCreator(Parcelable p) {
//① 先写入p的类名
String name = p.getClass().getName();
writeString(name);
}

Parcelable的读##

我们来看readParcelable方法

[Parcel.java]

public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
//① 调用readParcelableCreator
//这时获得就是我们自定义的CREATOR
Parcelable.Creator<?> creator = readParcelableCreator(loader); if (creator == null) {
return null;
}
// 判断当前creator是不是Parcelable.ClassLoaderCreator<?>的实例
if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
//如果是的话,,我们调用reateFromParcel(this, loader);
Parcelable.ClassLoaderCreator<?> classLoaderCreator =
(Parcelable.ClassLoaderCreator<?>) creator;
return (T) classLoaderCreator.createFromParcel(this, loader);
}
//调用我们自定义的CREATOR中重写的createFromParcel方法
return (T) creator.createFromParcel(this);
} public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
//首先把类名读取出来
String name = readString(); Parcelable.Creator<?> creator;
//mCreators做了一下缓存,如果之前某个classloader把一个parcelable的Creator获取过
//那么就不需要通过反射去查找了 synchronized (mCreators) {
HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
if (map == null) {
map = new HashMap<>();
mCreators.put(loader, map);
}
creator = map.get(name);
if (creator == null) {
try {
ClassLoader parcelableClassLoader =
(loader == null ? getClass().getClassLoader() : loader);
//加载我们自己实现Parcelable接口的类
Class<?> parcelableClass = Class.forName(name, false,
parcelableClassLoader); Field f = parcelableClass.getField("CREATOR");
Class<?> creatorType = f.getType();
creator = (Parcelable.Creator<?>) f.get(null);
}
catch (Exception e) {
//catch exception
}
if (creator == null) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "non-null Parcelable.Creator object called "
+ "CREATOR on class " + name);
} map.put(name, creator);
}
} return creator;
}

我们的测试例子读取Parcel

Book book1 = parcel.readParcelable(Book.class.getClassLoader());

可以看到我们在使用

readParcelable的时候,传入的参数是Book类的类加载器,根据我们上面的代码,我们知道我们先会通过反射获取定义在Book类中的CREATOR属性,我们回想一下在Book类中是怎么定义CREATOR的

    public static final Creator<Book> CREATOR = new Creator<Book>() {
//从Parcel中反序列化对象
@Override
public Book createFromParcel(Parcel in) {
//其内部调用Parcel的一系列readXXX方法实现反序列化过程
return new Book(in);
}
//创建序列化数组
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};

我们得到CREATOR属性后,调用它的createFromParcel方法,由多态可知调用的实际我们定义在CREATOR内的createFromParcel方法,在该方法内我们创建了Book对象(内部实现是通过Parcel的一系列readXXX方法)并返回。至此我们就得到了反序列化的对象


本篇总结

我们本篇详细分析了Android序列化相关知识,你可以使用Java中的Serializable也可以使用Parcelable。


下篇预告

下一篇文章是对前面所讲文章做一个小结。读者敬请期待哦。

此致,敬礼

Android开发之漫漫长途 X——Android序列化的更多相关文章

  1. Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. Android开发之漫漫长途 ⅥI——Android消息机制&lpar;Looper Handler MessageQueue Message&rpar;

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  3. Android开发之漫漫长途 XI——从I到X的小结

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  4. Android开发之漫漫长途 XIV——ListView

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  5. Android开发之漫漫长途 IX——彻底掌握Binder

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  6. Android开发之漫漫长途 Ⅰ——Android系统的创世之初以及Activity的生命周期

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>中的相关知识,再次表示该书 ...

  7. Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(2)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  8. Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(1)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  9. Android开发之漫漫长途 Ⅳ——Activity的显示之ViewRootImpl初探

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

随机推荐

  1. cpu利用率和cpu 队列

    SIP的第四期结束了,因为控制策略的丰富,早先的的压力测试结果已经无法反映在高并发和高压力下SIP的运行状况,因此需要重新作压力测试.跟在测试人员后面做了快一周的压力测试,压力测试的报告也正式出炉,本 ...

  2. URAL 1936 Roshambo 题解

    http://acm.timus.ru/problem.aspx?space=1&num=1936 F - Roshambo Time Limit:1000MS Memory Limit:65 ...

  3. &lbrack;ActionScript 3&period;0&rsqb; Away3D 非skybox的全景例子

    package { import away3d.containers.View3D; import away3d.controllers.HoverController; import away3d. ...

  4. ZOJ 2563 Long Dominoes(状态压缩DP)

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1563 题目大意:在h*w的矩阵里铺满1*3的小矩阵,共有多少种方法 ...

  5. Uniqueidentifier数据类型

    一.Uniqueidentifier数据类型 可存储16字节的二进制值 Uniqueidentifier用来存储一个全局唯一标识符,即GUID.GUID是唯一的二进制数:世界上的任何两台计算机都不会生 ...

  6. asp&period;net mvc cooike 购物车 如何实现

    先上代码: 1. ShoppingCartService 类 using System; using System.Collections.Generic; using System.Linq; us ...

  7. 关于百度DNS的解析过程

    if现在我用一台电脑,通过ISP接入互联网,那么ISP就会分配给我一个DNS服务器(非权威服务器). now,我的computer向这台ISPDNS发起请求查询www.baidu.com. 首先,IS ...

  8. Python PEP8 编码规范

    代码编排 缩进.缩进4个空格,不能混合使用Tab和空格. 每行最大长度79,文档字符串和注释行最大长度为72,换行可以使用反斜杠,最好使用圆括号. 类和顶层函数定义之间空两行:类中的方法定义以单行分隔 ...

  9. SQL学习总结-思维导图

  10. UOJ272 &lbrack;清华集训2016&rsqb; 石家庄的工人阶级队伍比较坚强 【分治乘法】

    题目分析: 首先不难注意到式子就是异或卷积,所以考虑用分治乘法推出优化方法.我们把一个整体$f$拆成$f-,f\pm,f+$,然后另一个拆成$g-,g\pm,g+$.这样做的好处是能更清楚的分析问题. ...