Android序列化学习

jkouu 55 0

什么是序列化

“序列化”这个名字听起来很抽象,其实它的意义很简单:把一个对象以某种形式存储到硬盘中,需要时再读取到内存。实际上,我们或多或少都接触过序列化,比如说我们做UI时用到的xml文件、客户端与服务器通信时用json传输一个对象的信息等。

实际上,序列化是一个功能很简单但是做起来很麻烦的工作。可能序列化看起来只是非常简单的文件读写操作,但是它要考虑的问题非常多。确实,在序列化的对象只是单纯的数字或者字符串的时候,这就是一个简单的文件读写。但是如果序列化的对象是一个类的实例呢?如果没有一种标准化的流程,那么序列化的实现就会非常困难。幸运的是,现在Android的序列化已经非常容易使用了。我们只需要知道一些API,就可以实现我们的需求。

序列化实现方式

一般来说,Android序列化有四种方式:Serializable、Parcelable以及Json和Xml。前面两种方式将序列化的对象输出成为十六进制字节码,后面两种方式则是我们常见的json文件和xml文件。显然,后面的两种方式的可读性更强,但是前面的两种方法的用处也不小。因为json文件和xml文件大家都用得很多,这里就不再着墨了,接下来只介绍前两种序列化的方式。

这里再说明一下,为了省事儿,下面的Demo类我没有写构造函数,不是说必须不写。

Serializable

Serializable是Java提供的序列化解决方案。它的实现非常的简单,只需要为要序列化的类使用Serializable接口就可以了,也不用实现什么方法,就像下面这样:

import java.io.Serializable;

public class Demo implements Serializable {
    int a=0;
    int b=0;
    int c=0;
}

输出到文件时,只需要向普通的文件读写那样建立输出流,然后调用writeObject方法就可以了:

public void Output(Demo d){
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("output.txt"));
    oos.writeObject();
    oos.flush();
    oos.close();
}

同样地,读取时只要把输出流改为输入流就可以了:

public void Read(){
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("output.txt"));
    Demo d = (Demo) ois.readObject();
    ois.close();
}

不过,这么写会有一些隐患。比如说刚才这个例子,我们现在想修改Demo这个类,再添加一个字段d,变成下面这样:

import java.io.Serializable;

public class Demo implements Serializable {
    int a=0;
    int b=0;
    int c=0;
    int d;
}

这样一来在读取我们刚才存的数据时就会出现错误。大家可以这么理解错误出现的原因:

Serializable之所以实现简单,是因为系统帮我们实现了Demo类的序列化方式。实际上,Demo类每修改一次,系统都会再重新实现一次Demo类的序列化方式。为了说明同一个类的不同版本,在生成序列化方式时,系统会自动为这个序列化方式添加一个ID。显然,我们刚才存的那个Demo类的实例化方式和添加完新字段后的实例化方式是两个版本。那么在读取保存的实例时,系统就会因为序列化实现不一样而报错。

那怎么解决这个问题呢?很简单,我们强制规定Demo类的序列化方式是唯一的一个给定的ID,这样一来就不会出现版本不匹配的原因了。系统在进行读取时会忽略新出现的字段(或者说系统在读取时只初始化文件中给定的字段的值),这样即使再修改了Demo类后,我们以前保存的文件也还可以继续用。当然,这种解决方案仅限于字段数量的变动。如果你把int改成了String,那这种方法也是无效的。

给定ID也很简单,就一句话的事:

public class demo implements Serializable {
    private static final long serialVersionUID = -1702767332483986504L;
    int a=0;
    int b=0;
    int c=0;
}

这句话可以由Android Studio自动添加。大家可以在Setting中的Inspections开启“Serializable class without 'serialVersionUID'"的警告功能,然后双击类的名称,就会出现自动生成的选项。

Parcelable

Parcelable是Android系统提供的序列化的解决方案。相比于Serializable,Parcelable的内存开销更小。但是,它的实现也比较麻烦。

import android.os.Parcel;
import android.os.Parcelable;

public class Demo implements Parcelable {
    int a=0;
    int b=0;
    int c=0;


    protected Demo(Parcel in) {
        a = in.readInt();
        b = in.readInt();
        c = in.readInt();
    }

    public static final Creator<Demo> CREATOR = new Creator<Demo>() {
        @Override
        public Demo createFromParcel(Parcel in) {
            return new Demo(in);
        }

        @Override
        public Demo[] newArray(int size) {
            return new Demo[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(a);
        dest.writeInt(b);
        dest.writeInt(c);
    }
}

上面是Demo类使用Parcelable的样子。可以看到,要使用Parcelable,我们要重写四个方法。不过其实大部分内容都已经生成好了,一般情况下不大需要再做改动。即使需要改动,方法的名字就已经讲明了方法的功能,也没有很难看懂的地方。这里只说明一下比较抽象的describeContents函数。其实也没啥可说明的,源码里规定的是只有在比较特殊的需要描述信息的对象才返回1,一般情况下只需要返回0。

序列化的用途

我们都知道,json一般用来和服务器交互信息,xml可以用来作为本地配置文件。那么Serializable和Parcelable能用来干什么呢?这里就说一个最实用的功能:进程通信。

我们都知道,Intent的Extra也好,Bundle也好,都只能传简单的数据类型,比如int或者String什么的。有了序列化,我们就能把一个类的实例通过Intent或者Bundle传过去:

public void communicate(Demo demo){
    Intent intent = new Intent();
    intent.putExtra("Demo", demo);
}

实际上,这是调用了下面的重载:

public @NonNull Intent putExtra(String name, @Nullable Parcelable value) {
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    mExtras.putParcelable(name, value);
    return this;
}

在收到Intent或者Bundle后,也可以很方便地把对象拿出来:

Demo d = intent.getParcelableExtra("Demo");

这里举的例子是Parcelable的,Serializable的操作也一样,只是换一下方法名而已。

不过要注意的是,序列化始终只是文件的读取,并不是传递的对象本身。也就是说,通过序列化拿到的对象和被序列化发送的对象其实是两个对象,序列化做的只是将对象的数值写入文件和根据文件提供的数值创造一个对象而已。

发表评论 取消回复
您必须 [登录] 才能发表评论!
分享