Android Binder框架跨进程传输的,基本都是通过Parcel来封装的数据[service_manager除外],这个是十分底层的实现了。对于Framework开发来说,除了了解RPC的抽象意义外,对底层的具体通信细节的
实现,也是要比较了解的,这里简单记录一下RPC通信数据传递的过程。
1. parcel是啥
Parcel主要是方便实现上层抽象对象或数据打包做跨进程传输而封装的一个类,类名的字面意思也差不多说明了。其作用,说白了,就是将要写入的数据,规整到一个连续的buffer内存中,同时记录一些数据信息属性。远端进程可接收后根据这些属性和读取顺序来克隆还原。
连续buffer内存方便驱动实现,同时效率也高。目前Android中,除了service_manager外[太简单了,而且纯C写的..],都是走parcel。
2.一个简单的binder通信例子
这里,我们简单的用一个dump服务的接口实现,来看一下在binder通信过程中,数据在进程中的传递过程。
#include <utils/Atomic.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <binder/IBinder.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <stdio.h>
using namespace android;
int main() {
status_t err = NO_ERROR;
const sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> ibinder = sm->getService(String16("activity"));
// 这里开始Binder传输,请求服务端执行dump,只有发送数据,不需要接收数据
if (ibinder != NULL ) {
// 构造两个空的Parcel,一个是用于发送,一个用于接受
Parcel send;
Parcel reply;
send.writeFileDescriptor(STDOUT_FILENO); // 写入文件FD.
send.writeInt32(0); // dump命令的参数个数,这里是0个,不带参数了。
status_t err = ibinder->transact(IBinder::DUMP_TRANSACTION, send, &reply);
}
// 这是binder传输,获取服务的descriptor, 不需要发送数据,只需要接收数据
{
Parcel send, reply;
status_t err = ibinder->transact(IBinder::INTERFACE_TRANSACTION, send, &reply);
if (err == NO_ERROR) {
printf("Read service interface: %s\n", String8(reply.readString16()).string());
}
}
return 0;
}
通信过程比较简单,将要传递的数据,通过Parcel的writeXXX系列函数写入到Parcel中,然后调用IBinder的transact,发起远程通信请求,通信的数据结果,存在Parcel对象reply中,从这个对象中读出结果即可。
Binder通信的详细流程不分析了,扒一下Binder驱动和IPCThreadState,整个Binder的一次传输过程,可以用如下图简单描述一下:
通信过程的数据传递,后面接着看一下。
3. 发送时待传输的Parcel对象
Binder通信基本上都是通过Parcel来传递数据的,
调用Parcel.writexxx将要传递数据存到对象中. 看下流程。
本地端,启动服务传输:
Parce构造函数,实际没做什么事情,没有分配存储对象的内存:
Parcel::Parcel()
{
LOG_ALLOC("Parcel %p: constructing", this);
initState();
}
..
void Parcel::initState()
{
LOG_ALLOC("Parcel %p: initState", this);
mError = NO_ERROR;
mData = 0;
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 = NULL;
mObjectsSize = 0;
mObjectsCapacity = 0;
mNextObjectHint = 0;
mHasFds = false;
mFdsKnown = true;
mAllowFds = true;
mOwner = NULL;
}
Parcel存储数据的内存缓冲[mData],是在写入对象时(这里基本数据类型,也可以看做对象,实现稍有差异,不过原理都一样),根据写入数据,自动调整的。看一下上面例子中,写入parcel的方法:
1. send
.
writeFileDescriptor
(
STDOUT_FILENO
);
时,
status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership)
{
flat_binder_object obj;
obj.type = BINDER_TYPE_FD;
obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
obj.handle = fd;
obj.cookie = takeOwnership ? 1 : 0;
return writeObject(obj, true);
}
这里传输fd会比较特殊。fd虽然是一个整形,但它代表的是一个文件句柄,跨进程传递到对方进程中。
代码也比较简单,把fd封装到flat_binder_object中,再通过writeObject写入Parcel中。
sta