Android Serializable和Parcelable序列化物件詳解
轉載:https://www.cnblogs.com/yezhennan/p/5527506.html
學習內容:
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)
速度測試:
測試方法:
1)、通過將一個物件放到一個bundle裡面然後呼叫Bundle#writeToParcel(Parcel, int)方法來模擬傳遞物件給一個activity的過程,然後再把這個物件取出來。
2)、在一個迴圈裡面執行1000 次。
3)、兩種方法分別執行10次來減少記憶體整理,cpu被其他應用佔用等情況的干擾。
4)、參與測試的物件就是上面的相關程式碼
5)、在多種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.當然實現後者也不是不行,關鍵在於我們頭腦中的那一份思想。