Android Parcel對象詳解

來源:互聯網
上載者:User

標籤:一點   建立   存在   一個   and   bsp   分析   包含   template   

關於Parcel的使用

在分析Parcel之前,首先按照分析流程,介紹下關於Parcel的相關常規使用。

首先是關於Parcel的擷取:

Parcel parcle = Parcel.Obtain();

 

額,這感覺似曾相識啊,我們這裡大膽猜測Parcel的初始化也是由其對象池進行初始化的。在得到了Parcel對象之後,下一步的工作。嗯,我想起來,應該介紹下Parcel的作用吧:

其實看到這篇文章的各位,應該也不需要這種科普吧,哈哈。我從源碼注釋中截取如下:

*Container for a message (data and object references) that can
* be sent through an IBinder. A Parcel can contain both flattened data
* that will be unflattened on the other side of the IPC (using the various
* methods here for writing specific types, or the general

從這段注釋中可以看出,Parcel是一個容器,他可以包含資料或者是對象引用,並且能夠用於Binder的傳輸。同時支援序列化以及跨進程之後進行還原序列化,同時其提供了很多方法協助開發人員完成這些功能。ok,對這裡差不多明朗了,Parcel主要就是用來進行IPC通訊的。當然不僅僅是Binder這一種跨進程通訊。

接下來回到這題,既然Parcel是一個容器,那麼肯定需要向其中傳入資料才行啊,沒錯,所以在初始化Parcel之後,需要進行如下操作:

parcel.writeInt(int val);

 

向Parcel中傳入一個Int型的資料,接下來還有:

parcel.writeString(String val);

 

向Parcel中傳入一個String型的資料。

這裡只以這兩種最為常見的資料類型的寫入作為例子,實際上Parcel所支援的資料類型可多了去了,具體可以如所示:

在完成了資料的寫入之後,就需要進行資料的序列化:

parcel.marshall();

 

在經過上一步的處理之後,返回了一個byte數組,主要的IPC相關的操作主要就是圍繞此byte數組進行的。同時,由於parcel的讀寫都是一個指標操作的,這一步涉及到native的操作,所以,在將資料寫入之後,需要將指標手動指向到最初的位置,即如下的操作:

parcel.setDataPosition(0);

 

到此處,Parcel的這一步操作還沒有收尾,想想前面parcel的Obtain()方法,我們有理由相信,parcel的銷毀應該是使用了對應的recycle()方法。

所以此處有:

parcel.recycle();

 

將此Parcel對象進行釋放,完成了IPC操作的一半。至於是如何將資料轉送過去的,暫不進行展開。此處在IPC的另一端的Parcel的擷取處理。

再進行了IPC的操作之後,一般讀取出來的就是之前序列化的byte數組,所以,首先要進行一個還原序列化操作,即如下的操作:

parcel.unmarshall(byte[] data, int offest, int length);

 

其中的參數分別是這個byte數組,以及讀取位移量,以及數組的長度。

此時得到的parcel就是一個正常的parcel對象,這時就可以將之前我們所存入的資料按照順序進行擷取,即:

parcel.readInt();

 

以及

parcel.readString();

 

讀取完畢之後,同樣是一個parcel的回收操作:

parcel.recycle();

 

以上就是parcel的常規使用,擷取有些朋友不太知道parcel的使用情境,其實最常見的,在我們編寫完AIDL的介面之後,IDE會自動產生一個對應的.java檔案,這個java檔案就是實際用來進行aidl的通訊的,在這個實現裡面,資料的傳遞就是使用的parcel,當然還有其他的應用情境,這裡只說了一個大家都比較常見的實踐。

關於Parcel的實現

之前有提到過,parcel的使用對於java開發人員來說,還是比較陌生的,像極了指標的操作,所以基本可以確定java層對於parcel的處理僅僅是一個封裝代理,實際的實現在c/c++ native。既然這樣的話,我們就應該想到,parcel的使用同樣涉及到jni的使用。所以我們目前的思路就是在源碼中找到parcel的三層代碼(Java-Jni-C)。

我的具體做法是直接使用 everything 在源碼目錄下搜尋 parcel,然後根據之前的思路進行包的匯出,我的分析基礎就是以下的幾個包裡的實現:

Java層:

\frameworks\base\core\java\android\os

 

JNI:

\frameworks\base\core\jni

 

native:

\frameworks\native\libs\binder

 

然後匯入這幾個包中的檔案方便檢索:

根據我們以上的使用順序來進行分析,首先需要進行一個Parcel的擷取,看看Java層的實現:

 /**     * Retrieve a new Parcel object from the pool.     */    public static Parcel obtain() {        final Parcel[] pool = sOwnedPool;        synchronized (pool) {            Parcel p;            for (int i=0; i<POOL_SIZE; i++) {                p = pool[i];                if (p != null) {                    pool[i] = null;                    if (DEBUG_RECYCLE) {                        p.mStack = new RuntimeException();                    }                    return p;                }            }        }        return new Parcel(0);    }

 

從注釋也可以看出,Parcle的初始化,主要是使用一個對象池進行的,這樣可以提高效能以及記憶體消耗。首先要明確的是,源碼中定義的池子有兩個:

    private static final int POOL_SIZE = 6;    private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];    private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];

 

從名字也可以看出,不同池子的作用,sOwnedPool 這個池子主要就是用來儲存parcel的,Obtain()方法首先會去檢索池子中的parcel對象,若是能取出parcel,那麼先將這個這個parcel返回,同時將這個位置置空。若是現在連池子都不存在的話,那麼就直接建立一個parcel對象。這裡的實現與Handler中的message採用同樣的處理。

我們瞭解了擷取之後,比較關心的就是如何去建立一個parcel對象,也就是new這個過程,那麼看看此處中的parcel構造方法:

private Parcel(int nativePtr) {        if (DEBUG_RECYCLE) {            mStack = new RuntimeException();        }        //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);        init(nativePtr);    }

 

可以看到,在此處參數名稱被稱為:nativePtr,這個大家都比較熟悉了,ptr嘛,指的就是一個指標,這裡又是一個封裝,需要繼續深入看實現:

private void init(int nativePtr) {        if (nativePtr != 0) {            mNativePtr = nativePtr;            mOwnsNativeParcelObject = false;        } else {            mNativePtr = nativeCreate();            mOwnsNativeParcelObject = true;        }    }

 

這裡首先對參數進行檢查,這裡因為初始化傳入的參數是0,那麼直接執行nativeCreate(),並且將標誌位mOwnsNativeParcelObject 置為true,表示這個 parcel已經在native進行了建立。

此處的ativeCreate()是一個native方法,其具體實現已經切換到native環境了,那麼我們此時的分析就要從jni進行了,經過檢索,在jni的代碼中,其實現為以下函數:

static jint android_os_Parcel_create(JNIEnv* env, jclass clazz){    Parcel* parcel = new Parcel();    return reinterpret_cast<jint>(parcel);}

 

這是一個jni的實現,首先是調用了native的初始化,並且,返回操作這個對象的指標:

Parcel::Parcel(){    initState();}

 

是一個c++的構造方法,關於析構方法,暫時不管,其中的init實現為:

void Parcel::initState(){    mError = NO_ERROR;    mData = 0;    mDataSize = 0;    mDataCapacity = 0;    mDataPos = 0;    ALOGV("initState Setting data size of %p to %d\n", this, mDataSize);    ALOGV("initState Setting data pos of %p to %d\n", this, mDataPos);    mObjects = NULL;    mObjectsSize = 0;    mObjectsCapacity = 0;    mNextObjectHint = 0;    mHasFds = false;    mFdsKnown = true;    mAllowFds = true;    mOwner = NULL;}

 

可以看出,對parcel的初始化,只是在native層初始化了一些資料值。

在完成初始化之後,就將這個操作指標給返回。這樣就完成了parcel的初始化。

初始化完畢之後,就可以進行資料的寫入了,首先寫入一個int型資料,其java層實現如下:

 /**     * Write an integer value into the parcel at the current dataPosition(),     * growing dataCapacity() if needed.     */    public final void writeInt(int val) {        nativeWriteInt(mNativePtr, val);    }

 

可以看出,在這裡java層就純粹是一個對於native實現的封裝了,這時候的分析來到jni:

static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jint nativePtr, jint val) {    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);    const status_t err = parcel->writeInt32(val);    if (err != NO_ERROR) {        signalExceptionForError(env, clazz, err);    }}

 

在這裡我們要特別注意兩個參數,一個是之前傳上去的指標以及需要儲存的int資料,這兩個值分別是:

jint nativePtr, jint val

 

首先是根據這個指標,這裡說一下,指標實際上就是一個整型地址值,所以這裡使用強轉將int值轉化為parcel類型的指標是可行的,然後使用這個指標來操作native的parcel對象,即:

const status_t err = parcel->writeInt32(val);

 

這裡注意到我們是寫入了一個32位的int值,這個點一定要注意,32位,4個位元組。

深入進去看看實現:

status_t Parcel::writeInt32(int32_t val){    return writeAligned(val);}

 

可以看出,這裡實際上調用了:

writeAligned(val);

來進行資料的寫入,這裡理解下align的意思,實際上是一個對齊寫入,怎麼個對齊法,看看:

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

 

在這個方法中首先是一個斷言檢查,然後對輸入的參數取size值,再加上之前已經移動的位置,判斷是否超過了該Pacel所定義的能力值mDataCapacity。

若是超過了能力值的話,那麼直接將能力值進行擴大,擴大的值是val值的大小,比如,int值是32bit,那麼就增加4個位元組,返回的結果是狀態值,若是沒有出錯的話,就利用goto語句執行,這裡的goto的語句只要是一個指標的操作,將指標移動到端點,然後寫入val的size值。這裡可以看出這個函數的意義,因為無論是否超過能力值它都會寫入T類型值的size值。

到這裡,Parcel就寫入了一個Int型的值。

同樣的思路,大家可以參考以上的分析,繼續進行Parcel一個常規使用的分析,我之前是想將全部的實現都分析出來的,但是後來發現,大體的思路都差不多,這麼寫的話,會多出來很多廢話,所以接下來的分析,大家如果有興趣的話,就繼續分析下去,歡迎一起進行討論!

另外,在分析過程中,我對Android的JNI調用進行一番探索,總之一句話就是說Jvm環境切換到Native環境之中後,Java如何通過Java層聲明的native方法來尋找到對應的JNI方法的?因為我對JVM的實現這一部分沒有太多瞭解,所以只能從Android源碼中代碼層面上來分析,至少在Android中:

在切換到native環境之後,實際上,這兩種函數的映射是由一個多重數組來進行管理的,具體如下:

static const JNINativeMethod gParcelMethods[] = {    {"nativeDataSize",            "(I)I", (void*)android_os_Parcel_dataSize},    {"nativeDataAvail",           "(I)I", (void*)android_os_Parcel_dataAvail},    {"nativeDataPosition",        "(I)I", (void*)android_os_Parcel_dataPosition},    {"nativeDataCapacity",        "(I)I", (void*)android_os_Parcel_dataCapacity},    {"nativeSetDataSize",         "(II)V", (void*)android_os_Parcel_setDataSize},    {"nativeSetDataPosition",     "(II)V", (void*)android_os_Parcel_setDataPosition},    {"nativeSetDataCapacity",     "(II)V", (void*)android_os_Parcel_setDataCapacity},    {"nativePushAllowFds",        "(IZ)Z", (void*)android_os_Parcel_pushAllowFds},    {"nativeRestoreAllowFds",     "(IZ)V", (void*)android_os_Parcel_restoreAllowFds},    {"nativeWriteByteArray",      "(I[BII)V", (void*)android_os_Parcel_writeNative},    {"nativeWriteInt",            "(II)V", (void*)android_os_Parcel_writeInt},    {"nativeWriteLong",           "(IJ)V", (void*)android_os_Parcel_writeLong},    {"nativeWriteFloat",          "(IF)V", (void*)android_os_Parcel_writeFloat},    {"nativeWriteDouble",         "(ID)V", (void*)android_os_Parcel_writeDouble},    {"nativeWriteString",         "(ILjava/lang/String;)V", (void*)android_os_Parcel_writeString},    {"nativeWriteStrongBinder",   "(ILandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},    {"nativeWriteFileDescriptor", "(ILjava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor},    {"nativeCreateByteArray",     "(I)[B", (void*)android_os_Parcel_createByteArray},    {"nativeReadInt",             "(I)I", (void*)android_os_Parcel_readInt},    {"nativeReadLong",            "(I)J", (void*)android_os_Parcel_readLong},    {"nativeReadFloat",           "(I)F", (void*)android_os_Parcel_readFloat},    {"nativeReadDouble",          "(I)D", (void*)android_os_Parcel_readDouble},    {"nativeReadString",          "(I)Ljava/lang/String;", (void*)android_os_Parcel_readString},    {"nativeReadStrongBinder",    "(I)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},    {"nativeReadFileDescriptor",  "(I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},........

 

以下還有很多映射關係,這樣通過映射就可以將函數給進行對應了,但是還有一點,這個東西是何時,以及何處進行調用的,這個展開說又是一個漫長的故事了,所以這一段我也不進行分析了,大家知道有這麼一個東西就ok了,當然歡迎一起進行討論哦!

Android Parcel對象詳解

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.