專注於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;