深入理解Android Parcel

Quibbler 2021-6-24 2312

深入理解Android Parcel


        原计划按照AIDL、Binder不断深入学习Android跨进程IPC,中途发现这些都离不开一个容器:Parcel,还记得在Android中的两种序列化:Parcelable和Serialable一文中讲到ParcelableParcelable也通过Parcel的写入和读出完成数据序列化。先来了解一下Parcel及其底层原理,将这前后知识串起来。



1、剖析Parcel

        Parcel是一个容器,用来存储序列化数据,并且能够通过Binder传输,需要注意的是Parcel并不是通用的序列化机制。按照惯例,先了解一个类的基本用法,及其内部成员,以及提供了哪些方法。java代码在framework层的位置:

        /frameworks/base/core/java/android/os/Parcel.java


1.1、获取Parcel

        Parcel实例只能通过该类提供的静态方法obtain()获取。

    Parcel parcel = Parcel.obtain();

        obtain()静态方法实现如下:

    /**
     * Retrieve a new Parcel object from the pool.
     */
    public static Parcel obtain() {
        final Parcel[] pool = sOwnedPool;
        synchronized (pool) {
            Parcel p;
            for (int i=0; i<POOL_SIZE; i++) {
                p = pool[i];
                //从复用对象池中取出一个可用非空实例返回,
                //并将当前池子中的位置设置为null,以便后面recycle()回收用
                if (p != null) {
                    pool[i] = null;
                    if (DEBUG_RECYCLE) {
                        p.mStack = new RuntimeException();
                    }
                    p.mReadWriteHelper = ReadWriteHelper.DEFAULT;
                    return p;
                }
            }
        }
        //如果池子中没有可用的对象,那么直接创建新的实例返回 
        return new Parcel(0);
    }

        首先,Parcel构造函数是私有的,不能直接通过构造函数创建对象;

    private Parcel(long nativePtr) {
        if (DEBUG_RECYCLE) {
            mStack = new RuntimeException();
        }
        //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
        init(nativePtr);
    }

        另外,避免大量创建Parcel对象,内部维护一个对象池sOwnedPool复用Parcel实例。

    private static final int POOL_SIZE = 6;
    private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
    private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];

        

1.2、回收Parcel

        获取到的Parcel对象在使用完之后,一定要调用recycle()方法将该对象回收尽可能复用。

    Parcel parcel = Parcel.obtain();
    try {
        //TODO
    }finally {
        parcel.recycle();
    }

        recycle()方法实现如下:

    /**
     * Put a Parcel object back into the pool.  You must not touch
     * the object after this call.
     */
    public final void recycle() {
        if (DEBUG_RECYCLE) mStack = null;
        freeBuffer();
        final Parcel[] pool;
        
        //根据mNativePtr标志,将当前Parcel对象回收到sOwnedPool或者sHolderPool池中。
        if (mOwnsNativeParcelObject) {
            pool = sOwnedPool;
        } else {
            mNativePtr = 0;
            pool = sHolderPool;
        }
        
        //找到池子中的空位置,存放当前回收的Parcel对象实例。
        synchronized (pool) {
            for (int i=0; i<POOL_SIZE; i++) {
                if (pool[i] == null) {
                    pool[i] = this;
                    return;
                }
            }
        }
    }

        调用过recycle()回收后的Parcel对象一定不能再进行任何操作。



2、读写数据

        获取到Parcel对象之后,让我们来看看能够进行什么操作。首先就是数据的写入和读取,因为Parcel大部分方法都围绕着读取和写入各种类型的数据。


2.1、写数据

        Parcel提供了几十个写数据的方法,涵盖基本类型、数组、集合、对象等等。这里罗列了一些,了解即可,会看文档更重要。

    writeArray()
    writeArrayMap()
    writeArraySet()
    writeBinderArray()
    writeBinderList()
    writeBlob()
    writeBoolean()
    writeBooleanArray()
    writeBundle()
    writeByte()
    writeByteArray()
    writeCharArray()
    writeCharSequence()
    writeCharSequenceArray()
    writeCharSequenceList()
    writeDouble()
    writeDoubleArray()
    writeException()
    writeFileDescriptor()
    writeFloat()
    writeFloatArray()
    writeInt()
    writeIntArray()
    writeInterfaceToken()
    writeList()
    writeLong()
    writeLongArray()
    writeMap()
    writeNoException()
    writeParcelable()
    ... ...

    

2.2、读数据

        相应的也有读取数据的方法。

	readArray()
	readArrayList()
	readArrayMap()
	readArraySet()
	readBinderArray()
	readBinderList()
	readBlob()
	readBoolean()
	readBooleanArray()
	readBundle()
	readByte()
	readByteArray()
	readCallingWorkSourceUid()
	readCharArray()
	readCharSequence()
	readCharSequenceArray()
	readCharSequenceList()
	readCreator()
	readDouble()
	readDoubleArray()
	readException()
	readExceptionCode()
	readFileDescriptor()
	readFloat()
	readFloatArray()
	readHashMap()
	readInt()
	readIntArray()
	readList()
	readLong()
	... ...

        通常在按顺序读取数据之前需要先用setDataPosition(int pos)方法将读取偏移到0,也就是从头开始读取。也可以移到指定的偏移位置读取数据。


2.3、native方法

        前面所有的读写方法都用到了native方法,或只是对native方法的封装,内部通过native方法完成数据的写入和读取。

    nativeAppendFrom()
    nativeCompareData()
    nativeCreate()
    nativeCreateByteArray()
    nativeDataAvail()
    nativeDataCapacity()
    nativeDataPosition()
    nativeDataSize()
    nativeDestroy()
    nativeEnforceInterface()
    nativeFreeBuffer()
    nativeGetBlobAshmemSize()
    nativeHasFileDescriptors()
    nativeMarshall()
    nativePushAllowFds()
    nativeReadBlob()
    nativeReadByteArray()
    nativeReadCallingWorkSourceUid()
    nativeReadDouble()
    nativeReadFileDescriptor()
    ... ...



3、底层实现

        既然java层只是对native层的封装,大部分功能还是通过native方法实现的。那么久继续往下深挖,看看底层是如何实现的。涉及到的JNI和native相关源码位置如下:

        /frameworks/base/core/jni/android_os_Parcel.h

        /frameworks/base/core/jni/android_os_Parcel.cpp

        /frameworks/native/include/binder/Parcel.h

        /frameworks/native/libs/binder/Parcel.cpp


3.1、对象创建及初始化

        在第1.1节中,通过obtain()方法获取Parcel实例,内部调用构造方法创建实例,构造方法内部调用了init(long nativePtr)方法。

    private void init(long nativePtr) {
        if (nativePtr != 0) {
            mNativePtr = nativePtr;
            mOwnsNativeParcelObject = false;
        } else {
            mNativePtr = nativeCreate();
            mOwnsNativeParcelObject = true;
        }
    }

        传入的参数为0,调用nativeCreate()方法由底层进行初始化。

    private static native long nativeCreate();

        Parcel中用到的native方法对应在JNI层的android_os_Parcel.cpp中,对应的映射关系由多重数组管理,gParcelMethodsandroid_os_Parcel.cpp中的定义如下:

    static const JNINativeMethod gParcelMethods[] = {
        ...
        // @FastNative
        {"nativeWriteInt",            "(JI)V", (void*)android_os_Parcel_writeInt},
        ...
        {"nativeReadByteArray",       "(J[BI)Z", (void*)android_os_Parcel_readByteArray},
        {"nativeReadBlob",            "(J)[B", (void*)android_os_Parcel_readBlob},
        // @CriticalNative
        {"nativeReadInt",             "(J)I", (void*)android_os_Parcel_readInt},
        ...
        {"nativeCreate",              "()J", (void*)android_os_Parcel_create},
        ...
    };

        从上面可以找到nativeCreate()方法对应JNI层的android_os_Parcel_create()方法:

    static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
    {
        Parcel* parcel = new Parcel();
        return reinterpret_cast<jlong>(parcel);
    }

        调用Natice层的Parcel.cpp构造方法创建Parcel实例,并且将Parcel*指针转为long类型返回:

    Parcel::Parcel()
    {
        LOG_ALLOC("Parcel %p: constructing", this);
        initState();
    }

        最后再调用initState()初始化底层必要的一些数据:

    void Parcel::initState()
    {
        LOG_ALLOC("Parcel %p: initState", this);
        mError = NO_ERROR;
        mData = nullptr;
        mDataSize = 0;
        mDataCapacity = 0;
        mDataPos = 0;
        ALOGV("initState Setting data size of %p to %zu", this, mDataSize);
        ALOGV("initState Setting data pos of %p to %zu", this, mDataPos);
        mObjects = nullptr;
        mObjectsSize = 0;
        mObjectsCapacity = 0;
        mNextObjectHint = 0;
        mObjectsSorted = false;
        mHasFds = false;
        mFdsKnown = true;
        mAllowFds = true;
        mOwner = nullptr;
        mOpenAshmemSize = 0;
        mWorkSourceRequestHeaderPosition = 0;
        mRequestHeaderPresent = false;
        ... ...
    }


3.2、写数据

        写入数据的方法很多,但基本都是一样的流程。以writeInt(int val)方法为例:

    /**
     * Write an integer value into the parcel at the current dataPosition(),
     * growing dataCapacity() if needed.
     */
    public final void writeInt(int val) {
        nativeWriteInt(mNativePtr, val);
    }

        调用native方法nativeWriteInt(long nativePtr, int val)

    @FastNative
    private static native void nativeWriteInt(long nativePtr, int val);

        通过前面提到的JNI方法注册关系,可以找到nativeWriteInt(long nativePtr, int val)对应的JNI层方法为android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val)

    static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
        Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
        if (parcel != NULL) {
            const status_t err = parcel->writeInt32(val);
            if (err != NO_ERROR) {
                signalExceptionForError(env, clazz, err);
            }
        }
    }

        先将Java传过来的long强制转换成的Parcel*指针,调用Parcel.cppwriteInt32()方法写入数据:

    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_UNSAFE(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;
    }

        如果分配的容量不够还会调用growData()方法扩容。


3.3、读数据

        同样的,读取数据和写入类似,也是从Java -> JNI -> Native。举个简单的例子,以readInt()方法为例,读取刚才writeInt()写入的Int值:

    /**
     * Read an integer value from the parcel at the current dataPosition().
     */
    public final int readInt() {
        return nativeReadInt(mNativePtr);
    }

        调用nativeReadInt()方法:

    @CriticalNative
    private static native int nativeReadInt(long nativePtr);

        在android_os_Parcel.cpp中对应的方法为android_os_Parcel_readInt(jlong nativePtr)

    static jint android_os_Parcel_readInt(jlong nativePtr)
    {
        Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
        if (parcel != NULL) {
            return parcel->readInt32();
        }
        return 0;
    }

        利用Java层传入的long值找到Parcel*指针,调用Parcel.cppreadInt32()方法:

    status_t Parcel::readInt32(int32_t *pArg) const
    {
        return readAligned(pArg);
    }

        最后用readAligned(T *pArg)读取数据,也是对齐读取(这里也不详细看该方法了,C++忘得差不多了( ・´ω`・ )

    template<class T>
    status_t Parcel::readAligned(T *pArg) const {
        COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
        if ((mDataPos+sizeof(T)) <= mDataSize) {
            if (mObjectsSize > 0) {
                status_t err = validateReadData(mDataPos + sizeof(T));
                if(err != NO_ERROR) {
                    // Still increment the data position by the expected length
                    mDataPos += sizeof(T);
                    return err;
                }
            }
            const void* data = mData+mDataPos;
            mDataPos += sizeof(T);
            *pArg =  *reinterpret_cast<const T*>(data);
            return NO_ERROR;
        } else {
            return NOT_ENOUGH_DATA;
        }
    }



        Android开发知识点很杂很多,既不能逮住一个方向钻,也不能每个都蜻蜓点水不深入。把技能点串起来,而不是一个个零散的。接下来,就要啃Binder这块大骨头。



相关链接:

        Android Developers > Docs > Parcel

        Android中的两种序列化:Parcelable和Serialable

        

不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com