想要深入的学习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 usedif (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;//1ALOGV("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驱动启动 - 简书

Android Parcel数据传输源码解析相关推荐

  1. http://a.codekk.com/detail/Android/grumoon/Volley 源码解析

    http://a.codekk.com/detail/Android/grumoon/Volley 源码解析

  2. BAT高级架构师合力熬夜15天,肝出了这份PDF版《Android百大框架源码解析》,还不快快码住。。。

    前言 为什么要阅读源码? 现在中高级Android岗位面试中,对于各种框架的源码都会刨根问底,从而来判断应试者的业务能力边际所在.但是很多开发者习惯直接搬运,对各种框架的源码都没有过深入研究,在面试时 ...

  3. Android通知系统源码解析

    Android通知系统源码解析 1. 概述 2. 流程图 2.1. 发送通知流程图 3. 源码解析 3.1. 使用通知--APP进程 3.1.1. 创建通知: 3.1.2. 发送(更新)通知: 3.1 ...

  4. Android Gradle Plugin 源码解析(上)

    一.源码依赖 本文基于: android gradle plugin版本: com.android.tools.build:gradle:2.3.0 gradle 版本:4.1 Gradle源码总共3 ...

  5. Android之EasyPermissions源码解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! 前言 我们知道在Android中想要申请权限就需要在AndroidManifest ...

  6. Android之AsyncTask源码解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 AsyncTask是一种轻量级的异步任务类,内部封装了Thread和Ha ...

  7. Android之DiskLruCache源码解析

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/73863258 本文出自:[顾林海的博客] 个人开发的微信小程序,目前功 ...

  8. Android Hawk的源码解析,一款基于SharedPreferences的存储框架

    转载请标注:http://blog.csdn.net/friendlychen/article/details/76218033 一.概念 SharedPreferences的使用大家应该非常熟悉啦. ...

  9. Android之LocalBroadcastManager源码解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! 前言 广播想必大家都不陌生,日常开发中同一个APP中的多个进程之间需要进行传输信息 ...

  10. android debug database 源码解析

    我们今天分析下android debug database 的源码: 项目地址: https://github.com/amitshekhariitbhu/Android-Debug-Database ...

最新文章

  1. 《麻省理工学院技术评论》评出最新十大突破性技术
  2. 解决linux服务器掉包问题
  3. python序列类型-python序列类型有哪些
  4. python2基础教程廖雪峰云-Python 基础教程
  5. NYOJ 663 弟弟的作业
  6. 记录Docker in Docker 安装(CentOS7)
  7. 编程语言的发展趋势及未来方向(5):元编程
  8. 外设驱动库开发笔记38:RTD热电阻测温驱动
  9. Java StringBuilder trimToSize()方法与示例
  10. SQL63 刷题通过的题目排名
  11. 2020-08-09
  12. python与excel-Python 与 Excel 不得不说的事
  13. python静态方法怎么调用_python实例方法、静态方法和类方法
  14. Android中生成库文件与移除以及导入jar包重复问题
  15. OpenCV4.5.1 | 使用一行代码将图像匹配性能提高14%
  16. 应用comsol模拟水力压裂应力分布
  17. Pointofix非常好用的一款屏幕书写软件
  18. 手写SSH2服务器连接池
  19. 【ELMAN回归预测】基于matlab天牛须算法优化ELMAN回归预测【含Matlab源码 1375期】
  20. LeetCode——1900. 最佳运动员的比拼回合(The Earliest and Latest Rounds Where Players Compete)[困难]——分析及代码(Java)

热门文章

  1. 图片识别不了小程序怎么办_图片转文字【小程序】
  2. atan java_Java Math atan() 使用方法及示例
  3. oracle svip地址,木子李QQ8.9 显IP地址SVIP完整版
  4. JAVA 生成二维码 并设置 +失效机制
  5. 计算机工程与应用出版时间,计算机工程与应用
  6. 中小网吧网络安全解决方案(转)
  7. 年薪45万阿里程序员想跳槽vivo,当他晒出期望工资,以为我看错了
  8. 手把手教用爬虫爬sciencedirect学术研究
  9. jquery插件——cookie
  10. 防治脖子痛的简易保健操