1. 程式人生 > >專注於Linux & Android & ARM

專注於Linux & Android & ARM

Parcel,即打包。為什麼需要打包呢?是為了序列化。

如果要在程序之間傳遞一個整數,很簡單,直接傳就行了;如果要傳一個字串,就稍微複雜了點:需先分配一塊可以容納字串的記憶體,然後將字串複製到記憶體中,再傳遞(新手可能問:為啥不直接把字串的引用傳過去呢?學過C/C++的地球人都知道:程序有自己的記憶體地址空間,一個程序中的1000地址可能在另一個程序中是100000,java物件的引用跟本上還是記憶體地址);再如果要傳遞一個類的例項呢?也是先為例項分配記憶體,然後複製一份再傳遞可以嗎?我認為不可以,我至少可以找到一個理由:類中成員除了屬性還有方法,即使屬效能完整傳過去,但方法呢?方法是獨立於類物件存在的,所以到另一個程序中再引用同一個方法就要出錯了,還是因為獨立地址空間的原因。

Android開發中,經常在Activity之間傳遞資料,而根據Android的設計架構,即使同一個應用程式中的Activity都不一定執行在同一個程序中,所以處理資料傳遞時你不能假設兩個Activity都運行於同一程序中,那麼只能按程序間傳遞資料來處理,使之具有最廣泛的適應性。

如何在程序之間傳遞類物件呢?簡單來說可以這樣做:在程序A中把類中的非預設值的屬性和類的唯一標誌打成包(這就叫序列化),把這個包傳遞到程序B,程序B接收到包後,根據類的唯一標誌把類創建出來,然後把傳來的屬性更新到類物件中,這樣程序A和程序B中就包含了兩個完全一樣的類物件。

Parcel就是一個存放讀取資料的容器, Android系統中的binder程序間通訊(IPC)就使用了Parcel類來進行客戶端與服務端資料的互動,而且AIDL的資料也是通過Parcel來互動的。在Java空間和C++都實現了Parcel,由於它在C/C++中,直接使用了記憶體來讀取資料,因此,它更有效率。

分析Binder機制中的客戶端與伺服器端進行實際操作onTransact()函式 :

// code :是請求的ID號    
// data :客戶端請求傳送的引數   
// reply:伺服器端返回的結果   
// flags:一些額外的標識,如FLAG_ONEWAY等,通常為0.   
virtual status_t    onTransact( uint32_t code,   const Parcel& data,   Parcel* reply,   uint32_t flags = 0); 

從中我們可以看到Parcel的重要性。

常用方法介紹:

            obtain()                          獲得一個新的parcel ,相當於new一個物件

            dataSize()                      得到當前parcel物件的實際儲存空間

            dataCapacity()               得到當前parcel物件的已分配的儲存空間, >=dataSize()值  (以空間換時間)

            dataPosition()                 獲得當前parcel物件的偏移量(類似於檔案流指標的偏移量)

            setDataPosition()           設定偏移量

            recycle()                           清空、回收parcel物件的記憶體

            writeInt(int)                     寫入一個整數

            writeFloat(float)              寫入一個浮點數

            writeDouble(double)       寫入一個雙精度數

            writeString(string)           寫入一個字串

 當然,還有更多的writeXXX()方法,與之對應的就是readXXX()。

事實上,我們可以顯式的通過setDataPostion(int postion) 來直接操作我們欲讀取資料時的偏移量。毫無疑問,你可以設定任何偏移量,但所讀取的值是型別可能有誤。因此顯示設定偏移量讀取值的時候,需要小心。

另外一個注意點就是我們在writeXXX()和readXXX()時,導致的偏移量是共用的,例如,我們在writeInt(23)後,

此時的datapostion=4,如果我們想讀取23,簡單的通過readInt()是不行的,只能得到0。這時我們只能通過

setDataPosition(0)設定為起始偏移量,從起始位置讀取四個位元組,即23。因此,在讀取某個值時,可能需要使用

setDataPostion(int postion)使偏移量裝換到我們的值處。

巧用setDataPosition()方法,當我們的Parcel物件中只存在某一型別時,我們就可以通過這個方法來快速的讀取

所有值。具體方法如下:

/**
 * 前提條件,Parcel存在多個型別相同的物件,本例子以10個float物件說明:  
 */  
public void readSameType() {  
        Parcel parcel =Parcel.obtain() ;  
        for (int i = 0; i < 10; i++) {  
            parcel.writeDouble(i);  
            Log.i(TAG, "write double ----> " + getParcelInfo());  
        }  
        //方法一 ,顯式設定偏移量   
        int i = 0;  
        int datasize = parcel.dataSize();  
        while (i < datasize) {  
            parcel.setDataPosition(i);  
            double fvalue = parcel.readDouble();  
            Log.i(TAG, " read double is=" + fvalue + ", --->" + getParcelInfo());  
            i += 8; // double佔用位元組為 8byte   
        }  
//      方法二,由於物件的型別一致,我們可以直接利用readXXX()讀取值會產生偏移量  
//      parcel.setDataPosition(0)  ;  //  
//      while(parcel.dataPosition()<parcel.dataSize()){  
//          double fvalue = parcel.readDouble();  
//          Log.i(TAG, " read double is=" + fvalue + ", --->" + getParcelInfo());  
//      }  
}

由於可能存在讀取值的偏差,一個預設的取值規範為:

1、  讀取複雜物件時: 物件匹配時,返回當前偏移位置的該物件;  物件不匹配時,返回null物件 ;

2、  讀取簡單物件時: 物件匹配時,返回當前偏移位置的該物件 ; 物件不匹配時,返回0;