Java的Serializable与Android的Parcelable序列化简析


关于序列化

这篇文章主要来讲一下在安卓中关于序列化的问题。首先了解一个问题:什么是序列化?为什么要用到序列化?

  • 什么是序列化:序列化就是就是把一个对象变成可传输的二进制流,可以进行传输。

  • 什么是反序列化:与序列化对应,反序列化就是把一个二进制流转化成对象。

  • 哪里用到序列化:上面说到序列化就是把对象变得可传输;例如在内存,或者网络中传输数据的时候,就得把一个对象变成二进制流可以进行传输。除此之外,在各种通信中,例如进程间通信,文件读取写入等等都要用到序列化。涉及到数据传输,就得使用序列化。因为只有二进制流才可以进行传输。

  • 我们在那些地方会遇到序列化:我们会发现,仅有基本数据类型可以自动进行序列化,但是我的自定义对象并不可以进行序列化。在哪里可以体现呢?当我们从一个Activity向另外一个Activity传递数据的时候,通过Intent,我们会发现只能放基本数据类型。

  • 怎么让自定义的对象可序列化:就是我们要讲的Serializable和Parcelable接口。这两个接口就可以让我们的自定义对象可序列化。

    那这两个接口怎么使用?他们有什么区别?这就是接下来我要讲的。

    Serializable

    简述原理

    Serializable只要使用Java的ObjectOutputStream与ObjectInputStream来开启流。而这个接口主要就是用来标识这个对象可以被转换。关于IO流的相关知识,读者有兴趣可以去深入了解,这里不做深入探究。

怎么使用

这个序列化接口是Java提供的,实现了这个接口的类,其实例就可以进行传输。那么这个接口怎么使用呢?
直接实现这个接口就行了。
因为这是一个空接口,所以使用方法极其简单,只需要实现这个接口即可。接下来看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Test implements Serializable {
private String string1;
private String string2;
private int num;
private Son son;
private List<Son> list = new ArrayList<>();

public String getString1() {
return string1;
}
public void setString1(String string1) {
this.string1 = string1;
}
public String getString2() {
return string2;
}

public void setString2(String string2) {
this.string2 = string2;
}

public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
public List<Son> getList() {
return list;
}
public void setList(List<Son> list) {
this.list = list;
}
}

这样就可以把这个Test对象序列化了。

Parcelable

同样是使对象可序列化,同样是继承接口,但是Parcelable相比Serializable就要复杂得多了。Paecelable需要在要序列化的类中利用Parcel重写序列化和反序列化的方法。

Parcelable的实现原理

Parcelable的实现原理主要就是在内存中开辟一个共享内存块,然后进行传输,看图:
在这里插入图片描述
如图在AB两个进程中要进行传输,不能直接把一个对象传输过去,开辟一块内存,通过Parcel,也就是打包的方式,把数据存在了共享内存,再把共享内存中的数据用Parcel取出来,实现数据传输。通俗点来讲,就像送外卖一样,商家找了外卖这个中间人,把食物打包给了外卖小哥,然后你再找外卖小哥拿外卖,再拆包就可以拿到食物了。
通过原理可以看到,实现这个接口的重点就在:装包,拆包。如果将对象里面的数据,一个个装起来,是我们要在类中实现的方法。而装拆包公司就是Parcel。所以首先来了解一下这个包装公司:Parcel!

“包装公司”Parcel

Parcel的使用和Intent是差不多的,就是write(put)进去,再read(get)出来。简单来看看怎么使用:

装包

直接上代码,相关方法说明看注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Parcel parcel;

//打包字符串
parcel.writeString(String string);

//打包整型数
parcel.writeInt(int num);

//打包布尔变量,要和bite进行转换
parcel.writeByte((byte) (boolean bl? 1 : 0));

//打包字符串集合
parcel.writeStringList(List<String> list);

//ElemType表示一个自定义类
//序列化对象的时候传入要序列化的对象和一个flag,这里的flag的意思是是否要将这个作为返回值返回
//一般传入0,要作为返回值就写1。另外这个对象也一定要是可序列化的
parcel.writeParcelable(ElemType elem, 0);

//这里写入自定义元素集合有两种方法,区别是是否写入类的信息
//第一种方法是不写入类信息,相对应的取出来的时候要用Parcel把元素里面的每一个元素拿出来
parcel.writeTypedList(mFriends);
//第二种方法是写入类的信息,取出来的时候就要用类加载器去加载
parcel.writeList(mFriends);
//同样也要保证集合的元素可序列化

这里可以看到整体和Intent的传入数据是十分像的。重点有三个地方不太一样:

  • 布尔变量。布尔变量不能直接放进去,要转换成bite
  • 可序列化对象。这个放进去的时候要保证这个对象是可序列化的,也就是必须实现序列化接口
  • 集合。集合分为两种。一种是把元素类的信息也放进去,一种是不把元素的类的信息放进去。下面将拆包会详细讲一下。
拆包

直接上代码:(变量使用装包中的变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Parcel source;//这个对象在反序列化的时候会作为参数传入供给使用

//获取字符串
String string= source.readString();

//获取整型
int i = source.readInt();

//获取布尔数
boolean b = source.readByte() != 0;//这里注意转化

//获取自定义对象
// 读取对象需要提供一个类加载器去读取,因为写入的时候写入了类的相关信息
ElemType e = source.readParcelable(ElemType.class.getClassLoader());

//获取字符串集合
List<String> list = source.createStringArrayList();


//这一类需要用相应的类加载器去获取
//ElemType表示一个自定义类
source.readList(List<Elemtype> list, ElemType.class.getClassLoader());
//这一类需要使用自定义类的CREATOR,也就是使用Pacel的反序列方法去获取
//source.readTypedList(ElemType e, ElemType.CREATOR); //对应writeTypeList
//books = in.createTypedArrayList(ElemType.CREATOR); //对应writeTypeList

主要就是对于布尔变量以及集合的转换会比较不一样。
代码中的Friend.CREATOR是Parcelable接口中反序列的方法,实现原理也是用Parcel把对象里面的数据一个个转换出来。

  • 布尔变量要进行转换
  • 对象要用类加载器去加载。因为写入的时候写入了类信息
  • 集合看情况;写入了类信息就要用类加载器;否则用反序列的方法。
小结

Parcel的使用方法和Intent很像,但是有几个点要注意一下:

  • Parcel中没有布尔变量,所以必须将布尔变量和bite进行转换

  • Parcel包装自定义对象时,这个对象对应的类必须也得是实现了序列化接口的才可以进行打包。装包的这个方法因为写入了类的信息,所以拆包的时候需要用到对应类的加载器来进行加载。

  • Parcel在包装集合的时候有两种方法:一种是写入类的信息,一种是不写入类的信息。但都有一个前提:集合中的元素必须是可序列化的。第一种方法需要用到类加载器去加载,第二种就需要用到自定义类中的Parcel的反序列化方法。

  • 对于类中有多个相同类型的数据,例如3个字符串,3个int等等,存入的顺序和取出来的顺序一定要一致。例如存int a,int b;那么取出来的时候也得是int a,int b。不能颠倒。

    Parcelable的使用

    看完了上面的Pacel可能还是有一点懵,那这个怎么Parcelable怎么用Pacel实现呢?Parcelable的主要实现思路就是把一个对象中的数据进行分解,分解出来的数据都是可以用Parcel打包的。Parcelable具体怎么使用呢?接下来就来看一下。先看看谷歌给出的示例(代码很重要,记得看注释):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    //首先要实现Parcelable接口
    public class MyParcelable implements Parcelable {
    //类中的数据
    private int mData;

    //接下来这两个方法是关于序列化的。一定要重写的两个方法
    //第一个方法是内容描述。一般没什么要求都是返回0就行了
    public int describeContents() {
    return 0;
    }

    //第二个是序列化数据的方法。当我们序列化这个类的对象的时候就会调用到这个方法
    //两个参数:一个是Pacel“包装公司”上面我们讲的,一个是flags。第二个参数表示是否把数据返回。一般为0.
    //利用这个Parcel把类中所有的数据“打包”序列化
    public void writeToParcel(Parcel out, int flags) {
    //这个就是上面我们介绍的Parcel的使用
    out.writeInt(mData);
    }


    //接下来的这两个就是关于反序列化的了
    //CREATOR是Parcelable中的一个静态变量,这个名字不能改,因为他内部调用的时候就是用CREATOR来调用的改了名字就找不到了
    //观察我们上面的Pacel使用方法也可以发现,是传入ElemType.CREATOR。如果改了名字就找不到了。
    //Creator对象中有两个方法要重写:
    public static final Creator<MyParceable> CREATOR
    = new Creator<MyParceable>() {

    //这个就是反序列方法了。在Pacel中把数据取出来构建成一个MyParcelable对象
    public MyParcelable createFromParcel(Parcel in) {
    //这里我们用到构造器来获取这个对象。具体构造器实现方法在下面
    return new MyParcelable(in);
    }

    //这个是开辟数组供给使用。一般按照下面的格式写就好了。
    public MyParcelable[] newArray(int size) {
    return new MyParcelable[size];
    }
    };
    //这个是构造器。从Parcel中把数据取出来
    private MyParcelable(Parcel in) {
    //这里就用到了上面我们讲的Parcel的获取数据的方法了
    mData = in.readInt();
    }

    通过上面的代码相信也可以很清晰的了解怎么实现了。主要就是分为两个部分:序列化和反序列化。

  • 序列化主要就是用一个Parcel把数据封装起来。怎么使用按照上面的Parcel使用方法。

  • 反序列化要用到一个静态变量,这也可以说是一个构造器,Creator本身就是创造的意思嘛。通过这个Creator吧Parcel中的数据取出来再构造成一个对象,对应拆包。具体我们使用构造器来实现。构造器里面用Parcel的获取数据方法来把数据取出来。需要注意的是这里的CREATOR名字不能改。

    两者的区别

    好了。到这里相信都了解怎么使用这两个接口了。主要是Parcelable比较复杂。现在有个问题了,为什么要造一个如此复杂的接口呢?来看看他们各自的优缺点。

    Serializable

    这个是java内部定义的序列化接口。

  • 优点:使用范围广,在网络,内存文件等均可以使用;使用方式简单,仅仅只需要继承一个接口就可以使用。

  • 缺点:性能差;序列化的时候需要创建很多的临时对象。

Parcelable

这个安卓中可以使用的接口,仅用于安卓。

  • 优点:性能强大。开发者号称比Serialization强大10倍的性能。
  • 缺点:实现过程复杂,需要把对象中的数据一个个拆开进行序列化;
    从实现原理可以看出来,这个只能用于内存中的序列化,不能用于网络和本地硬盘文件。

总结

序列化在安卓开发中还是很常用的,例如活动之间传递信息,跨进程传递数据等等都要用到。这两个接口一个简单粗暴,一个复杂强大,平常开发的话,尽量还是使用第二个接口,毕竟多一点代码可以加强这么多的性能何乐不为呢。Parcelable可能比较难理解,多看几遍就可以了。不理解或者笔者写错的可以评论区留言。


文章作者: zhegnhuan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zhegnhuan !
 上一篇
Android使用系统相机进行拍照 Android使用系统相机进行拍照
前言我们在日常的开发中有时候会遇到需要用到相机的需求,而相机也是很常用的东西,例如扫二维码啊拍照上传啊等等。这里我不讲像qq那样自定义很强的拍照功能(事实上我也不会),讲个最简单的调用系统相机拍照并储存 调用系统相机步骤这里我通过一个简单的
2020-04-21
下一篇 
LitePal的使用 LitePal的使用
前言数据库操作一直都是比较繁琐而且单一的东西,平时开发中数据库也很常见。有学过mysql的读者可能会觉得sql语句确实让人很难受。同样android中,虽然有内置数据库SQLite,但是操作起来还是非常的不方便。跟网络请求类似,当我们用原生
2020-04-21
  目录