Android Parcelable原理分析

前言

在Android中,一般使用序列化時,谷歌官方都推薦咱們使用Parcelable來實現,由於效率比jdk提供的Serializable要高不少(大約10倍)。java

這裏咱們首先先探討一下Parcelable怎麼用,而後從源碼出發解讀Parcelable的效率爲何這麼高。最後分析一下Parcelable的應用場景,以及和Serializable的區別。android

如何使用

按照以下方式定義一個實現了Parcelable的POJO對象,c++

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

public class Book implements Parcelable {
    private int id;
    private String name;

    //setter & getter & constructor
    //...

    //下面是實現Parcelable接口的內容
    //除了要序列化特殊的文件描述符場景外,通常返回零就能夠了
    @Override
    public int describeContents() {
        return 0;
    }

    //序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }

    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];
        }
    };

    //根據反序列化獲得的各個屬性,生成與以前對象內容相同的對象
    private Book(Parcel in) {
        //切記反序列化的屬性的順序必須和以前寫入的順序一致!!
        id = in.readInt();
        name = in.readString();
    }
}
複製代碼

如下代碼展現瞭如何在Activity之間經過序列化的方式傳遞Book對象的數據,ide

//傳遞
Book book = new Book(123, "Hello world");
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("book_data", book);
startActivity(intent);

//接受
Book book = (Book) getIntent.getParcelableExtra("book_data);
複製代碼

分析源碼

咱們進入源碼查看,Parcelable只是一個接口,實現是交給Parcel對象進行的。好比咱們在writeToParcel時會調用Parcel類中的方法,進入其中能夠看到實現是交給native作的,函數

...
public final void writeInt(int val) {
    nativeWriteInt(mNativePtr, val);
}
...
@FastNative
private static native void nativeWriteInt(long nativePtr, int val);
@FastNative
private static native void nativeWriteLong(long nativePtr, long val);
@FastNative
private static native void nativeWriteFloat(long nativePtr, float val);
...
複製代碼

既然是native實現,就須要去看Android源碼了。咱們打開androidxref.com網站(可能須要fq,國內有個鏡像站推薦一下aospxref.com),在Android源碼中搜索nativeWriteInt,定位到對應的c++實現位於目錄frameworks/base/core/jni/android_os_Parcel.cpp,有興趣的朋友能夠瀏覽一下。網站

Android源碼裏不少native方法都是動態註冊的,這裏再也不贅述如何找到對應c的實現,咱們直接往下看,this

static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
    //經過指針強轉拿到native層的Parcel對象
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        //最終仍是調用的Parcel中的writeInt32函數
        const status_t err = parcel->writeInt32(val);
        if (err != NO_ERROR) {
            signalExceptionForError(env, clazz, err);
        }
    }
}

...

//實際調用的是一個通用的模版方法
status_t Parcel::writeInt32(int32_t val)
{
    return writeAligned(val);
}

...

//模版方法
//其中
//mData表示指向Parcel內存的首地址
//mDataPos表示指向Parcel空閒內存的首地址
//mDataCapacity表示Parcel分配內存的大小
template<class T> status_t Parcel::writeAligned(T val) {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
    //先判斷加上val後會不會超過可用大小
    if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
        //reinterpret_cast是c++的再解釋強制轉換操做
        //首先會計算mData + mDataPos獲得物理地址,轉成指向T類型的指針(T類型就是實際入參的類型)
        //而後將val賦值給指針指向的內容
        *reinterpret_cast<T*>(mData+mDataPos) = val;
        //主要邏輯是修改mDataPos的偏移地址
        //將偏移地址加上新增長的數據的字節數
        return finishWrite(sizeof(val));
    }
    //若是超過了可用大小,執行增加函數
    //以後再goto到上面的restart_write標籤執行寫入邏輯
    status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write;
    return err;
}
複製代碼

經過上述代碼分析,writeInt32函數會將數據寫入到一段共享的內存中,因此同理咱們在readInt時,也是經過Parcel對象從該段內存中讀取對應的值的。作同理分析,以下,spa

template<class T> status_t Parcel::readAligned(T *pArg) const {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
    if ((mDataPos+sizeof(T)) <= mDataSize) {
        //獲取讀取數據的地址
        const void* data = mData+mDataPos;
        //mDataPos指向下一個數據
        mDataPos += sizeof(T);
        //根據數據指針類型取出數據
        *pArg =  *reinterpret_cast<const T*>(data);
        return NO_ERROR;
    } else {
        return NOT_ENOUGH_DATA;
    }
}
複製代碼

寫數據時在一塊Parcel內存地址中,寫入12,34;讀取數據時從起始地址(Android系統在讀取時會將mDataPos重置爲起始值)+指針類型對應的字節數來一一讀取,首先讀取12,而後讀取34。指針

這也就是爲何咱們在寫序列化方法時,必定要將對應的成員變量的讀取/寫入順序保持一致的緣由。rest

與Serializable的區別

  1. Parcelable在對與只須要進行內存序列化的操做時很快,由於Serializable須要頻繁的進行I/O。
  2. Parcelable實現較爲複雜且要注意讀寫順序的一致性,Serializable相對來講實現很簡單。
  3. Parcelable不適合用於作數據持久化,而Serializable適合。
相關文章
相關標籤/搜索