Android Parcel数据传输源码解析

想要深入的学习Binder跨进程通信,Parcel是必须要学习的内容之一。Parcel肩负着Binder中数据传输的重任。了解AIDL的同学,对Parcel应该不会太陌生,如果不了解,请移步上一篇文章《Android AIDL的基本使用》。废话不多说,我们直接进入正题!

Parcel这个类在Java层,JNI层,Native层都有相应的实现,而Java层的Parcel更多是写入和写出功能的封装,具体的数据装载和取出是在Native层,相关文件路径如下:

\frameworks\base\core\java\android\os\Parcel.java
\frameworks\base\core\jni\android_os_Parcel.cpp
\frameworks\native\libs\binder\Parcel.cpp

Java层

Parcel.java中,除了native层代码的调用以外,内部维护了一个对象池用于对象的复用,最新的Android31和之前的版本实现原理有些不一样。

Android31的Parcel

相关代码如下

    @GuardedBy("sPoolSync")
    private Parcel mPoolNext;

    @GuardedBy("sPoolSync")
    private static Parcel sOwnedPool;
    //对象池的数量
    @GuardedBy("sPoolSync")
    private static int sOwnedPoolSize = 0;
    //对象池的最大容量
    private static final int POOL_SIZE = 32;
    
    ...
    //obtain方法
    public static Parcel obtain() {
        Parcel res = null;
        synchronized (sPoolSync) {
            if (sOwnedPool != null) {
                res = sOwnedPool;
                sOwnedPool = res.mPoolNext;
                res.mPoolNext = null;
                sOwnedPoolSize--;
            }
        }

        // When no cache found above, create from scratch; otherwise prepare the
        // cached object to be used
        if (res == null) {
            res = new Parcel(0);
        } else {
            if (DEBUG_RECYCLE) {
                res.mStack = new RuntimeException();
            }
            res.mReadWriteHelper = ReadWriteHelper.DEFAULT;
        }
        return res;
    }

    //recycle方法
    public final void recycle() {
        if (DEBUG_RECYCLE) mStack = null;
        freeBuffer();

        if (mOwnsNativeParcelObject) {
            synchronized (sPoolSync) {
                if (sOwnedPoolSize < POOL_SIZE) {
                    mPoolNext = sOwnedPool;
                    sOwnedPool = this;
                    sOwnedPoolSize++;
                }
            }
        } else {
            mNativePtr = 0;
            synchronized (sPoolSync) {
                if (sHolderPoolSize < POOL_SIZE) {
                    mPoolNext = sHolderPool;
                    sHolderPool = this;
                    sHolderPoolSize++;
                }
            }
        }
    }

是不是很眼熟,Android31的Parcel对象复用原理和Handler中的Message是一样的。

Parcel是一个单链表的结构,每次obtain的时候,会拿到链表的第一个元素;每次recycle的时候,会添加到链表最后一个元素。如果obtain的时候,对象为空,则最终会调用到Native层的Parcel.cpp的构造函数,创建对象进行返回。Parcel内部的对象池的容量为32,支持最多16个进程的Parcel对象存储(每个进程交互包含了一个请求数据data和响应数据reply)

Android31以前的Parcel

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


    //obtain方法
    public static Parcel obtain() {
        final Parcel[] pool = sOwnedPool;
        synchronized (pool) {
            Parcel p;
            for (int i=0; i<POOL_SIZE; i++) {
                p = pool[i];
                if (p != null) {
                    pool[i] = null;
                    if (DEBUG_RECYCLE) {
                        p.mStack = new RuntimeException();
                    }
                    p.mReadWriteHelper = ReadWriteHelper.DEFAULT;
                    return p;
                }
            }
        }
        return new Parcel(0);
    }

    //recycle方法
    public final void recycle() {
        if (DEBUG_RECYCLE) mStack = null;
        freeBuffer();

        final Parcel[] pool;
        if (mOwnsNativeParcelObject) {
            pool = sOwnedPool;
        } else {
            mNativePtr = 0;
            pool = sHolderPool;
        }

        synchronized (pool) {
            for (int i=0; i<POOL_SIZE; i++) {
                if (pool[i] == null) {
                    pool[i] = this;
                    return;
                }
            }
        }
    }

也很好理解,内部使用了对象数组作为对象池,obtain和recycle主要是对对象数组进行增减,对象池的容量为6

JNI层

JNI层的Parcel具体实现类为android_os_Parcel.cpp,主要是一个桥接的作用,桥接Java层和Native层,所以具体的Parcel细节要看Native层的Parcel

Native层

Native层的Parcel具体实现类为Parcel.cpp,对于基本数据的写入,原理大致相同,我们看一下int类型的写入

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));//1
    }

    status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write;
    return err;
}

最终都会调用到writeAligned方法(写入对齐),这个方法内部主要是两个逻辑:

(1)如果写入的数据大小+之前存储的数据大小小于阈值,那么直接进行把数据写入到内存

(2)如果写入的数据大小+之前存储的数据大小大于阈值,那么先调用growData进行扩容,然后调用通过goto语句,重新进行数据写入,扩容的比例为原来容积的1.5倍。

之后调用了finishWrite方法,将写入值的大小作为参数传了进去,我们看一下finishWrite方法

status_t Parcel::finishWrite(size_t len)
{
    if (len > INT32_MAX) {
        // don't accept size_t values which may have come from an
        // inadvertent conversion from a negative int.
        return BAD_VALUE;
    }

    //printf("Finish write of %d\n", len);
    mDataPos += len;//1
    ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos);
    if (mDataPos > mDataSize) {
        mDataSize = mDataPos;
        ALOGV("finishWrite Setting data size of %p to %zu", this, mDataSize);
    }
    //printf("New pos=%d, size=%d\n", mDataPos, mDataSize);
    return NO_ERROR;
}

在注释1的地方,我们可以看到,mDataPos的值增加了写入数据的大小。这样做的目的是规定了读取顺序,我们可以看一下取值的方法readAligned:

template<class T>
T Parcel::readAligned() const {
    T result;
    if (readAligned(&result) != NO_ERROR) {
        result = 0;
    }

    return result;
}

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) {
        const void* data = mData+mDataPos;
        mDataPos += sizeof(T);//1
        *pArg =  *reinterpret_cast<const T*>(data);
        return NO_ERROR;
    } else {
        return NOT_ENOUGH_DATA;
    }
}

无参的readAligned调用了有参的readAligned方法,因为我们以Int类型为例,所以参数为Int类型

我们从注释1可以看见,mDataPos的值也进行了相应的变化,也加上了Int类型的字节大小,从而从正确位置取出数据。

比较特殊的是Binder对象的写入,我们看下代码:

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}


status_t flatten_binder(const sp<ProcessState>& /*proc*/,
    const sp<IBinder>& binder, Parcel* out)
{
    flat_binder_object obj;

    if (IPCThreadState::self()->backgroundSchedulingDisabled()) {
        /* minimum priority for all nodes is nice 0 */
        obj.flags = FLAT_BINDER_FLAG_ACCEPTS_FDS;
    } else {
        /* minimum priority for all nodes is MAX_NICE(19) */
        obj.flags = 0x13 | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    }

    if (binder != NULL) {
        IBinder *local = binder->localBinder();
        if (!local) {
            BpBinder *proxy = binder->remoteBinder();
            if (proxy == NULL) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.type = BINDER_TYPE_HANDLE;
            obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
            obj.handle = handle;
            obj.cookie = 0;
        } else {
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local);
        }
    } else {
        obj.type = BINDER_TYPE_BINDER;
        obj.binder = 0;
        obj.cookie = 0;
    }

    return finish_flatten_binder(binder, obj, out);
}

在Parcel.java中的方法是nativeWriteStrongBinder,在Native中的方法实现为writeStrongBinder,最终调用的方法为flatten_binder方法。在flatten_binder方法中,判断binder是为跨进程类型,然后将相关信息存储到flat_binder_object这个结构体中,写入到Parcel对象里。

flatten_binder方法作用:从字面意义上来看,就是对binder进行压平、压缩,减少存储数据的大小。原理也很简单,binder的父类是IBinder,而IBinder的数据结构很大,包含了跨进程和非跨进程的所有属性,如果直接存储的话,无用的数据也都很被存储。而flatten_binder方法中,将binder进行了类型的区分,将有效的数据存储到了flat_binder_object结构体中,这个结构体很简单,所以也不占空间

参考文章:

Android Binder机制(二) Binder中的数据结构 | skywang

Android-Binder驱动启动 - 简书

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值