想要深入的学习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结构体中,这个结构体很简单,所以也不占空间
参考文章: