Android中Parcelable的原理和使用方法

經(jīng)常閱讀博客是個(gè)好習(xí)慣
推薦技術(shù)小黑屋的Blog

Parcelable的簡(jiǎn)單介紹

介紹Parcelable不得不先提一下Serializable接口,Serializable是Java為我們提供的一個(gè)標(biāo)準(zhǔn)化的序列化接口,那什么是序列化呢?

進(jìn)行Android開發(fā)的時(shí)候,無法將對(duì)象的引用傳給Activities或者Fragments,我們需要將這些對(duì)象放到一個(gè)Intent或者Bundle里面,然后再傳遞。簡(jiǎn)單來說就是將對(duì)象轉(zhuǎn)換為可以傳輸?shù)亩M(jìn)制流(二進(jìn)制序列)的過程,這樣我們就可以通過序列化,轉(zhuǎn)化為可以在網(wǎng)絡(luò)傳輸或者保存到本地的流(序列),從而進(jìn)行傳輸數(shù)據(jù) ,那反序列化就是從二進(jìn)制流(序列)轉(zhuǎn)化為對(duì)象的過程.

Parcelable是Android為我們提供的序列化的接口,Parcelable相對(duì)于Serializable的使用相對(duì)復(fù)雜一些,但Parcelable的效率相對(duì)Serializable也高很多,這一直是Google工程師引以為傲的,有時(shí)間的可以看一下Parcelable和Serializable的效率對(duì)比 Parcelable vs Serializable 號(hào)稱快10倍的效率

Android源碼中的Parcelable

/**
     * Interface for classes whose instances can be written to
   * and restored from a {@link Parcel}.  Classes implementing the Parcelable
 * interface must also have a non-null static field called <code>CREATOR</code>
 * of a type that implements the {@link Parcelable.Creator} interface.
 * 
 * <p>A typical implementation of Parcelable is:</p>
 * 
 * <pre>
 * public class MyParcelable implements Parcelable {
 *     private int mData;
 *
 *       public int describeContents() {
 *         return 0;
 *     }
 *
 *     public void writeToParcel(Parcel out, int flags) {
 *         out.writeInt(mData);
 *     }
 *
 *     public static final Parcelable.Creator<MyParcelable> CREATOR
 *             = new Parcelable.Creator<MyParcelable>() {
 *         public MyParcelable createFromParcel(Parcel in) {
 *             return new MyParcelable(in);
 *         }
 *
 *         public MyParcelable[] newArray(int size) {
 *             return new MyParcelable[size];
 *         }
 *     };
 *     
 *     private MyParcelable(Parcel in) {
 *         mData = in.readInt();
 *     }
 * }</pre>
 */

通過源碼中的介紹 可以知道,Parcelable接口的實(shí)現(xiàn)類是可以通過Parcel寫入和恢復(fù)數(shù)據(jù)的,并且必須要有一個(gè)非空的靜態(tài)變量 CREATOR,而且還給了一個(gè)例子,這樣我們寫起來就比較簡(jiǎn)單了,但是簡(jiǎn)單的使用并不是我們的最終目的,通過查看Android源碼中Parcelable可以看出,Parcelable實(shí)現(xiàn)過程主要分為序列化,反序列化,描述三個(gè)過程,下面分別介紹下這三個(gè)過程。

Parcel的簡(jiǎn)介

在介紹之前我們需要先了解Parcel是什么?Parcel翻譯過來是打包的意思,其實(shí)就是包裝了我們需要傳輸?shù)臄?shù)據(jù),然后在Binder中傳輸,也就是用于跨進(jìn)程傳輸數(shù)據(jù)

簡(jiǎn)單來說,Parcel提供了一套機(jī)制,可以將序列化之后的數(shù)據(jù)寫入到一個(gè)共享內(nèi)存中,其他進(jìn)程通過Parcel可以從這塊共享內(nèi)存中讀出字節(jié)流,并反序列化成對(duì)象,下圖是這個(gè)過程的模型。


5889165-0bc444ac5c6f505e.png

Parcel可以包含原始數(shù)據(jù)類型(用各種對(duì)應(yīng)的方法寫入,比如writeInt(),writeFloat()等),可以包含Parcelable對(duì)象,它還包含了一個(gè)活動(dòng)的IBinder對(duì)象的引用,這個(gè)引用導(dǎo)致另一端接收到一個(gè)指向這個(gè)IBinder的代理IBinder。
Parcelable通過Parcel實(shí)現(xiàn)了read和write的方法,從而實(shí)現(xiàn)序列化和反序列化

Parcelable中的三大過程介紹(序列化,反序列化,描述)

什么是序列化

序列化,表示將一個(gè)對(duì)象轉(zhuǎn)換成可存儲(chǔ)或可傳輸?shù)臓顟B(tài)。序列化后的對(duì)象可以在網(wǎng)絡(luò)上進(jìn)行傳輸,也可以存儲(chǔ)到本地。

到這里,基本上關(guān)系都理清了,也明白簡(jiǎn)單的介紹和原理了,接下來在實(shí)現(xiàn)Parcelable之前,介紹下實(shí)現(xiàn)Parcelable的三大流程 .
[圖片上傳中...(image.png-73288d-1522235519935-0)]

我先把代碼貼出來

public class Album implements Parcelable {

    /**
     * 負(fù)責(zé)反序列化
     */
    private static final Creator<Album> CREATOR = new Creator<Album>() {
        /**
         * 從序列化對(duì)象中,獲取原始的對(duì)象
         * @param source
         * @return
         */
        @Override
        public Album createFromParcel(Parcel source) {
            return new Album(source);
        }

        /**
         * 創(chuàng)建指定長(zhǎng)度的原始對(duì)象數(shù)組
         * @param size
         * @return
         */
        @Override
        public Album[] newArray(int size) {
            return new Album[0];
        }
    };



    private final String mId;
    private final String mCoverPath;
    private final String mDisplayName;
    private final long mCount;


    Album(String id, String coverPath, String displayName, long count) {
        mId = id;
        mCoverPath = coverPath;
        mDisplayName = displayName;
        mCount = count;
    }

    Album(Parcel source) {
        mId = source.readString();
        mCoverPath = source.readString();
        mDisplayName = source.readString();
        mCount = source.readLong();
    }

    /**
     * 描述
     * 返回的是內(nèi)容的描述信息
     * 只針對(duì)一些特殊的需要描述信息的對(duì)象,需要返回1,其他情況返回0就可以
     *
     * @return
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 序列化
     *
     * @param dest
     * @param flags
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mId);
        dest.writeString(mCoverPath);
        dest.writeString(mDisplayName);
        dest.writeLong(mCount);
    }

實(shí)現(xiàn)Parcelable的作用

1)永久性保存對(duì)象,保存對(duì)象的字節(jié)序列到本地文件中;

2)通過序列化對(duì)象在網(wǎng)絡(luò)中傳遞對(duì)象;

3)通過序列化在進(jìn)程間傳遞對(duì)象。

首先寫一個(gè)類實(shí)現(xiàn)Parcelable接口,會(huì)讓我們實(shí)現(xiàn)兩個(gè)方法:

  • describeContents 描述
    其中describeContents就是負(fù)責(zé)文件描述.通過源碼的描述可以看出,只針對(duì)一些特殊的需要描述信息的對(duì)象,需要返回1,其他情況返回0就可以
  • writeToParcel 序列化
    我們通過writeToParcel方法實(shí)現(xiàn)序列化,writeToParcel返回了Parcel,所以我們可以直接調(diào)用Parcel中的write方法,基本的write方法都有,對(duì)象和集合比較特殊下面單獨(dú)講,基本的數(shù)據(jù)類型除了boolean其他都有,Boolean可以使用int或byte存儲(chǔ)

我們將上面的Album對(duì)象實(shí)現(xiàn)序列化,Album對(duì)象包含四個(gè)字段。

反序列化

反序列化需要定義一個(gè)CREATOR的變量,上面也說了具體的做法,這里可以直接復(fù)制Android給的例子中的,也可以自己定義一個(gè)(名字千萬不能改),通過匿名內(nèi)部類實(shí)現(xiàn)Parcelable中的Creator的接口

Parcelable的使用和實(shí)現(xiàn)

根據(jù)上面三個(gè)過程的介紹,Parcelable就寫完了,就可以直接在Intent中傳輸了,可以自己寫兩個(gè)Activity傳輸一下數(shù)據(jù)試一下,其中一個(gè)putExtra另一個(gè)getParcelableExtra即可。

Parcelable中對(duì)象和集合的處理

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

import java.util.ArrayList;

/**
 * Created by fengxing on 2018/3/28.
 */

public class ParcelDemo implements Parcelable {

    private int count;
    private String name;
    private ArrayList<String> tags;
    private Book book;
    // ***** 注意: 這里如果是集合 ,一定要初始化 *****
    private ArrayList<Book> books = new ArrayList<>();


    /**
     * 序列化
     *
     * @param in
     */
    protected ParcelDemo(Parcel in) {
        count = in.readInt();
        name = in.readString();
        tags = in.createStringArrayList();

        // 讀取對(duì)象需要提供一個(gè)類加載器去讀取,因?yàn)閷懭氲臅r(shí)候?qū)懭肓祟惖南嚓P(guān)信息
        book = in.readParcelable(Book.class.getClassLoader());


        //讀取集合也分為兩類,對(duì)應(yīng)寫入的兩類

        //這一類需要用相應(yīng)的類加載器去獲取
        in.readList(books, Book.class.getClassLoader());// 對(duì)應(yīng)writeList


        //這一類需要使用類的CREATOR去獲取
        in.readTypedList(books, Book.CREATOR); //對(duì)應(yīng)writeTypeList

        //books = in.createTypedArrayList(Book.CREATOR); //對(duì)應(yīng)writeTypeList


        //這里獲取類加載器主要有幾種方式
        getClass().getClassLoader();
        Thread.currentThread().getContextClassLoader();
        Book.class.getClassLoader();


    }

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

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

    /**
     * 描述
     *
     * @return
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 反序列化
     *
     * @param dest
     * @param flags
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(count);
        dest.writeString(name);
        //序列化一個(gè)String的集合
        dest.writeStringList(tags);
        // 序列化對(duì)象的時(shí)候傳入要序列化的對(duì)象和一個(gè)flag,
        // 這里的flag幾乎都是0,除非標(biāo)識(shí)當(dāng)前對(duì)象需要作為返回值返回,不能立即釋放資源
        dest.writeParcelable(book, 0);

        // 序列化一個(gè)對(duì)象的集合有兩種方式,以下兩種方式都可以


        //這些方法們把類的信息和數(shù)據(jù)都寫入Parcel,以使將來能使用合適的類裝載器重新構(gòu)造類的實(shí)例.所以效率不高
        dest.writeList(books);


        //這些方法不會(huì)寫入類的信息,取而代之的是:讀取時(shí)必須能知道數(shù)據(jù)屬于哪個(gè)類并傳入正確的Parcelable.Creator來創(chuàng)建對(duì)象
        // 而不是直接構(gòu)造新對(duì)象。(更加高效的讀寫單個(gè)Parcelable對(duì)象的方法是:
        // 直接調(diào)用Parcelable.writeToParcel()和Parcelable.Creator.createFromParcel())
        dest.writeTypedList(books);


    }
}

Book類,需要先實(shí)現(xiàn)Parcelable,實(shí)現(xiàn)步驟就不貼出來了,和普通的對(duì)象一樣,實(shí)現(xiàn)三個(gè)過程

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

/**
 * Created by fengxing on 2018/3/28.
 */

public class Book implements Parcelable {

    protected Book(Parcel in) {
    }

    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 dest, int flags) {
    }
}

寫入和讀取集合有兩種方式,
一種是寫入類的相關(guān)信息,然后通過類加載器去讀取, –> writeList | readList
二是不用類相關(guān)信息,創(chuàng)建時(shí)傳入相關(guān)類的CREATOR來創(chuàng)建 –> writeTypeList | readTypeList | createTypedArrayList
第二種效率高一些
一定要注意如果有集合定義的時(shí)候一定要初始化 like this –>
public ArrayList<T> demo = new ArrayList<>();

Parcelable和Serializable的區(qū)別和比較

Parcelable和Serializable都是實(shí)現(xiàn)序列化并且都可以用于Intent間傳遞數(shù)據(jù),Serializable是Java的實(shí)現(xiàn)方式,可能會(huì)頻繁的IO操作,所以消耗比較大,但是實(shí)現(xiàn)方式簡(jiǎn)單 Parcelable是Android提供的方式,效率比較高,但是實(shí)現(xiàn)起來復(fù)雜一些 , 二者的選取規(guī)則是:內(nèi)存序列化上選擇Parcelable, 存儲(chǔ)到設(shè)備或者網(wǎng)絡(luò)傳輸上選擇Serializable(當(dāng)然Parcelable也可以但是稍顯復(fù)雜)

選擇序列化方法的原則

1)在使用內(nèi)存的時(shí)候,Parcelable比Serializable性能高,所以推薦使用Parcelable。

2)Serializable在序列化的時(shí)候會(huì)產(chǎn)生大量的臨時(shí)變量,從而引起頻繁的GC。

3)Parcelable不能使用在要將數(shù)據(jù)存儲(chǔ)在磁盤上的情況,因?yàn)镻arcelable不能很好的保證數(shù)據(jù)的持續(xù)性在外界有變化的情況下。盡管Serializable效率低點(diǎn),但此時(shí)還是建議使用Serializable 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容