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

  • 相关阅读:
    案例分享:Qt+Arm基于RV1126平台的内窥镜软硬整套解决方案(实时影像、冻结、拍照、录像、背光调整、硬件光源调整,其他产品也可使用该平台,如视频监控,物联网产品等等)
    libzip开发笔记(二):libzip库介绍、ubuntu平台编译和工程模板
    案例分享:Qt西门子机床人机界面以及数据看板定制(西门子通讯,mysql数据库,生产信息,参数信息,信息化看板,权限控制,播放器,二维图表,参数调试界面)
    sshpass 简介
    SSH 协议及 OpenSSH 实现
    Linux从头学07:中断那么重要,它的本质到底是什么?
    Linux从头学06:16张结构图,彻底理解【代码重定位】的底层原理
    Linux从头学05-系统启动过程中的几个神秘地址,你知道是什么意思吗?
    所有编程语言中的栈操作,底层原理都在这里
    WSL2:Windows 亲生的 Linux 子系统
  • 原文地址:https://www.cnblogs.com/wytiger/p/12875640.html
Copyright © 2020-2023  润新知