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中取出指定的数据类型。

原文地址:https://www.cnblogs.com/wytiger/p/12875640.html