该文章是一个系列文章,是本人在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消息机制(Looper Handler MessageQueue Message)

    该文章是一个系列文章,是本人在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. canvas 从初级到XX 1# 部分非基础原生API的使用 [初级向]

    标题canvas 从初级到XX,XX是因为本文随机都可能会太监,并不会支持到入土.请慎重的往下看. 对于canvas的介绍,随处都可以找到,也就不啰嗦太多了.就直奔主题了. 先看一段代码,以及实现的效 ...

  2. BFS求最短路 Abbottt's Revenge UVa 816

    本题的题意是输入起点,朝向和终点,求一条最短路径(多解时任意输出一个即可) 本题的主要代码是bfs求解,就是以下代码中的slove的主要部分,通过起点按照路径的长度来寻找最短路径,输出最先到终点的一系 ...

  3. C++map类型 之 简单介绍

    一:map的前世今生 (1)从关联容器与顺序容器说起.关联容器通过键(key)存储和读取元素.而顺序容器则通过元素在容器中的位置顺序存储和訪问元素(vector,queue,stack,list等). ...

  4. DB主从一致性架构优化4种方法

    需求缘起 大部分互联网的业务都是"读多写少"的场景,数据库层面,读性能往往成为瓶颈.如下图:业界通常采用"一主多从,读写分离,冗余多个读库"的数据库架构来提升数 ...

  5. 前端优化之动画为什么要尽量用css3代替js

    导致JavaScript效率低的两大原因:操作DOM和使用页面动画.通常我们会通过频繁的操作 DOM的CSS来实现视觉上的动画效果,导致js效率低的两个因素都包括在内了在频繁的操作DOM和CSS时,浏 ...

  6. Python笔记·第九章—— 函数 (一)

    一.函数的作用 函数可以让我们代码结构更清晰,而且避免了代码的重复,冗余,使一段代码或者功能可以反复的被调用,大大提高了开发效率 二.函数的定义 def 函数名(参数1,参数2,*args,默认参数, ...

  7. 关于Idea中右边的maven projects窗口找不到了如何调出来

    关于Idea中右边的maven  projects窗口找不到了如何调出来? 具体的idea版本我不太清楚,我用的是2016版,其他版本应该也是一样的. 首先idea自带了maven控件,不像Eclip ...

  8. springboot 入门五-日志一

    springboot内部采用commons logging作为日志纪录,但也保留了第三方的日志框架接入的实现,例如Java Util Logging,Log4J2还有Logback.如果你要实现一种日 ...

  9. java随机生成验证码

    package com.yuyuchen.util; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; im ...

  10. bzoj 3718: [PA2014]Parking

    Description 你的老板命令你将停车场里的车移动成他想要的样子. 停车场是一个长条矩形,宽度为w.我们以其左下角顶点为原点,坐标轴平行于矩形的边,建立直角坐标系.停车场很长,我们可以认为它一直 ...