對象的序列化存儲:Serializable 和 Parceable

文章參考自任玉剛大神的書籍《Android開發藝術探索》,強烈推薦這本書。java

在進行Android開發的時候咱們有時候須要用到數據的持久化存儲,或者在進程之間傳遞數據。其中就可能須要用到對象的序列化,通過序列化的對象以後能夠經過Intent或者Boundle來傳輸了。接下來仍是想些介紹下吧。android

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

序列化: 將數據結構或對象轉換成二進制串的過程。
反序列化:將在序列化過程當中所生成的二進制串轉換成數據結構或者對象的過程。網絡

簡單來講,序列化就是將咱們生成的對象進行存儲起來(好比磁盤上),以用來未來使用或者在網絡上進行傳輸,而反序列化呢,就是由咱們的以前序列化生成的二進制串從新生成對象的過程。注意,這裏咱們反覆說的序列化啦,反序列化啦,都是針對的對象,而非。由於咱們是針對對象進行存取與傳輸的,而非類,當咱們須要從新獲取以前的對象的時候,是直接讀取出來的(從文件或網絡中),而非根據類new出一個對象,這點是須要注意的。數據結構

2.如何序列化

序列話的方式有兩種,一種是實現Serializable接口,一種是實現Parceable接口,下面會具體介紹這兩種方式。ide

a.實現Serializable接口

這種序列化方式是Java提供的,它的優勢是簡單,其實Serializable接口是個空接口,於是咱們並不須要實現什麼抽象方法,可是咱們卻每每須要在類中聲明一個靜態變量標識(serialVersionUID),但這不是必須的,咱們不聲明,依然能夠實現序列化,可是這樣的話會對反序列化產生必定的影響,可能會在咱們對類作了修改以後而形成對象的反序列化失敗。聲明方式以下:this

private static final long serialVersionUID = 8711368828010083044L;

注意,這裏的值能夠是任意值。spa

下面咱們來具體實現下。code

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 +
                " }";
    }
}

下面是序列化與反序列化過程:xml

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 等。

相關文章
相關標籤/搜索