Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android編程入門 >> Android序列化之Serializable和Parcelable

Android序列化之Serializable和Parcelable

編輯:Android編程入門

PS:還有幾天就開學了.先來一發.

 

學習內容:

1.序列化的目的

2.Android中序列化的兩種方式

3.Parcelable與Serializable的性能比較

4.Android中如何使用Parcelable進行序列化操作

5.Parcelable的工作原理

6.相關實例

 

 

1.序列化的目的

        1.永久的保存對象數據(將對象數據保存在文件當中,或者是磁盤中

  2.通過序列化操作將對象數據在網絡上進行傳輸(由於網絡傳輸是以字節流的方式對數據進行傳輸的.因此序列化的目的是將對象數據轉換成字節流的形式)

  3.將對象數據在進程之間進行傳遞(Activity之間傳遞對象數據時,需要在當前的Activity中對對象數據進行序列化操作.在另一個Activity中需要進行反序列化操作講數據取出)

  4.Java平台允許我們在內存中創建可復用的Java對象,但一般情況下,只有當JVM處於運行時,這些對象才可能存在,即,這些對象的生命周期不會比JVM的生命周期更長(即每個對象都在JVM中)但在現實應用中,就可能要停止JVM運行,但有要保存某些指定的對象,並在將來重新讀取被保存的對象。這是Java對象序列化就能夠實現該功能。(可選擇入數據庫、或文件的形式保存)

  5.序列化對象的時候只是針對變量進行序列化,不針對方法進行序列化.

  6.在Intent之間,基本的數據類型直接進行相關傳遞即可,但是一旦數據類型比較復雜的時候,就需要進行序列化操作了.

2.Android中實現序列化的兩種方式

  1.Implements Serializable 接口 (聲明一下即可)

Serializable 的簡單實例:

public class Person implements Serializable{
    private static final long serialVersionUID = -7060210544600464481L;
    private String name;
    private int age;
    
    public String getName(){
        return name;
    }
    
    public void setName(String name){
        this.name = name;
    }
    
    public int getAge(){
        return age;
    }
    
    public void setAge(int age){
        this.age = age;
    }
}

  2.Implements Parcelable 接口(不僅僅需要聲明,還需要實現內部的相應方法)

Parcelable的簡單實例:

注:寫入數據的順序和讀出數據的順序必須是相同的.
public class Book implements Parcelable{
    private String bookName;
    private String author;
    private int publishDate;
    
    public Book(){
        
    }
    
    public String getBookName(){
        return bookName;
    }
    
    public void setBookName(String bookName){
        this.bookName = bookName;
    }
    
    public String getAuthor(){
        return author;
    }
    
    public void setAuthor(String author){
        this.author = author;
    }
    
    public int getPublishDate(){
        return publishDate;
    }
    
    public void setPublishDate(int publishDate){
        this.publishDate = publishDate;
    }
    
    @Override
    public int describeContents(){
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel out, int flags){
        out.writeString(bookName);
        out.writeString(author);
        out.writeInt(publishDate);
    }
    
    public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>(){
        
     @Override public Book[] newArray(int size){ return new Book[size]; } @Override public Book createFromParcel(Parcel in){ return new Book(in); } }; public Book(Parcel in){ //如果元素數據是list類型的時候需要: lits = new ArrayList<?> in.readList(list); 否則會出現空指針異常.並且讀出和寫入的數據類型必須相同.如果不想對部分關鍵字進行序列化,可以使用transient關鍵字來修飾以及static修飾. bookName = in.readString(); author = in.readString(); publishDate = in.readInt(); } }

  我們知道在Java應用程序當中對類進行序列化操作只需要實現Serializable接口就可以,由系統來完成序列化和反序列化操作,但是在Android中序列化操作有另外一種方式來完成,那就是實現Parcelable接口.也是Android中特有的接口來實現類的序列化操作.原因是Parcelable的性能要強於Serializable.因此在絕大多數的情況下,Android還是推薦使用Parcelable來完成對類的序列化操作的.

3.Parcelable與Serializable的性能比較

  首先Parcelable的性能要強於Serializable的原因我需要簡單的闡述一下

  1. 在內存的使用中,前者在性能方面要強於後者

  2. 後者在序列化操作的時候會產生大量的臨時變量,(原因是使用了反射機制)從而導致GC的頻繁調用,因此在性能上會稍微遜色

  3. Parcelable是以Ibinder作為信息載體的.在內存上的開銷比較小,因此在內存之間進行數據傳遞的時候,Android推薦使用Parcelable,既然是內存方面比價有優勢,那麼自然就要優先選擇.

  4. 在讀寫數據的時候,Parcelable是在內存中直接進行讀寫,而Serializable是通過使用IO流的形式將數據讀寫入在硬盤上.

  但是:雖然Parcelable的性能要強於Serializable,但是仍然有特殊的情況需要使用Serializable,而不去使用Parcelable,因為Parcelable無法將數據進行持久化,因此在將數據保存在磁盤的時候,仍然需要使用後者,因為前者無法很好的將數據進行持久化.(原因是在不同的Android版本當中,Parcelable可能會不同,因此數據的持久化方面仍然是使用Serializable)

  速度測試:

  測試方法:

 

  • 通過將一個對象放到一個bundle裡面然後調用Bundle#writeToParcel(Parcel, int)方法來模擬傳遞對象給一個activity的過程,然後再把這個對象取出來。
  • 在一個循環裡面運行1000 次。
  • 兩種方法分別運行10次來減少內存整理,cpu被其他應用占用等情況的干擾。
  • 參與測試的對象就是上面的相關代碼
  • 在多種Android軟硬件環境上進行測試
    • LG Nexus 4 – Android 4.2.2
    • Samsung Nexus 10 – Android 4.2.2
    • HTC Desire Z – Android 2.3.3

 

  結果如圖:

 

 

  性能差異:

    Nexus 10

    Serializable: 1.0004ms,  Parcelable: 0.0850ms – 提升10.16倍。

  Nexus 4

  Serializable: 1.8539ms – Parcelable: 0.1824ms – 提升11.80倍。

  Desire Z

  Serializable: 5.1224ms – Parcelable: 0.2938ms – 提升17.36倍。

   由此可以得出: Parcelable 比 Serializable快了10多倍。

  從相對的比較我們可以看出,Parcelable的性能要比Serializable要優秀的多,因此在Android中進行序列化操作的時候,我們需要盡可能的選擇前者,需要花上大量的時間去實現Parcelable接口中的內部方法.

4.Android中如何使用Parcelable進行序列化操作

  說了這麼多,我們還是來看看Android中如何去使用Parcelable實現類的序列化操作吧.

   Implements Parcelable的時候需要實現內部的方法:

  1.writeToParcel 將對象數據序列化成一個Parcel對象(序列化之後成為Parcel對象.以便Parcel容器取出數據)

  2.重寫describeContents方法,默認值為0

  3.Public static final Parcelable.Creator<T>CREATOR (將Parcel容器中的數據轉換成對象數據) 同時需要實現兩個方法:
  3.1 CreateFromParcel(從Parcel容器中取出數據並進行轉換.)
  3.2 newArray(int size)返回對象數據的大小

  因此,很明顯實現Parcelable並不容易。實現Parcelable接口需要寫大量的模板代碼,這使得對象代碼變得難以閱讀和維護。具體的實例就是上面Parcelable的實例代碼.就不進行列舉了.(有興趣的可以去看看Android中NetWorkInfo的源代碼,是關於網絡連接額外信息的一個相關類,內部就實現了序列化操作.大家可以去看看)

5.Parcelable的工作原理

  無論是對數據的讀還是寫都需要使用Parcel作為中間層將數據進行傳遞.Parcel涉及到的東西就是與C++底層有關了.都是使用JNI.在Java應用層是先創建Parcel(Java)對象,然後再調用相關的讀寫操作的時候.就拿讀寫32為Int數據來說吧:

static jint android_os_Parcel_readInt(JNIEnv* env, jobject clazz){
    Parcel* parcel = parcelForJavaObject(env, clazz);
    if (parcel != NULL) {
        return parcel->readInt32();
    }
    return 0;
}

調用的方法就是這個過程,首先是將Parcel(Java)對象轉換成Parcel(C++)對象,然後被封裝在Parcel中的相關數據由C++底層來完成數據的序列化操作.

status_t Parcel::writeInt32(int32_t val){
    return writeAligned(val);
} 
template<class t="">
status_t Parcel::writeAligned(T val) {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
 
    if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
        *reinterpret_cast<t*>(mData+mDataPos) = val;
        return finishWrite(sizeof(val));
    }
 
    status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write;
    return err;
}

  真正的讀寫過程是由下面的源代碼來完成的.

status_t Parcel::continueWrite(size_t desired)
{
    // If shrinking, first adjust for any objects that appear
    // after the new data size.
    size_t objectsSize = mObjectsSize;
    if (desired < mDataSize) {
        if (desired == 0) {
            objectsSize = 0;
        } else {
            while (objectsSize > 0) {
                if (mObjects[objectsSize-1] < desired)
                    break;
                objectsSize--;
            }
        }
    }
    
    if (mOwner) {
        // If the size is going to zero, just release the owner's data.
        if (desired == 0) {
            freeData();
            return NO_ERROR;
        }

        // If there is a different owner, we need to take
        // posession.
        uint8_t* data = (uint8_t*)malloc(desired);
        if (!data) {
            mError = NO_MEMORY;
            return NO_MEMORY;
        }
        size_t* objects = NULL;
        
        if (objectsSize) {
            objects = (size_t*)malloc(objectsSize*sizeof(size_t));
            if (!objects) {
                mError = NO_MEMORY;
                return NO_MEMORY;
            }

            // Little hack to only acquire references on objects
            // we will be keeping.
            size_t oldObjectsSize = mObjectsSize;
            mObjectsSize = objectsSize;
            acquireObjects();
            mObjectsSize = oldObjectsSize;
        }
        
        if (mData) {
            memcpy(data, mData, mDataSize < desired ? mDataSize : desired);
        }
        if (objects && mObjects) {
            memcpy(objects, mObjects, objectsSize*sizeof(size_t));
        }
        //ALOGI("Freeing data ref of %p (pid=%d)\n", this, getpid());
        mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie);
        mOwner = NULL;

        mData = data;
        mObjects = objects;
        mDataSize = (mDataSize < desired) ? mDataSize : desired;
        ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize);
        mDataCapacity = desired;
        mObjectsSize = mObjectsCapacity = objectsSize;
        mNextObjectHint = 0;

    } else if (mData) {
        if (objectsSize < mObjectsSize) {
            // Need to release refs on any objects we are dropping.
            const sp<ProcessState> proc(ProcessState::self());
            for (size_t i=objectsSize; i<mObjectsSize; i++) {
                const flat_binder_object* flat
                    = reinterpret_cast<flat_binder_object*>(mData+mObjects[i]);
                if (flat->type == BINDER_TYPE_FD) {
                    // will need to rescan because we may have lopped off the only FDs
                    mFdsKnown = false;
                }
                release_object(proc, *flat, this);
            }
            size_t* objects =
                (size_t*)realloc(mObjects, objectsSize*sizeof(size_t));
            if (objects) {
                mObjects = objects;
            }
            mObjectsSize = objectsSize;
            mNextObjectHint = 0;
        }

        // We own the data, so we can just do a realloc().
        if (desired > mDataCapacity) {
            uint8_t* data = (uint8_t*)realloc(mData, desired);
            if (data) {
                mData = data;
                mDataCapacity = desired;
            } else if (desired > mDataCapacity) {
                mError = NO_MEMORY;
                return NO_MEMORY;
            }
        } else {
            if (mDataSize > desired) {
                mDataSize = desired;
                ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize);
            }
            if (mDataPos > desired) {
                mDataPos = desired;
                ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos);
            }
        }
        
    } else {
        // This is the first data.  Easy!
        uint8_t* data = (uint8_t*)malloc(desired);
        if (!data) {
            mError = NO_MEMORY;
            return NO_MEMORY;
        }
        
        if(!(mDataCapacity == 0 && mObjects == NULL
             && mObjectsCapacity == 0)) {
            ALOGE("continueWrite: %d/%p/%d/%d", mDataCapacity, mObjects, mObjectsCapacity, desired);
        }
        
        mData = data;
        mDataSize = mDataPos = 0;
        ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize);
        ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos);
        mDataCapacity = desired;
    }

    return NO_ERROR;
}

   1.整個讀寫全是在內存中進行,主要是通過malloc()、realloc()、memcpy()等內存操作進行,所以效率比JAVA序列化中使用外部存儲器會高很多

   2.讀寫時是4字節對齊的,可以看到#define PAD_SIZE(s) (((s)+3)&~3)這句宏定義就是在做這件事情

   3.如果預分配的空間不夠時newSize = ((mDataSize+len)*3)/2;會一次多分配50%

   4.對於普通數據,使用的是mData內存地址,對於IBinder類型的數據以及FileDescriptor使用的是mObjects內存地址。後者是通過flatten_binder()和unflatten_binder()實現的,目的是反序列化時讀出的對象就是原對象而不用重新new一個新對象。
6.相關實例

  最後上一個例子..

  首先是序列化的類Book.class

public class Book implements Parcelable{
    private String bookName;
    private String author;
    private int publishDate;
    
    public Book(){
        
    }
    
    public String getBookName(){
        return bookName;
    }
    
    public void setBookName(String bookName){
        this.bookName = bookName;
    }
    
    public String getAuthor(){
        return author;
    }
    
    public void setAuthor(String author){
        this.author = author;
    }
    
    public int getPublishDate(){
        return publishDate;
    }
    
    public void setPublishDate(int publishDate){
        this.publishDate = publishDate;
    }
    
    @Override
    public int describeContents(){
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel out, int flags){
        out.writeString(bookName);
        out.writeString(author);
        out.writeInt(publishDate);
    }
    
    public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>(){
        
     @Override
        public Book[] newArray(int size){
            return new Book[size];
        }
        
        @Override
        public Book createFromParcel(Parcel in){
            return new Book(in);
        }
    };
    
    public Book(Parcel in){
        //如果元素數據是list類型的時候需要: lits = new ArrayList<?> in.readList(list); 否則會出現空指針異常.並且讀出和寫入的數據類型必須相同.如果不想對部分關鍵字進行序列化,可以使用transient關鍵字來修飾以及static修飾.
        bookName = in.readString();
        author = in.readString();
        publishDate = in.readInt();
    }
}

 

   第一個Activity,MainActivity

Book book = new Book();
book.setBookname("Darker");
book.setBookauthor("me");
book.setPublishDate(20);
Bundle bundle = new Bundle();
bundle.putParcelable("book", book);
Intent intent = new Intent(MainActivity.this,AnotherActivity.class);
intent.putExtras(bundle);

  第二個Activity,AnotherActivity

Intent intent = getIntent();
Bundle bun = intent.getExtras();
Book book = bun.getParcelable("book");
System.out.println(book);

 總結:Java應用程序中有Serializable來實現序列化操作,Android中有Parcelable來實現序列化操作,相關的性能也作出了比較,因此在Android中除了對數據持久化的時候需要使用到Serializable來實現序列化操作,其他的時候我們仍然需要使用Parcelable來實現序列化操作,因為在Android中效率並不是最重要的,而是內存,通過比較Parcelable在效率和內存上都要優秀與Serializable,盡管Parcelable實現起來比較復雜,但是如果我們想要成為一名優秀的Android軟件工程師,那麼我們就需要勤快一些去實現Parcelable,而不是偷懶與實現Serializable.當然實現後者也不是不行,關鍵在於我們頭腦中的那一份思想.

 

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved