栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Android Parcel数据传输源码解析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android Parcel数据传输源码解析

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

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

frameworksbasecorejavaandroidosParcel.java
frameworksbasecorejniandroid_os_Parcel.cpp
frameworksnativelibsbinderParcel.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 

也很好理解,内部使用了对象数组作为对象池,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
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(mData+mDataPos) = val;
        return finishWrite(sizeof(val));
    }

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

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

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

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

而读取的操作最终会调用到readAligned方法,读取的时候主要是通过mDataPos(也就是当前读取位置)和存储数据类型大小决定的,所以写入和读取的顺序要正确。

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

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


status_t flatten_binder(const sp& ,
    const sp& binder, Parcel* out)
{
    flat_binder_object obj;

    if (IPCThreadState::self()->backgroundSchedulingDisabled()) {
        
        obj.flags = FLAT_BINDER_FLAG_ACCEPTS_FDS;
    } else {
        
        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; 
            obj.handle = handle;
            obj.cookie = 0;
        } else {
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast(local->getWeakRefs());
            obj.cookie = reinterpret_cast(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驱动启动 - 简书

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/712564.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号