Android开发之漫漫长途 X——Android序列化
该文章是一个系列文章,是本人在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;
}
}
不过在使用中也需要注意以下几个问题:
- serialVersionUID用来标识当前序列化对象的类版本,建议每一个实现Serialization的类都指定该域。当然如果我们没有指定,JVM会根据类的信息自动生成一个UID。
- 被transient描述的域和类的静态变量是不会被序列化的,序列化是针对类实例。
- 需要进行序列化的对象所有的域都必须实现Serializable接口,不然会直接报错NotSerializableException。当然,有两个例外:域为空 或者域被transient描述是不会报错的。
- 如果一个实现了Serializable类的对象继承自另外一个类,那么这个类要么需要继承自Serializable,要么需要提供一个无参构造器。
- 反序列化产生的对象并不是通过构造器创建的,那么很多依赖于构造器保证的约束条件在对象反序列化时都无法保证。比如一个设计成单例的类如果能够被序列化就可以分分钟克隆出多个实例...
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序列化的更多相关文章
- Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android开发之漫漫长途 ⅥI——Android消息机制(Looper Handler MessageQueue Message)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android开发之漫漫长途 XI——从I到X的小结
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android开发之漫漫长途 XIV——ListView
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android开发之漫漫长途 IX——彻底掌握Binder
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android开发之漫漫长途 Ⅰ——Android系统的创世之初以及Activity的生命周期
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>中的相关知识,再次表示该书 ...
- Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(2)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(1)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android开发之漫漫长途 Ⅳ——Activity的显示之ViewRootImpl初探
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
随机推荐
- Java基础—标识符及命名规范
什么是标识符符? 凡是可以由自己命名的地方都称为修饰符. 例: 项目名 ,包名 ,类名 .方法名 2. 命名规范. ① 不可使用java关键字和保留字,但是可以包含关键字和保留字. ② ...
- 《Effective Modern C++》翻译--简单介绍
北京时间2016年1月9日10:31:06.正式開始翻译.水平有限,各位看官若有觉得不妥之处,请批评指正. 之前已经有人翻译了前几个条目,有些借鉴出处:http://www.cnblogs.com/m ...
- Error: Failed to launch instance "win7": Please try again later [Error: No valid host was found. ].
感谢朋友支持本博客,欢迎共同探讨交流.因为能力和时间有限,错误之处在所难免,欢迎指正! 假设转载,请保留作者信息. 博客地址:http://blog.csdn.net/qq_21398167 原博文地 ...
- git 操作问题
clone远程版本号库的时候,报错,提示路径不正确. 之前输入的路径为:ssh://[ip]:[port号]/[数据库名称] 增加username后的路径:ssh://[username]@[ip]: ...
- HTTP请求中怎样选择Get和Post方式
在应用中最经常使用的Http请求无非是get和post,get请求能够获取静态页面,也能够把參数放在URL字串后面,传递给servlet.post与get的不同之处在于post的參数不是放在URL字串 ...
- Laravel技巧之记录多日志
相信每个小伙伴在使用laravel的时候都会记录日志.查看日志.那么问题来了,比如我在对接zabbix接口的时候,使用 Log::info() 会让日志全部记录在 storage/logs/larav ...
- 基于Gulp + Browserify构建es6环境下的自动化前端项目
随着React.Angular2.Redux等前沿的前端框架越来越流行,使用webpack.gulp等工具构建前端自动化项目也随之变得越来越重要.鉴于目前业界普遍更流行使用webpack来构建es6( ...
- 深入理解计算机系统_3e 第五章家庭作业 CS:APP3e chapter 5 homework
5.13 A. B. 由浮点数加法的延迟,CPE的下界应该是3. C. 由整数加法的延迟,CPE的下界应该是1. D. 由A中的数据流图,虽然浮点数乘法需要5个周期,但是它没有"数据依赖&q ...
- 分享一个开源免费、目前最好的API接口管理平台----eoLinker
一.概况 eoLinker 是目前业内领先.国内最大的在线 API 接口管理平台,提供自动生成 API 文档.API 自动化测试.Mock 测试.团队协作等功能,旨在解决由于前后端分离导致的开发效率低 ...
- 非常有用的GitHub链接
平常开发工作中,我经常取Github上搜索项目,Clone下来学习使用,在这个过程中,发现了好多比较好的Github地址,记录下来,分享出去. image 非常有用的GitHub链接(顺序不分先后): ...