Binder 中的 Parcel 数据结构分析(C++)

Android Framework 系列教程:yuandaimaahao.github.io/AndroidFram...

视频教程、源码、答疑服务与进入 Framework 技术交流群请联系微信 zzh0838

Parcel 是一个横跨 Java 层与 Native 层的数据结构,扮演了 Binder 远程过程调用中数据传输载体的角色。客户端将数据写入 Parcel,服务端从 Parcel 中读出数据。接下来我们就从 C++ 源码的角度来看看 Parcel 内部的实现原理。

Parcel 支持读写的数据类型如下:

  • Primitives:基本数据类型
  • Primitives Arrays:基本数据类型数组
  • Parcelables:实现了 Parcelable 接口的数据类型
  • Bundle 数据类型:Java 层封装的数据包,仅 Java 层支持
  • Active Objects:IBinder 对象和 FileDescriptor 对象
  • Untyped Containers:未指定泛型类型的 List Map SparseArray

Parcel 源码分析之 Int32 类型数据读写

我们这里先给出一个 Parcel 读写 Int32 数据的示例程序:

main.cpp:

cpp 复制代码
#define LOG_TAG "Parcel"
#include <cstdio>
#include <binder/Parcel.h>
#include <utils/Log.h>

using namespace android;
using namespace std;

int main(int argc, char const *argv[])
{
    Parcel data;
    data.writeInt32(123);
    ALOGI("mDataSize is %d",data.dataSize());
    ALOGI("mDataPos is %d",data.dataPosition());
    ALOGI("mDataCapacity is %d",data.dataCapacity());

    data.setDataPosition(0); //从 0 开始位置开始读取数据

    int32_t result = data.readInt32();
    ALOGI("result is %d", result);
    return 0;
}

Android.bp:

json 复制代码
cc_binary {
    name: "parceltest",
    srcs: [ "main.cpp" ],
    product_specific: true,
    shared_libs: [
        "liblog",
        "libcutils",
        "libutils",
        "libbinder",
    ],
}

我们先把 Int32 读写的代码给拎出来:

cpp 复制代码
//客户端写数据
Parcel data;
data.writeInt32(123);

//服务端读数据
int32_t result = data.readInt32();

客户端首先定义了 Parcel data:这里会调用到 Parcel 的构造函数:

cpp 复制代码
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;

    // racing multiple init leads only to multiple identical write
    if (gMaxFds == 0) {
        struct rlimit result;
        // 获取进程能够打开的最多文件数目
        if (!getrlimit(RLIMIT_NOFILE, &result)) {
            gMaxFds = (size_t)result.rlim_cur;
            //ALOGI("parcel fd limit set to %zu", gMaxFds);
        } else {
            ALOGW("Unable to getrlimit: %s", strerror(errno));
            gMaxFds = 1024;
        }
    }
}

这里涉及到了 linux 下的 getrlimit() 与 setrlimit() 函数,下面简单介绍一下:

进程在操作系统内核中是一个独立存在的运行实体。每个进程都有一组资源限制,限制进程对于系统资源的申请量。可以通过 getrlimit 来获取当前进程某一指定资源的限制。可以通过 setrlimit 来设置当前进程某一指定资源的限制。进程的资源限制通常是在系统初始化时由 0 进程建立的,然后后续进程继承。

相关的函数与数据结构如下:

cpp 复制代码
#include <sys/time.h>
#include <sys/resource.h>

//获取软件资源上线
int getrlimit(int resource, struct rlimit *rlim);
//设置软件资源上线
int setrlimit(int resource, const struct rlimit *rlim);

struct rlimit
{
    lim_t rlim_cur;   /* Soft limit */
    rlim_t rlim_max;  /* Hard limit (ceiling for rlim_cur) */
};

getrlimit 和 setrlimit 都有两个参数:

  • int resource :资源的类型,常用的类型如下:

    cpp 复制代码
    RLIMIT_AS/RLIMIT_VMEM: 这两个资源表示的是同一个含义,都是只address space限制,可用内存用户地址空间最大长度,会影响到sbrk和mmap函数。
    RLIMIT_STACK:栈的长度,默认一般是8K
    RLIMIT_CORE:程序 crash 后生成的 core dump 文件的大小,如果为0将不生成对应的core文件。
    RLIMIT_NOFILE:进程能够打开的最多文件数目,此限制会影响到sysconf的_SC_OPEN_MAX的返回值。
    RLIMIT_NPROC:每个用户ID能够拥有的最大子进程数目,此限制会影响到sysconf的_SC_CHILD_MAX的返回值。
    RLIMIT_NICE:对应进程的优先级nice值。
    RLIMIT_SWAP:进程能够消耗的最大swap空间。
    RLIMIT_CPU:CPU时间的最大值(秒单位),超过此限制后会发送SIGXCPU信号给进程。
    RLIMIT_DATA:数据段的最大长度。默认为unlimited
    RLIMIT_FSIZE:创建文件的最大字节长度。默认为ulimited
    RLIMIT_MSGQUEUE:为posix消息队列可分配的最大存储字节数
    RLIMIT_SIGPENDING:可排队的信号最大数量
    RLIMIT_NPTS:可同时打开的伪终端数目
    RLIMIT_RSS:最大可驻内存字节长度
    RLIMIT_SBSIZE:单个用户所有套接字缓冲区的最大长度
    RLIMIT_MEMLOCK:一个进程使用mlock能够锁定存储空间中的最大字节长度
  • struct rlimit *rlim:结构体,表示资源的限制分为 soft limit 与 hard limit

    • soft limit 是指内核所能支持的资源上限。比如对于 RLIMIT_NOFILE( 一个进程能打开的最大文件数,内核默认是1024),soft limit 最大也只能达到 1024。对于 RLIMIT_CORE(core文件的大小,内核不做限制),soft limit 最大能是 unlimited。
    • hard limit 在资源中只是作为 soft limit 的上限。当你设置 hard limit 后,你以后设置的 soft limit 只能小于 hard limit。要说明的是,hard limit 只针对非特权进程,也就是进程的有效用户 ID(effective user ID) 不是 0 的进程。具有特权级别的进程(具有属性CAP_SYS_RESOURCE),soft limit 则只有内核上限。

Parcel 扮演了数据载体的角色,那么其内部就应该有一个指针,指向一段内存,这段内存用于存储和管理写入 Parcel 中的数据。这个指针就是 mDatamData 在构造函数中初始化为 nullptr,那么有了这个指针,还得配合一些变量,来标记这块内存总的大小以及这块内存已经使用了多少:

  • mDataCapacity:标记这块内存总大小的变量,在构造函数中初始化为 0
  • mDataSize 用于表示 mData 指向的内存区域中已使用内存的大小,在构造函数中初始化为 0
  • mDataPos 用来标记下个数据的写入/读取位置,在构造函数中初始化为 0
  • mObjects 是一个 long long 型的数组结构(或者叫指针结构),用来保存存入 Parcel 的 flat_binder_object 对象的内存地址。因为存入 Parcel 的 flat_binder_object 对象的不止一个,所以需要一个数组来保存其地址
  • mObjectsSize 用于表示 Parcel 中已经保存的 flat_binder_object 对象的个数
  • mObjectsCapacity 用于表示 Parcel 可以保存 flat_binder_object 对象的容量(或者说上限)

接下来我们就来看看 Parcel 写数据 writeInt32 的具体实现:

cpp 复制代码
status_t Parcel::writeInt32(int32_t val)
{
    return writeAligned(val);
}

writeInt32 内部调用 writeAligned:

cpp 复制代码
template<class T>
status_t Parcel::writeAligned(T val) {
    // Assert 操作,不用管
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));

    // Parcle 刚初始化
    // mDataPos = 0
    // 这里模版类型是 int32_t,sizeof(val) = 4
    // mDataCapacity 在构造函数中初始化为 0
    //关注点1
    if ((mDataPos+sizeof(val)) <= mDataCapacity) { // 不进 if
restart_write:
        *reinterpret_cast<T*>(mData+mDataPos) = val;
        return finishWrite(sizeof(val));
    }

    //扩展内存区
    //关注点2
    status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write;
    return err;
}

关注点 1 处,mDataPos = 0,sizeof(val) = 4,DataCapacity = 0,所以这里不会进入 if 分支。

接着关注点 2 处,这里会调用 growData 来做扩容操作:

cpp 复制代码
// len 是待写入数据的大小
status_t Parcel::growData(size_t len)
{
    // 关注点 3
    // 防御式编程,数据不合规范,直接返回
    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;
    }
    
    if (len > SIZE_MAX - mDataSize) return NO_MEMORY; // overflow
    if (mDataSize + len > SIZE_MAX / 3) return NO_MEMORY; // overflow

    // 关注点 4
    //mDataSize 用于表示 mData 指向的内存区域中已使用内存的大小,在构造函数中初始化为 0。
    // 按 1.5 倍的方式扩容
    size_t newSize = ((mDataSize+len)*3)/2; 
    return (newSize <= mDataSize)
            ? (status_t) NO_MEMORY
            : continueWrite(newSize);
}

关注点 3 处,有三个 if 判断,主要是检查数据是否正确,做一些防御式的判断。

关注点 4 处,首先以(原内存区长度 + 数据长度)* 1.5倍的方式计算出扩容后的内存区大小 newSize,接着再调用 continueWrite 函数:

cpp 复制代码
// 接着看 continueWrite 函数
status_t Parcel::continueWrite(size_t desired)
{
    //防御式检查
    if (desired > INT32_MAX) {
        return BAD_VALUE;
    }

    // mObjectsSize 表示 mData 中存储的 Object 的个数,初始化为 0
    size_t objectsSize = mObjectsSize;

    //关注点 5
    if (desired < mDataSize) { // desired = 4, mDataSize 初始值为 0 不进入if
        //......
    }

    if (mOwner) { // mOwner 初始值是 nullptr,不进入 if 分支
       //......
    } else if (mData) { // mData 初始值是 nullptr,不进入 if 分支
        //......
    } else {
        //关注点 6
        //分配内存
        uint8_t* data = (uint8_t*)malloc(desired);
        if (!data) {
            mError = NO_MEMORY;
            return NO_MEMORY;
        }
        //数据检查
        if(!(mDataCapacity == 0 && mObjects == nullptr
             && mObjectsCapacity == 0)) {
            ALOGE("continueWrite: %zu/%p/%zu/%zu", mDataCapacity, mObjects, mObjectsCapacity, desired);
        }

        //记录分配的内存区大小
        LOG_ALLOC("Parcel %p: allocating with %zu capacity", this, desired);
        pthread_mutex_lock(&gParcelGlobalAllocSizeLock);
        gParcelGlobalAllocSize += desired;
        gParcelGlobalAllocCount++;
        pthread_mutex_unlock(&gParcelGlobalAllocSizeLock);

        //修改相关变量
        mData = data;
        mDataSize = mDataPos = 0;
        ALOGV("continueWrite Setting data size of %p to %zu", this, mDataSize);
        ALOGV("continueWrite Setting data pos of %p to %zu", this, mDataPos);
        mDataCapacity = desired;
    }

    return NO_ERROR;
}

关注点 5 处,会做一些 if 判读,这些判断在代码中已做说明,它们都不会成立,最终会进入到最后一个分支 else 关注点 6 处。

关注点 6 处,通过 malloc 分配 4 个字节(desired = 4)的内存,分配完成后会做两个数据检查,接着通过 gParcelGlobalAllocSize gParcelGlobalAllocCount 两个变量来记录分配内存行为,最后将分配好的 uint8_t* data 指针赋值给 Parcel 的成员变量 mData,并修改 Parcel 内部记录内存区大小的内部成员。

上面的代码执行完成后,就会回到关注点 2 处,growData 执行成功,goto 到 restart_write 处:

cpp 复制代码
// 在 writeAligned 中调用 growData 分配好内存后,会接着调用 finishWrite 来写入数据
template<class T>
status_t Parcel::writeAligned(T val) {
    
    //关注点1
    if ((mDataPos+sizeof(val)) <= mDataCapacity) { // 不进 if
restart_write:
        //在分配的内存区中写入数据
        *reinterpret_cast<T*>(mData+mDataPos) = val;
        return finishWrite(sizeof(val));
    }

    //扩展内存区
    //关注点2
    status_t err = growData(sizeof(val));
    //goto 跳转到上面
    if (err == NO_ERROR) goto restart_write;
    return err;
}

在 restart_write 处会在分配的内存处写入 Int32 型数据 val,然后调用 finishWrite:

cpp 复制代码
status_t Parcel::finishWrite(size_t len)
{
    if (len > INT32_MAX) {
        return BAD_VALUE;
    }
    
    //修改位置标记变量
    mDataPos += len;
    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;
}

finishWrite 主要是用于修改 Parcel 内部一些变量的值,包括了 mDataPos mDataSize

以上代码就完成了 Int32 型数据的写入操作,接下来我们再来看看 Int32 数据的读取操作:

cpp 复制代码
int32_t Parcel::readInt32() const
{
    return readAligned<int32_t>();
}

readInt32 把所有的工作委托给了 readAligned:

cpp 复制代码
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) {
        if (mObjectsSize > 0) { //mObjectsSize 目前为 0,不进 if
          //......
        }

        const void* data = mData+mDataPos; //找到对应指针
        mDataPos += sizeof(T);
        *pArg =  *reinterpret_cast<const T*>(data); //返回数据
        return NO_ERROR;
    } else {
        return NOT_ENOUGH_DATA;
    }
}

上述代码总体逻辑还是比较简单,就是通过 mDataPos 找到数据所在的指针位置,然后将这个指针指向的数据赋值给传入的指针,完成数据读取操作。

Parcel 源码分析之 String16 类型数据读写

接着我们再来看看 String16 字符串数据的读写操作,我们先看一个实例:

cpp 复制代码
#define LOG_TAG "Parcel"
#include <cstdio>
#include <binder/Parcel.h>
#include <utils/Log.h>

using namespace android;
using namespace std;

int main(int argc, char const *argv[])
{
    
    Parcel data;
    data.writeString16(String16("IHelloService"));

    ALOGI("mDataSize is %d",data.dataSize());
    ALOGI("mDataPos is %d",data.dataPosition());
    ALOGI("mDataCapacity is %d",data.dataCapacity());

    data.setDataPosition(0); //从 0 开始位置开始读取数据

    String16 result = data.readString16();
    ALOGI("result is %s", String8(result).c_str());
    return 0;
}

writeString16 实现分析:

cpp 复制代码
status_t Parcel::writeString16(const String16& str)
{
    return writeString16(str.string(), str.size());
}

status_t Parcel::writeString16(const char16_t* str, size_t len)
{   
    //关注点 1
    //字符串为 null,写入一个 32 位 -1
    if (str == nullptr) return writeInt32(-1);

    //关注点 2
    //字符串不为空
    //先写入字符串的长度
    status_t err = writeInt32(len);
    if (err == NO_ERROR) {
        len *= sizeof(char16_t); //len 转换为字节数
        //writeInplace 4字节对齐的方式返回分配好的内存地址
        uint8_t* data = (uint8_t*)writeInplace(len+sizeof(char16_t));
        if (data) {
            memcpy(data, str, len); //字符串拷贝进去
            *reinterpret_cast<char16_t*>(data+len) = 0;
            return NO_ERROR;
        }
        err = mError;
    }
    return err;
}

关注点 1 处,如果写入的字符串是 nullptr,直接向 Parcel 写入一个 Int32 整型 -1,然后返回。

关注点 2 处,首先向 Parcel 写入一个 Int32 整型,这个整型就是待写入字符串的长度,接着调用 writeInplace,writeInplace 会以 4 字节对齐的方式返回分配好的内存地址。接着将字符串拷贝到这块内存中,完成字符串写入操作。

接下来,我们来看看 writeInplace 是如何分配内存的:

cpp 复制代码
void* Parcel::writeInplace(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 nullptr;
    }

    // 关注点 3
    // 4 字节对齐操作
    const size_t padded = pad_size(len);

    // sanity check for integer overflow
    if (mDataPos+padded < mDataPos) {
        return nullptr;
    }

    // 关注点 4
    if ((mDataPos+padded) <= mDataCapacity) { //mDataCapacity 初始化为 0,不进入 if
restart_write:
        //printf("Writing %ld bytes, padded to %ld\n", len, padded);
        uint8_t* const data = mData+mDataPos;

        // Need to pad at end?
        
        //因为我们做了 4 字节对齐,所以实际分配的内存数量可能比字符串本身需要的数量多
        //这里我们用一些固定值与操作来将多出来的尾部内存区域填充为 0
        if (padded != len) {
        //区分大小端
#if BYTE_ORDER == BIG_ENDIAN
            static const uint32_t mask[4] = {
                0x00000000, 0xffffff00, 0xffff0000, 0xff000000
            };
#endif
#if BYTE_ORDER == LITTLE_ENDIAN
            static const uint32_t mask[4] = {
                0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff
            };
#endif
            //printf("Applying pad mask: %p to %p\n", (void*)mask[padded-len],
            //    *reinterpret_cast<void**>(data+padded-4));
            *reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len];
        }

        //最后调用 finishWrite 修改 Parcel 内部的成员变量 
        finishWrite(padded);
        return data;
    }

    // 关注点 5
    status_t err = growData(padded); //扩展数据
    if (err == NO_ERROR) goto restart_write; //goto 跳转写数据
    return nullptr;
}

关注点 3 处,会对传入的字符串长度做一个 4 字节对齐操作:

cpp 复制代码
// 4 字节对齐操作,
// 比如 pad_size(1) 计算后的结果是 4
// pad_size(53) 计算后的结果是 56
static size_t pad_size(size_t s) {
    if (s > (SIZE_T_MAX - 3)) {
        abort();
    }
    return PAD_SIZE_UNSAFE(s);
}
#define PAD_SIZE_UNSAFE(s) (((s)+3)&~3)

关注点 4 处,mDataCapacity 初始化为 0,不会进入 if 分支

关注点 5 处会执行 growData 来完成内存区域的扩容,然后跳转到 restart_write,因为我们做了 4 字节对齐操作,所以实际分配的内存可能会比字符串本身需要的内存多出 0-3 个字节,在这里,通过与操作将内存区域最后多出的 0-3 个字节置为 0,然后调用 finishWrite 来设置 Parcel 内部的位置变量:

cpp 复制代码
status_t Parcel::finishWrite(size_t len)
{
    if (len > INT32_MAX) {
        return BAD_VALUE;
    }
    
    //修改位置标记变量
    mDataPos += len;
    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;
}

String16 的读操作总体比较简单,读操作的分析就留给读者了。

Parcel 源码分析之对象类型数据读写

接下来我们以添加 Native 系统服务回调中给出的服务回调注册过程来分析对象类型数据的读写操作。

cpp 复制代码
//客户端发起远程调用
::android::binder::Status BpHello::registerCallback(const ::android::sp<::com::yuandaima::ICallback>& cb) {
  // Parcel 初始化
  ::android::Parcel _aidl_data;
  ::android::Parcel _aidl_reply;
  ::android::status_t _aidl_ret_status = ::android::OK;
  ::android::binder::Status _aidl_status;
  //关注点 1
  //内部写入一些整型和字符串,没什么需要特别说明的
  _aidl_ret_status = _aidl_data.writeInterfaceToken(getInterfaceDescriptor());
  if (((_aidl_ret_status) != (::android::OK))) {
    goto _aidl_error;
  }

  //关注点 2
  //写入 IBinder 对象
  _aidl_ret_status = _aidl_data.writeStrongBinder(::com::yuandaima::ICallback::asBinder(cb));
  if (((_aidl_ret_status) != (::android::OK))) {
    goto _aidl_error;
  }

  //发起远程过程调用
  _aidl_ret_status = remote()->transact(::android::IBinder::FIRST_CALL_TRANSACTION + 2 /* registerCallback */, _aidl_data, &_aidl_reply);
  if (UNLIKELY(_aidl_ret_status == ::android::UNKNOWN_TRANSACTION && IHello::getDefaultImpl())) {
     return IHello::getDefaultImpl()->registerCallback(cb);
  }
  if (((_aidl_ret_status) != (::android::OK))) {
    goto _aidl_error;
  }
  _aidl_ret_status = _aidl_status.readFromParcel(_aidl_reply);
  if (((_aidl_ret_status) != (::android::OK))) {
    goto _aidl_error;
  }
  if (!_aidl_status.isOk()) {
    return _aidl_status;
  }
  _aidl_error:
  _aidl_status.setFromStatusT(_aidl_ret_status);
  return _aidl_status;
}

关注点 1 处调用了 writeInterfaceToken:

cpp 复制代码
//写入一些整型和字符串,没什么需要特别说明的
status_t Parcel::writeInterfaceToken(const String16& interface)
{
    const IPCThreadState* threadState = IPCThreadState::self();
    writeInt32(threadState->getStrictModePolicy() | STRICT_MODE_PENALTY_GATHER);
    updateWorkSourceRequestHeaderPosition();
    writeInt32(threadState->shouldPropagateWorkSource() ?
            threadState->getCallingWorkSourceUid() : IPCThreadState::kUnsetWorkSource);
    // currently the interface identification token is just its name as a string
    return writeString16(interface);
}

writeInterfaceToken 内部主要是写入一些整型和字符串,没什么需要特别说明的。

关注点 2 处调用了 writeStrongBinder 写入一个 IBinder 对象:

cpp 复制代码
// 写入 IBinder 对象,实际传入的是一个 BnCallback 对象
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}

在之前的分析文章我们介绍过,writeStrongBinder 函数参数实际传入的是一个 BnCallback 对象,其具体逻辑又交给了 flatten_binder 函数:

cpp 复制代码
status_t flatten_binder(const sp<ProcessState>& /*proc*/,
    const sp<IBinder>& binder, Parcel* out)
{   
    //定义一个 flat_binder_object 结构体
    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 != nullptr) {
        //localBinder() 返回 BnCallback 对象自己,BnCallback 的父类是 BBinder
        BBinder *local = binder->localBinder();
        if (!local) { 
            BpBinder *proxy = binder->remoteBinder();
            if (proxy == nullptr) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.hdr.type = BINDER_TYPE_HANDLE;
            obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
            obj.handle = handle;
            obj.cookie = 0;
        } else { //local 不为空,走这个分支
            if (local->isRequestingSid()) {
                obj.flags |= FLAT_BINDER_FLAG_TXN_SECURITY_CTX;
            }
            obj.hdr.type = BINDER_TYPE_BINDER; //重要变量1
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local); //重点变量2
        }
    } else {
        obj.hdr.type = BINDER_TYPE_BINDER;
        obj.binder = 0;
        obj.cookie = 0;
    }

    //接着调用 finish_flatten_binder 将构建好的 flat_binder_object 写入 Parcel
    return finish_flatten_binder(binder, obj, out);
}

flatten_binder 函数中会根据传入的 BnCallback 构建一个 flat_binder_object 结构体,我们主要需要注意两个核心变量:

cpp 复制代码
bj.hdr.type = BINDER_TYPE_BINDER; //重要变量1
obj.cookie = reinterpret_cast<uintptr_t>(local); //重点变量2,local 就是传入的 BnCallback

接着调用 finish_flatten_binder 将构建好的 flat_binder_object 写入 Parcel:

cpp 复制代码
inline static status_t finish_flatten_binder(
    const sp<IBinder>& /*binder*/, const flat_binder_object& flat, Parcel* out)
{
    //进一步调用 writeObject
    return out->writeObject(flat, false);
}

finish_flatten_binder 进一步调用 writeObject 来完成写入操作:

status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
    const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;
    const bool enoughObjects = mObjectsSize < mObjectsCapacity;
    //关注点 3
    if (enoughData && enoughObjects) { //都是 false 不会进入
restart_write:
        //内存区写入数据
        *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val;

        // remember if it's a file descriptor
        if (val.hdr.type == BINDER_TYPE_FD) {
            if (!mAllowFds) {
                // fail before modifying our object index
                return FDS_NOT_ALLOWED;
            }
            mHasFds = mFdsKnown = true;
        }

        // Need to write meta-data?
        if (nullMetaData || val.binder != 0) {
            //在 binder_size_t* mObjects 数组中记录 对象的偏移
            mObjects[mObjectsSize] = mDataPos;
            acquire_object(ProcessState::self(), val, this, &mOpenAshmemSize);
            mObjectsSize++;
        }

        return finishWrite(sizeof(flat_binder_object));
    }

    //关注点 4
    if (!enoughData) { //扩容
        const status_t err = growData(sizeof(val));
        if (err != NO_ERROR) return err;
    }

    //关注点 5
    //修改对象相关的内部变量
    //binder_size_t* mObjects 是一个 long long 型的数组,用于保存每个 flat_binder_object 在 mData 指向的内存块中的偏移值
    if (!enoughObjects) {
        if (mObjectsSize > SIZE_MAX - 2) return NO_MEMORY; // overflow
        if ((mObjectsSize + 2) > SIZE_MAX / 3) return NO_MEMORY; // overflow
        size_t newSize = ((mObjectsSize+2)*3)/2;
        if (newSize > SIZE_MAX / sizeof(binder_size_t)) return NO_MEMORY; // overflow
        binder_size_t* objects = (binder_size_t*)realloc(mObjects, newSize*sizeof(binder_size_t));
        if (objects == nullptr) return NO_MEMORY;
        mObjects = objects;
        mObjectsCapacity = newSize;
    }

    goto restart_write;
}

关注点 3 处,if 中两个条件都不会成立,代码直接执行到关注点 4 处,这里会调用 growData 进行扩容操作,扩容操作流程与 Int32 数据写入基本一致,这里不再重复,扩容完成后,代码执行到关注点 5 处,这里会给 mObjects mObjectsCapacity 重新赋值,mObjects 是一个 long long 型的数组,用于保存每个 flat_binder_object 在 mData 指向的内存块中的偏移值,mObjectsCapacity 用于记录 Parcel 可以保存的对象个数的上限。接着会通过 goto 语句执行到 restart_write 处,在这里首先会把传入的 flat_binder_object 对象写入分配好的内存中,然后在 mObjects 中记录好这个对象在内存块中的偏移值,最后调用 finishWrite 修改 Parcel 内存相关标记值,完成写对象操作。

接下来我们就来看看服务端如何读出客户端写入的 flat_binder_object 对象:

cpp 复制代码
//服务端对应远程调用
::android::status_t BnHello::onTransact(uint32_t _aidl_code, const ::android::Parcel& _aidl_data, ::android::Parcel* _aidl_reply, uint32_t _aidl_flags) {
  ::android::status_t _aidl_ret_status = ::android::OK;
  switch (_aidl_code) {
  //..... 省略其他 case
  //走这个 Case
  case ::android::IBinder::FIRST_CALL_TRANSACTION + 2 /* registerCallback */:
  {
    ::android::sp<::com::yuandaima::ICallback> in_cb;
    if (!(_aidl_data.checkInterface(this))) {
      _aidl_ret_status = ::android::BAD_TYPE;
      break;
    }
    _aidl_ret_status = _aidl_data.readStrongBinder(&in_cb);
    if (((_aidl_ret_status) != (::android::OK))) {
      break;
    }
    ::android::binder::Status _aidl_status(registerCallback(in_cb));
    _aidl_ret_status = _aidl_status.writeToParcel(_aidl_reply);
    if (((_aidl_ret_status) != (::android::OK))) {
      break; 
    }
    if (!_aidl_status.isOk()) {
      break;
    }
  }
  break;
  //...... 省略其他 case
  }
  if (_aidl_ret_status == ::android::UNEXPECTED_NULL) {
    _aidl_ret_status = ::android::binder::Status::fromExceptionCode(::android::binder::Status::EX_NULL_POINTER).writeToParcel(_aidl_reply);
  }
  return _aidl_ret_status;
}

这里,我们主要关注 readStrongBinder 读出数据的过程:

cpp 复制代码
status_t Parcel::readStrongBinder(sp<IBinder>* val) const
{
    status_t status = readNullableStrongBinder(val);
    if (status == OK && !val->get()) {
        status = UNEXPECTED_NULL;
    }
    return status;
}

readStrongBinder 内部进一步调用 readNullableStrongBinder:

cpp 复制代码
status_t Parcel::readNullableStrongBinder(sp<IBinder>* val) const
{
    return unflatten_binder(ProcessState::self(), *this, val);
}

readNullableStrongBinder 内部进一步调用 unflatten_binder:

cpp 复制代码
status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);

    if (flat) {
        switch (flat->hdr.type) {
            case BINDER_TYPE_BINDER: //走这个 case
                *out = reinterpret_cast<IBinder*>(flat->cookie);
                return finish_unflatten_binder(nullptr, *flat, in);
            case BINDER_TYPE_HANDLE:
                *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in);
        }
    }
    return BAD_TYPE;
}

unflatten_binder 中先调用 readObject 将 flat_binder_object 对象从 Parcel 的内部内存中读出,接着通过 flat_binder_object 的 cookie 成员将 IBinder 读出,不过这个 IBinder 对象已经被驱动动过手脚了,读出的是一个 BpCallback 对象,不再是写入时的 BnCallback 对象。

接下来我们来看看 readObject 的具体实现:

cpp 复制代码
const flat_binder_object* Parcel::readObject(bool nullMetaData) const
{
    const size_t DPOS = mDataPos;
    if ((DPOS+sizeof(flat_binder_object)) <= mDataSize) {
        //让 obj 指向 mData 中对应的 flat_binder_object 结构体
        const flat_binder_object* obj
                = reinterpret_cast<const flat_binder_object*>(mData+DPOS);
        mDataPos = DPOS + sizeof(flat_binder_object);
        if (!nullMetaData && (obj->cookie == 0 && obj->binder == 0)) {
            // When transferring a NULL object, we don't write it into
            // the object list, so we don't want to check for it when
            // reading.
            ALOGV("readObject Setting data pos of %p to %zu", this, mDataPos);
            return obj;
        }

        //修改一些内部变量
        // Ensure that this object is valid...
        binder_size_t* const OBJS = mObjects;
        const size_t N = mObjectsSize;
        size_t opos = mNextObjectHint;

        if (N > 0) {
            ALOGV("Parcel %p looking for obj at %zu, hint=%zu",
                 this, DPOS, opos);

            // Start at the current hint position, looking for an object at
            // the current data position.
            if (opos < N) {
                while (opos < (N-1) && OBJS[opos] < DPOS) {
                    opos++;
                }
            } else {
                opos = N-1;
            }
            if (OBJS[opos] == DPOS) {
                // Found it!
                ALOGV("Parcel %p found obj %zu at index %zu with forward search",
                     this, DPOS, opos);
                mNextObjectHint = opos+1;
                ALOGV("readObject Setting data pos of %p to %zu", this, mDataPos);
                return obj;
            }

            // Look backwards for it...
            while (opos > 0 && OBJS[opos] > DPOS) {
                opos--;
            }
            if (OBJS[opos] == DPOS) {
                // Found it!
                ALOGV("Parcel %p found obj %zu at index %zu with backward search",
                     this, DPOS, opos);
                mNextObjectHint = opos+1;
                ALOGV("readObject Setting data pos of %p to %zu", this, mDataPos);
                return obj;
            }
        }
        ALOGW("Attempt to read object from Parcel %p at offset %zu that is not in the object list",
             this, DPOS);
    }
    return nullptr;
}

首先从内存区读出数据,接着修改内部标记变量。调用完成后,回到 unflatten_binder 函数的 switch 分支调用到 finish_unflatten_binder,然后返回,结束读取过程。

cpp 复制代码
inline static status_t finish_unflatten_binder(
    BpBinder* /*proxy*/, const flat_binder_object& /*flat*/,
    const Parcel& /*in*/)
{
    return NO_ERROR;
}

参考资料

关于

我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,主要研究方向是 Android Framework 与 Linux Kernel。

如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

相关推荐
带电的小王2 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡2 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道2 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库3 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道4 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe4 小时前
Android Hook - 动态加载so库
android
居居飒5 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He8 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗8 小时前
Android笔试面试题AI答之Android基础(1)
android
qq_397562319 小时前
android studio更改应用图片,和应用名字。
android·ide·android studio