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

关于序列化

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

  • 什么是序列化:序列化就是就是把一个对象变成可传输的二进制流,可以进行传输。
  • 什么是反序列化:与序列化对应,反序列化就是把一个二进制流转化成对象。
  • 哪里用到序列化:上面说到序列化就是把对象变得可传输;例如在内存,或者网络中传输数据的时候,就得把一个对象变成二进制流可以进行传输。除此之外,在各种通信中,例如进程间通信,文件读取写入等等都要用到序列化。涉及到数据传输,就得使用序列化。因为只有二进制流才可以进行传输。
  • 我们在那些地方会遇到序列化:我们会发现,仅有基本数据类型可以自动进行序列化,但是我的自定义对象并不可以进行序列化。在哪里可以体现呢?当我们从一个Activity向另外一个Activity传递数据的时候,通过Intent,我们会发现只能放基本数据类型。
  • 怎么让自定义的对象可序列化:就是我们要讲的Serializable和Parcelable接口。这两个接口就可以让我们的自定义对象可序列化。

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

Serializable

简述原理

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

怎么使用

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

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)出来。简单来看看怎么使用:

装包

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

		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
  • 可序列化对象。这个放进去的时候要保证这个对象是可序列化的,也就是必须实现序列化接口
  • 集合。集合分为两种。一种是把元素类的信息也放进去,一种是不把元素的类的信息放进去。下面将拆包会详细讲一下。
拆包

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

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具体怎么使用呢?接下来就来看一下。先看看谷歌给出的示例(代码很重要,记得看注释):

//首先要实现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可能比较难理解,多看几遍就可以了。不理解或者笔者写错的可以评论区留言。

猜你喜欢

转载自blog.csdn.net/weixin_43766753/article/details/102616908