Parcel與Parcelable剖析


原文: https://www.jianshu.com/p/116fce3e78c6

 

Android中的Binder IPC傳輸的是什么樣的數據呢?最近正在學習android camera相關的知識,我們經常看到應用程序進程到camera service中傳輸數據使用的是什么數據載體。
frameworks/base/core/java/android/hardware/camera2/impl/CameraMetadataNative.java

public class CameraMetadataNative implements Parcelable { //...... } 

實現一個Parcelable接口就可以讓CameraMetadataNative 對象在進程間通信了,什么原因了?我們需要了解一下Parcel與Parcelable是什么?以及它們為什么可以將對象轉化成可以在進程間通信的數據。

1.Parcel與Parcelable簡介

Parcel
android.os.Parcel,Parcel是一個消息容器,消息就是指數據和對象引用,這些數據信息通過IBinder來傳輸,在IPC通信的時候,一端將對象數據准備好(可以Parcel化,就是當前的類需要實現Parcelable接口,表明當前的類的實例是可以Parcel化的),然后接收端在接受到這個Parcel化得數據之后,也可以通過Parcel提供的方法將數據完整的取出來,這個有點神奇。
Parcel不是通用序列化機制。Parcel(以及用於將任意對象放入包中的相應Parcelable API)被設計為高性能IPC傳輸數據載體。因此,將任何Parcel數據放入持久存儲中是不合適的:Parcel中任何數據的底層實現的更改都可能導致舊數據不可讀。

Parcelable
android.os.Parcelable,實現Parcelable的類的實例通過Parcel寫入與還原數據。實現Parcelable接口的類還必須具有一個名為CREATOR的非空靜態字段,該字段實現Parcelable.Creator接口。

public class MyParcelable implements Parcelable { private int mData; public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(mData); } public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() { public MyParcelable createFromParcel(Parcel in) { return new MyParcelable(in); } public MyParcelable[] newArray(int size) { return new MyParcelable[size]; } }; private MyParcelable(Parcel in) { mData = in.readInt(); } } 
  • Parcel是序列化,但是不是持久序列化。
  • Parcel主要目的是提高IPC時的數據傳輸性能。

2.Parcel與Parcelable工作原理

從上面的介紹中我們知道Parcelable是android特有的一種序列化的方式,但是不能持久化存儲,我們知道通常的序列化就是——序列化、反序列化過程,但是Parcelable還多了一個描述的過程。

 
Parcelable原理.jpg

上面這個流程比較清晰的說明了Parcelable的原理,兩個進程之間通信,中間需要傳輸數據,Parcelable就是這種序列化之后的傳輸數據,而序列化和反序列的使用的是Parcel方式,與Serializable不太一樣,Parcelable並不是持久化存儲。

 


Parcelable可以傳輸傳統的數據,也可以傳輸對象,甚至可以傳輸Parcelable類型的數據,也可以傳輸活動的IBinder對象的引用。下面看看代碼中這些讀寫方法。

    private static native void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len); private static native void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len); @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); @FastNative private static native void nativeWriteDouble(long nativePtr, double val); static native void nativeWriteString(long nativePtr, String val); private static native void nativeWriteStrongBinder(long nativePtr, IBinder val); private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val); private static native boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen); private static native byte[] nativeReadBlob(long nativePtr); @CriticalNative private static native int nativeReadInt(long nativePtr); @CriticalNative private static native long nativeReadLong(long nativePtr); @CriticalNative private static native float nativeReadFloat(long nativePtr); @CriticalNative private static native double nativeReadDouble(long nativePtr); static native String nativeReadString(long nativePtr); private static native IBinder nativeReadStrongBinder(long nativePtr); private static native FileDescriptor nativeReadFileDescriptor(long nativePtr); 

 

3.Parcelable執行步驟

上面也談到了Parcelable操作的3步驟:描述、序列化、反序列化。

描述
     public int describeContents() { return 0; } 

describeContents是Parcelable接口中的函數,實現類都需要實現這個方法,一般情況下我們都是返回0,但是特殊情況下,需要返回1,就是Parcelable中定義的變量。
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
描述此Parcelable實例的封送表示中包含的特殊對象的種類,例如,當前對象在操作writeToParcel(Parcel, int)的時候包含一個file descriptor,那就必須要要返回CONTENTS_FILE_DESCRIPTOR ,就是1。

序列化
     public void writeToParcel(Parcel out, int flags) { out.writeInt(mData); } 

writeToParcel也是Parcelable中的方法,上面再講解Parcelable原理的時候也談到了這個writeToParcel的實現方法,會通過Parcel中的writeXXX方法在寫入共享內存中。

反序列化
     public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() { public MyParcelable createFromParcel(Parcel in) { return new MyParcelable(in); } public MyParcelable[] newArray(int size) { return new MyParcelable[size]; } }; 
    public interface Creator<T> { public T createFromParcel(Parcel source); public T[] newArray(int size); } public interface ClassLoaderCreator<T> extends Creator<T> { public T createFromParcel(Parcel source, ClassLoader loader); } } 

上面定義的Parcelable.Creator與Parcelable.ClassLoaderCreator分別代表不同的數據反序列化的。
Parcelable.ClassLoaderCreator是繼承Parcelable.Creator的,允許傳入ClassLoader變量,通過這個ClassLoader實例可以調用反射等等,等於是擴充了原來的反序列化得操作了。

4.與Serializable區別

  • Serializable是java序列化的方式,存取的過程有頻繁的IO,性能較差,但是實現簡單。
  • Parcelable是android序列化的方式,采用共享內存的方式實現用戶空間和內核空間的交換,性能很好,但是實現方式比較復雜。
  • Serializable可以持久化存儲,Parcelable是存儲在內存中的,不能持久化存儲。

 


 Parcel

Parcel是一個用於包裝各種數據的容器類,凡是經過Parcel包裝后的數據都可以通過在binder進程間通信IPC中進行服務端和數據端的數據交互,AIDL中也用到了Parcel進行數據封裝。
現在簡單介紹一下,使用Parcel包裝后進程間通信的工作大致的原理:
假如有進程A、B需要進行通信,在進程A中用Parcel將需要傳輸的數據類中的飛默認值和唯一類標識打包(此過程稱為序列化),再把這個包傳輸到進程B中,進程B通過這個包中的唯一標示將會重新創建一個一模一樣的類對象,這就是通信的大概過程。
雖然,parcel在網上是這么被描述的,但是對於初學Android的我來說還是不懂,所以就去看了一下源碼部分。


Parcel is <strong>not</strong> a general-purpose
serialization mechanism.  This class (and the corresponding
{@link Parcelable} API for placing arbitrary objects into a Parcel) is designed as a high-performance IPC transport.

從上面可以看出,parcel不是一般用途的序列化機制,這個類以及與之相匹配的Parcelable接口(Parcelable能夠將任意的對象打包進parcel實例中)使IPC數據傳輸更加的高效。

另外,Parcel類中的核心部分涉及了不同數據類型到parcel對象的讀寫的實現。其中,基本類型(long、int、String、double等)是可以直接進行打包或者讀取的,但是自定義的類型必須在實現了Parcelable后才能進行打包。

看到的幾個應該注意的點:
1)obtain()和recycle()方法,一個類似於new一個parcel對象,一個回收一個parcel對象。
2)涉及Map的write或者read操作,一般的推薦使用writeBundle和readBundle,因為readMap或者writeMap方法的花銷要比前者大的多,所以不推薦使用。
3)dataSize()和dataCapacity()的含義有所區別:前者表示實際大小,后者表示一個parcel分配到的大小,一般是大於dataSize的。

另外:源碼中幾乎都是readXXXX和writeXXXX的方法,這也說明了parcel的功能和他的地位。

Parcelable

字面意義上來講,是一個使對象能夠parcel的接口。上面的描述中,我們提到了parcel如果想要打包自定義的數據結構,那么這個自定義的類必須實現Parcelable的方法。所以,我們可以這么理解Parcelable:他是一個使自定義類對象具有序列化能力的接口,凡是實現了該接口的類對象都能夠被parcel。

Parcelable中有兩個使自定義類具備parcel能力的方法(接口):
1)public void writeToParcel(Parcel dest,int flags),實現這個方法又該如何做呢?我們知道哪怕是自定義類,他的成員最終也會是基本類型,所以我們只需要將每一個基本類型的成員屬性利用dest.writeXXXX打包進Parcel dest中去。而非基本類型的自定義成員屬性,我們可以繼續實現Parcelable接口。

2)public interface Creator< T >,其中包含兩個方法:public T createFromParcel(Parcel source)和public T[] newArray(int size)。
用於從Parcel中取出指定的數據類型。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM