该文章是一个系列文章,是本人在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. onunload事件和onbeforeunload事件

    记录知识点背景:在做一个h5项目时,在统计事件时有这样一个需求, 希望能统计到用户是从第几页离开的,用到了这个知识点.在此记录. window.onunload 1.定义和用法 onunload事件在 ...

  2. 谷哥的小弟学前端(11)——JavaScript基础知识(2)

    探索Android软键盘的疑难杂症 深入探讨Android异步精髓Handler 具体解释Android主流框架不可或缺的基石 站在源代码的肩膀上全解Scroller工作机制 Android多分辨率适 ...

  3. 側滑回退的layout(相似IOS側滑回退到上一个activity)

    用过apple的同学应该都知道,大多数IOS应用都支持側滑回退,就不具体说明了,直接上图: 作为使用ios的android开发人员来说,我是特别喜欢这个功能的.既然这样,那就在android上也实现这 ...

  4. ZOJ ACM 1204 (JAVA)

    毕业好几年了,对算法还是比較有兴趣,所以想又一次開始做ACM题.俺做题比較任意,一般先挑通过率高的题来做. 第1204题,详细描写叙述请參考,ZOJ ACM 1204 1)难度分析 这个题目,基本的难 ...

  5. 《RabbitMQ Tutorial》译文 第 6 章 远程过程调用(RPC)

    原文来自 RabbitMQ 英文官网的教程(6.Remote procedure call - RPC),其示例代码采用了 .NET C# 语言. In the second tutorial we ...

  6. docker容器自动退出的问题

    如果用了一段时间的docker就会发现,我们的容器经常用了一段时间就自动退出了,docker ps已经找不到了,在docker ps -a里面了,然后我们docker start containerI ...

  7. iOS 电脑新装的系统, 使用sourceTree 创建本地仓库的时候, 总是提示, 无效路径

    把qq聊天记录分享出来: 我电脑新装的系统, 使用sourceTree 创建本地仓库的时候, 总是提示, 无效路径请问哪位遇到过求指教群里有产品经理没有? ssh 配制的不对重装系统过后,重新生成一下 ...

  8. [array] leetcode - 54. Spiral Matrix - Medium

    leetcode-54. Spiral Matrix - Medium descrition GGiven a matrix of m x n elements (m rows, n columns) ...

  9. SpringMVC 中配置 Swagger 插件.

    一.简介 Swagger的目标是为REST API定义一个与语言无关的标准接口,允许用户发现和理解计算机服务的功能,而无需访问源代码.当通过Swagger正确定义时,用户可以用最少量的实现逻辑理解远程 ...

  10. linux权限设置(开放某个文件夹给指定用户)

    问题背景: 今天想把自己的数据集开放给同事a,只允许其读,不允许写. 操作: step1: 查看该文件夹属于哪一个用户,哪一个组 ls 文件夹 -lstep2: usermod -a -G 指定文件夹 ...