Android IPC传输之Parcel与Parcelable剖析

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还多了一个描述的过程。

《Android IPC传输之Parcel与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);

我们找一下native方法看看底层的实现。看看nativeWriteByteArray与nativeReadByteArray这一对吧。

android_os_Parcel.cpp

static void android_os_Parcel_writeByteArray(JNIEnv* env, jclass clazz, jlong nativePtr,
                                             jobject data, jint offset, jint length)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel == NULL) {
        return;
    }

    const status_t err = parcel->writeInt32(length);
    if (err != NO_ERROR) {
        signalExceptionForError(env, clazz, err);
        return;
    }

    void* dest = parcel->writeInplace(length);
    if (dest == NULL) {
        signalExceptionForError(env, clazz, NO_MEMORY);
        return;
    }

    jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)data, 0);
    if (ar) {
        memcpy(dest, ar + offset, length);
        env->ReleasePrimitiveArrayCritical((jarray)data, ar, 0);
    }
}
  • Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    获取native中的Parcel实例。
  • const status_t err = parcel->writeInt32(length);
    写入数组长度,这儿只是数组长度。
  • void* dest = parcel->writeInplace(length);
    写完数组长度之后,重新定位写入指针的位置。
  • jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)data, 0);
    将当前要写入的数组data转化为jbyte指针,方便后续写入。
  • memcpy(dest, ar + offset, length);
    写入共享内存,起始位置,偏移位置多少,写入长度多少。
android_os_Parcel.cpp

static jboolean android_os_Parcel_readByteArray(JNIEnv* env, jclass clazz, jlong nativePtr,
                                                jobject dest, jint destLen)
{
    jboolean ret = JNI_FALSE;
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel == NULL) {
        return ret;
    }

    int32_t len = parcel->readInt32();
    if (len >= 0 && len <= (int32_t)parcel->dataAvail() && len == destLen) {
        jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)dest, 0);
        if (ar) {
            const void* data = parcel->readInplace(len);
            if (data) {
                memcpy(ar, data, len);
                ret = JNI_TRUE;
            } else {
                ret = JNI_FALSE;
            }

            env->ReleasePrimitiveArrayCritical((jarray)dest, ar, 0);
        }
    }
    return ret;
}

这儿完全就是write的反操作的,记住我们当前持有的地址指针nativePtr,这个是很重要的,通过这个我们可以读取了当前地址的数据,将地址的存储的变量或者对象取出来。
int32_t len = parcel->readInt32();
jbyte* ar = (jbyte)env->GetPrimitiveArrayCritical((jarray)dest, 0);
const void
data = parcel->readInplace(len);
memcpy(ar, data, len);
关键的4个步骤,将存储的数组长度取出,校验一下当前内存地址中是否存在数据?读取length长度的数据,然后执行memcpy,将取出的数据读取出来。

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是存储在内存中的,不能持久化存储。
    原文作者:木子一秋
    原文地址: https://www.jianshu.com/p/116fce3e78c6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞