对象的序列化存储:Serializable 和 Parceable

0

文章参考自任玉刚大神的书籍《Android开发艺术探索》,强烈推荐这本书。

在进行Android开发的时候我们有时候需要用到数据的持久化存储,或者在进程之间传递数据。其中就可能需要用到对象的序列化,经过序列化的对象之后可以通过Intent或者Boundle来传输了。接下来还是想些介绍下吧。

1.什么叫序列化,什么叫反序列化

序列化: 将数据结构或对象转换成二进制串的过程。
反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。

简单来说,序列化就是将我们生成的对象进行存储起来(比如磁盘上),以用来将来使用或者在网络上进行传输,而反序列化呢,就是由我们的之前序列化生成的二进制串重新生成对象的过程。注意,这里我们反复说的序列化啦,反序列化啦,都是针对的对象,而非。因为我们是针对对象进行存取与传输的,而非类,当我们需要重新获取之前的对象的时候,是直接读取出来的(从文件或网络中),而非根据类new出一个对象,这点是需要注意的。

2.如何序列化

序列话的方式有两种,一种是实现Serializable接口,一种是实现Parceable接口,下面会具体介绍这两种方式。

a.实现Serializable接口

这种序列化方式是Java提供的,它的优点是简单,其实Serializable接口是个空接口,因而我们并不需要实现什么抽象方法,但是我们却往往需要在类中声明一个静态变量标识(serialVersionUID),但这不是必须的,我们不声明,依然可以实现序列化,但是这样的话会对反序列化产生一定的影响,可能会在我们对类做了修改之后而造成对象的反序列化失败。声明方式如下:

private static final long serialVersionUID = 8711368828010083044L;

注意,这里的值可以是任意值。

下面我们来具体实现下。

package com.qc.admin.myserializableparceabledemo;

import java.io.Serializable;

/**
 * Created by admin on 2016/12/1.
 */

public class User implements Serializable {

    private static final long serialVersionUID = 519067123721295773L;

    public int userId;
    public String userName;
    public boolean isMale;

    public User(int userId, String userName, boolean isMale) {

        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;

    }

    @Override
    public String toString() {
        return "User{ " +
                "userId = " + userId +
                ", userName = " + userName +
                ", isMale = " + isMale +
                " }";
    }
}

下面是序列化与反序列化过程:

    private void beginSerizable() throws IOException, ClassNotFoundException {

        // 序列化
        User user = new User(2016, "qian", true);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File(getFilesDir(), "myfile.txt")));
        out.writeObject(user);
        out.close();

        // 反序列化
        // 注意,这里后面的“/myfile.txt”前面有个斜杠“/”,否则会报“FileNotFoundException”异常
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(getFilesDir() + "/myfile.txt"));
        User mUser = (User) in.readObject();
        textView.setText(mUser.toString());
        in.close();
        Log.i("test",mUser.toString());
    }

运行结果截图:

clipboard.png

注意:如果是在Android项目中调用以上方法,别忘了在Manifest.xml文件中配置如下权限:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

b.实现Parceable接口

这种方式是Android提供的方式,相比较前面那种方式来讲,这种方式稍微有点复杂,我们需要自己尽享序列化与反序列化的操作,但是它却更加高效,并不需要执行大量的I/O操作。而且这种方式也是Android推荐的序列化方式,因此我们应该首选Parceable。只要实现了这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder进行传递了。下面请看实例:

public class Book implements Parcelable {
    public String bookTitle;
    public int bookId;

    protected Book(Parcel in) {
        bookTitle = in.readString();
        bookId = in.readInt();
    }

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

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

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(bookTitle);
        parcel.writeInt(bookId);
    }
}

这里将Book这个类就实现了Parcelable接口,其实在Android Studio IDE中,上述过程很简单,我们只需要定义一个类,实现Parcelable接口,然后在里面定义我们的属性或者说是字段,根据提示的错误,按照它提示的方法覆盖相应的方法,之后的一切其实都可以自动生成(不过如果需要构造方法的话,那就需要自动生成了,toString()方法也是自己实现的),所以不用担心在Android开发中通过实现Parceable接口会比较麻烦,因为AS都会为你自动生成。
上面我们已经完整的将Book类实现了Parceable接口,那接下来如何序列化和反序列化呢?如果你说,刚才不是已经说过了吗,采用文件读取的方式不久可以了啦...当你那样做的时候,你会发现会报如下的错误:

clipboard.png

Why???...什么情况?提示我们Book类没有实现序列化:

/System.err: java.io.NotSerializableException: com.qc.admin.myserializableparceabledemo.Book

好啦,之所以出现这种问题,并不是我们的实现过程有问题,而是使用该类的方式行不通。到这里我们就明白了Serializable和Parceable两种方式实现序列化还是有区别的,刚才我们也讲了,Parceable更加高效,不会像Serializable那样有大量的I/O操作,这句话的具体含义就道出了Serializable与Parcelable区别:虽然两者都是用于支持序列化、反序列化话操作,但是两者最大的区别在于存储媒介的不同,Serializable是将序列化后的对象存储在硬盘上,使用I/O读写的方式,而Parcelable是将其存储在内存中,是针对内存的读写,熟悉计算机组成原理的朋友都知道,内存的读写速度显然要远远大于I/O的读写速度,这也是为什么Android中推荐使用Parcelable这种方式来实现对象的序列化。
那我们应该怎么使用通过实现Parcelable接口实现序列化的对象呢?答案是:通过Intent方式传递,除了基本类型外,Intent只能传输序列化之后的对象,对应这两种序列化方式,也有两种相应的方法:

        mIntent.getSerializableExtra(string name );
        mIntent.getParcelableExtra(String name );

当然,放入的操作就没有这种区分了,都是方法:

mIntent.putExtra();

我们可以在第一个Activity中将序列化对象放入Intent,在另一个Activity中取出,比如:
在另一端获取对象,例如:

        Bundle mBundle = getIntent().getExtras();
        Book mBook = mBundle.getParcelable("book1");

下面再看类User实现Parceable接口的过程,它内部包含了一个可序列化的类Book,具体细节跟上面的有点不同:

package com.qc.admin.myserializableparceabledemo;

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

/**
 * Created by admin on 2016/12/1.
 */

public class User implements Parcelable {

    public int userId;
    public String userName;
    public boolean isMale;
    public Book book;


    public User(int userId, String userName, boolean isMale, Book book) {

        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
        this.book = book;
    }


    protected User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() != 0;
        // 此为不同之处1
        // 也可以通过这种方式:book = in.readParcelable(Thread.currentThread().getContextClassLoader());
        book = in.readParcelable(Book.class.getClassLoader());
        
    }

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

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


    // 几乎在所有的情况下都应该返回0,只有在当前对象中存在文件描述的时候,此方法返回CONTENTS_FILE_DESCRIPTOR(常量值为1)
    @Override
    public int describeContents() {
        return 0;
    }

    // 将对象写入序列化结构中,其中i标识有两种值,0或者1(PARCELABLE_WRITE_RETURN_VALUE)
    // 为1时表示当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(userId);
        parcel.writeString(userName);
        // 注意这里,并不是直接写入boolean值,而是写入整数值
        parcel.writeByte((byte) (isMale ? 1 : 0));
        // 此为不同之处2
        parcel.writeParcelable(book, i);
    }

    @Override
    public String toString() {
        return "User{ " +
                "userId = " + userId +
                ", userName = " + userName +
                ", isMale = " + isMale +
                "book = " + book.toString() +
                " }";
    }

}

可以看出,结果已经正确的打印了出来了:

clipboard.png

注意:在 Parcelable 中,我们无法直接写入 boolean 值,而是将其转化为整数值进行保存,这里为 Byte,当然,你也可以使用 Int 等。

你可能感兴趣的

载入中...