1 Parcel简介
- Parcel翻译过来就是打包的意思,其实就是包装了我们需要传输的数据,然后在Binder中传输,用于跨进程传输数据。
- Parcel提供了一套机制,可以将序列化之后的数据写入一个共享内存中,其他进程通过Parcel可以从这块共享内存读出字节流,并反序列化成对象
![](https://img.haomeiwen.com/i5982616/fa64f3397d5f4abf.png)
- Parcel是一个存放读取数据的容器,系统中的binder进程间通信(IPC)就使用了Parcel类来进行客户端与服务端数据交互,而且AIDL的数据也是通过Parcel来交互的。在Java层和C++层都实现了Parcel,由于它在C/C++中,直接使用了内存来读取数据,因此,它更有效率。
- 其实Parcel是内存中的结构的是一块连续的内存,会自动根据需要自动扩展大小
- Parcel传递数据,可以分为3种,传递方式也不一样:
- 小型数据:从用户空间(源进程)copy到kernel空间(Binder驱动中)再写回用户空间(目标进程,binder驱动负责寻找目标进程)
- 大型数据:使用android 的匿名共享内存 (Ashmem)传递
- binder对象:Kernel binder驱动专门处理
2 Parcel.java类介绍
主要讲解两个部分,一个是类注释,一个是核心方法。
2.1 Parcel.java类注释
-
Parcel是一个消息(数据和对象的引用)的容器,IBinder是android用于进程间通信的方式(IPC)。
-
Parcel可以携带序列化后(flattened/marshalled/serialized,通过使用多种类型的writing函数或者Parcelable接口)的数据,在IPC的另外一个反序列化数据(变回反序列化的对象),Parcel也可以携带活的IBinder对象,传递到Parcel中与原本IBinder相连的代理Binder中。
-
Parcel不是通用的序列化机制(Android特有的,Java的序列化机制是Serilizable,在效率上不如Parcel)。
-
这个class(以及相应的ParcelableAPI,用于将任意对象转换为Parcel)被设计为高性能的的IPC传输方式.
-
因此将Parcel数据放置在持久化存储位置是不合适的,任何基于Parcel中数据前世的变化都会导致旧数据不可读。
-
关于支持的类型,总结一下如下:
-
与Parcel相关的API有多种读写不同类型的方式,主要的6种类型如:
-
Primitives 基本数据类型
-
Primitives Arrays 基本数据类型数组
-
Parcelables 实现了Parcelables接口的对象
-
Bundle类
-
Active Objects 类 主要包括 Binder对象和FileDescriptor对象
-
Untyped Containers 容器,比如List、Map、SparseArray
2.2 常用方法
2.2.1 Parcel设置相关
母庸质疑,存入的数据越多,Parcel所占用的内存空间越大。
- dataSize():得到当前Parcel对象的实际存储空间
- setDataCapacity(int size):设置Parcel的空间大小,显然存储的数据不能大于这个值
- setDataPosition(int pos):改变Parcel中的读写位置(我喜欢叫偏移量),必须介于0和dataSize()间
- dataAvail():当前Parcel中的可读数据大小。
- dataCapacity():当前Parce的存储能力
- dataPosition():数据的当前位置值(偏移量),有点类似于游标
ps:如果写入数据时,系统发现已经超出了Parcel的存储能力,它会自动申请所需要的内存空间,并扩展dataCapacity;并且每次写入都是从dataPosition()开始的。
2.2.2 Primitives
原始类型数据的读写操作。比如:
- writeInt(int) :写入一个整数
- writeFloat(float) :写入一个浮点数(单精度)
- writeDouble(double):写入一个双精度
- writeString(string):写入一个字符串
- readInt(int) :读出一个整数
- readFloat(float) :读出一个浮点数(单精度)
- readDouble(double):读出一个双精度
- readString(string):读出一个字符串
2.2.3 Primitives Arrays
- 原始数据类型的数组读写操作通常是先用4个字节表示数据的大小值,接着才写入数据本身。
- 另外用户既可以选择将数据读入现有的数据空间中,也可以让Parcel返回一个新的数组。
- writeBooleanArray(boolean[]):写入布尔数组
- readBooleanArray(boolean[]):读出布尔数组
- boolean[] createBooleanArray():读取并返回一个布尔数组
- writeByteArray(byte[] ):写入字节数组
- writeByteArray(byte[],int , int ) 和上面几个不同的是,这个函数最后面的两个参数分别表示数组中需要被写入的数据起点以及需要写入多少。
- readByteArray(byte[]):读取字节数组
- byte[] createByteArray():读取并返回一个数组
2.2.4 Parcelables
遵循Parcelable协议的对象可以通过Parcel来存取,如开发人员经常用的的bundle就是实现Parcelable的,与此类对象相关的Parcel操作包括:
- writeParcelable(Parcelable,int):将这个Parcel类的名字和内容写入Parcel中,实际上它是通过回调此Parcelable的writeToParcel()方法来写入数据的。
- readParcelable(ClassLoader):读取并返回一个新的Parcelable对象
- writeParcelableArray(T[],int):写入Parcelable对象数组。
- readParcelable(ClassLoader):读取并返回一个Parcelable数组对象
2.2.5 Bundle
Bundle继承自Parcelable,是一种特殊的type-safe的容器。Bundle的最大特点是采用键值对的方式存储数据,并在一定程度上优化了读取效率。
- writeBundle(Bundle):将Bundle写入parcel
- readBundle():读取并返回一个新的Bundle对象
- readBundle(ClassLoader):读取并返回一个新的Bundle对象,ClassLoader用于Bundle获取对应的Parcelable对象。
2.2.6 Activity Object
- Parcel的另一个强大武器就是可以读写Active Object
- 通常我们存入Parcel的是对象的内容,而Active Object 写入的则是他们的特殊标志引用
- 所以在从Parcel中读取这些对象时,大家看到的并不是重新创建的对象实例,而是原来那个被写入的实例。
- 能够以这种方式传输的对象不会很多,目前主要有两类
- 1、Binder:Binder一方面是Android系统IPC通信的核心机制之一,另一方面也是一个对象。利用Parcel将Binder写入,读取时就能得到原始的Binder对象,或者是它的特殊代理实现(最终操作的还是原始Binder对象),与此相关的操作包括:
○ writeStrongBinder(IBinder)
○ writeStrongInterface(IInterface)
○ readStrongBinder() - 2、FileDescriptor:FileDescriptor是Linux中的文件描述符,可以通过Parcel如下方法进行传递
○ writeFileDescriptor(FileDescriptor)
○ readFileDescriptor()
因为传递后的对象仍然会基于和原对象相同的文件流进行操作,因而可以认为是Active Object的一种
2.2.7 Untyped Containers
它用于读写标准的任意类型的java容器。包括:
- writeArray(Object[])
- readArray(ClassLoader)
- writeList(List)
- readList(List, ClassLoader)
- readArrayList(ClassLoader)
- writeMap(Map)
- readMap(Map, ClassLoader)
- writeSparseArray(SparseArray)
- readSparseArray(ClassLoader)
2.2.8 Parcel创建与回收
- obtain():获取一个Parcel,可以理解new了一个对象,其实是有一个Parcel池
- recyle():清空,回收Parcel对象的内存
2.2.9 异常的读写
- writeException():在Parcel队头写入一个异常
- readException():在Parcel队头读取,若读取值为异常,则抛出该异常;否则程序正常运行
3 创建Parcl对象
3.1 obtain()方法
app可以通过Parcel。obtain()接口来获取一个Parcel对象。
/**
* Retrieve a new Parcel object from the pool.
* 从Parcel池中取出一个新的Parcel对象
*/
public static Parcel obtain() {
//系统预先产出的一个Parcel池(其实就是一个Parcel数组),大小为6
final Parcel[] pool = sOwnedPool;
synchronized (pool) {
Parcel p;
for (int i=0; i<POOL_SIZE; i++) {
p = pool[i];
if (p != null) {
//引用置为空,这样下次就知道这个Parcel已经被占用了
pool[i] = null;
if (DEBUG_RECYCLE) {
p.mStack = new RuntimeException();
}
return p;
}
}
}
//如果Parcel池中已经空,就直接新建一个。
return new Parcel(0);
}
在这里,我们要注意到这里的池其实是一个数组,从里面提取对象的时候,从头扫描到尾,找不到为null的手,直接new一个Parcle对象并返回。
3.2 Parcel构造函数
private Parcel(long nativePtr) {
if (DEBUG_RECYCLE) {
mStack = new RuntimeException();
}
//Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
init(nativePtr);
}
通过上面代码,我们知道,构造函数里面什么都没做,只是调用了init()函数,注意传入的是nativePtr是0
private void init(long nativePtr) {
if (nativePtr != 0) {
//如果nativePtr不是0
mNativePtr = nativePtr;
mOwnsNativeParcelObject = false;
} else {
//如果nativePtr是0
// nativeCreate() 为本地层代码准备的指针
mNativePtr = nativeCreate();
mOwnsNativeParcelObject = true;
}
}
private long mNativePtr; // used by native code
private static native long nativeCreate();
3.3 nativeCreate()方法
Android跨进程通信IPC之4——关于"JNI"的那些事
我们得知nativeCreate对应的是JNI层的/frameworks/base/core/jni中,实际上Parcel.java只是一个简单的中介,最终所有类型的读写操作都是通过本地代码实现的:
在android_os_Parcel.cpp中
//frameworks/base/core/jni/android_os_Parcel.cpp 551行
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
Parcel* parcel = new Parcel();
return reinterpret_cast<jlong>(parcel);
}
mNativePtr变量实际上是一个本底层的Parcel(C++)对象
Parcel-Native的构造函数
代码在Parcel.cpp 343行
Parcel::Parcel()
{
LOG_ALLOC("Parcel %p: constructing", this);
initState();
}
构造函数很简单就是调用了 initState() 函数,让我们来看下 initState()函数。
代码在Parcel.cpp 1901行
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;
mOpenAshmemSize = 0;
}
初始话很简单,几乎都是初始化为 0(NULL) 的
3.4 Parcel.h:
其实每一个Parcel对象都有一个Native对象相对应(以后均简称Parcel-Native对象,而Parcel对象均指Java层),该Native对象就是实际的写入读出的一个对象,java端对它的引用是上面mNativePtr。
对应的Native层的Parcel定义是在 /frameworks/native/inlcude/binder/Parcel.h
class Parcel {
public:
...
int32_t readInt32() const; // 举个例子
...
status_t writeInt32(int32_t val);
...
private:
uint8_t* mData;//数据存储的起始指针
size_t mDataSize;//总数据大小
size_t mDataCapacity;//总空间 (包括已用和可用)大小,这个空间是变长的
mutable size_t mDataPos;//当前数据可写入的内存其实位置
}
- Parcel对象的数据读取、写入操作都是最终通过Parcel-Native对象搞定的
- mData总是一块连续的内存地址,每一次其总空间大小增长都会通过realloc进行内存分配,如果数据量过大、内存碎片过多导致内存分配失败就会报错。
3.5 总结:
使用Parcel一般是通过Parcel.obtain()从对象池中获取一个新的Parcel对象,如果对象池中没有则直接new的Parcel则直接创建新的一个Parcel对象,并且会自动创建一个Parcel-Native对象。
4 Parcell对象回收
4.1 Parcel-Native对象的回收
- 创建Parcel对象的时候,会自动创建一个Parcel-Native对象。
- 在Parcel对象销毁时,即finalize()时回收
// Parcel.java
@Override
protected void finalize() throws Throwable {
if (DEBUG_RECYCLE) {
if (mStack != null) {
Log.w(TAG, "Client did not call Parcel.recycle()", mStack);
}
}
destroy();
}
private void destroy() {
if (mNativePtr != 0) {
if (mOwnsNativeParcelObject) {
nativeDestroy(mNativePtr);
updateNativeSize(0);
}
mNativePtr = 0;
}
}
private static native void nativeDestroy(long nativePtr);
在finalize()方法里面调用了 destroy()方法,而在destroy()方法里面调用了nativeDestroy(long)方法
//frameworks/base/core/jni/android_os_Parcel.cpp 567行
static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
delete parcel;//直接delete了 parcel对象
}
4.2 recycle()方法
- sOwneredPool算是一个Parcel对象池(sHolderPool也是,二者区别在于sOwnerPool用于保存Parcel-Native对象生命周期需要由自己管理的Parcel对象),可以复用之前创建好的Parcel对象,我们在使用完Parcel对象后,可以通过recycle方法回收到对象池中
/**
* Put a Parcel object back into the pool. You must not touch
* the object after this call.
* 将Parcel对象放回池中。 这次调用之后,你就无法继续联系他了
*/
public final void recycle() {
if (DEBUG_RECYCLE) mStack = null;
//Parcel-Native 对象的数据清空
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()启动,否则不能直接使用,因为它时刻都可能被其他地方获取使用导致数据错误。
5 存储空间
- Parcel内部的存储区域主要有两个,是mData和mObjects,mData存储是基本数据类型,mObjects存储Binder数据类型。
- Parcel提供了针对各种数据写入和读出的操作函数。
- 这两块区域都是使用malloc分配出来的。
flat_binder_object
- 在Parcel的序列化中,Binder对象使用flat_binder_object结构体保存。
- 同时提供了flatten_binder和unflatten_binder函数用于序列化和反序列化。
结构体代码在Linux的binder.h 68行
/*
* This is the flattened representation of a Binder object for transfer
* between processes. The 'offsets' supplied as part of a binder transaction
* contains offsets into the data where these structures occur. The Binder
* driver takes care of re-writing the structure type and data as it moves
* between processes.
*/
struct flat_binder_object {
struct binder_object_header hdr;
__u32 flags;
/* 8 bytes of data. */
union {
binder_uintptr_t binder; /* local object */
__u32 handle; /* remote object */
};
/* extra data associated with local object */
binder_uintptr_t cookie;
};
- Binder实体或引用在传递时,被表示为flat_binder_object,flat_binder_object的type域表示传输Binder的类型。
- 跨进程的时候:flat_binder_object的type为BINDER_TYPE_HANDLE跨进程的时候
- 非跨进程的时候:flat_binder_object的type为BINDER_TYPE_BINDER
6 关于偏移量
那么Parcel内部存储机制是怎么样的?偏移量又是什么东西?
6.1 基本类型
类型 | bit数量 | 字节 |
---|---|---|
boolean | 1 bit | 1字节 |
char | 16bit | 2字节 |
int | 32bit | 4字节 |
long | 64 bit | 8 字节 |
float | 32 bit | 4 字节 |
double | 64bit | 8字节 |
- C语言中结构体的内存对齐和Parcel采用的内存存放机制一样,即读取最小字节为32bit,也即4个字节。
- 高于4个字节的,以实际数据类型进行存放,但得为4byte的倍数。
基本公式如下:
- 实际存放字节:
辨别一:32bit (<=32bit) 例如:boolean,char,int
辨别二:实际占用字节(>32bit) 例如:long,float,String 等- 实际读取字节:
辨别一:32bit (<=32bit) 例如:boolean,char,int
辨别二:实际占用字节(>32bit) 例如:long,float,String 组等
所以,当我们写入/读取一个数据时,偏移量至少为4byte(32bit),于是,偏移量的公式如下:
f(x)= 4x (x=0,1,....n)
事实上,我们可以通过setDataPosition(int position)来直接操作我们欲读取数据时的偏移量。毫无疑问,你可以设置任何便宜量,但是所读取值的类型是有误的。因此设置便宜量读取值的时候,需要小心。
6.2 注意事项
在writeXXX()和readXXX()时,导致的偏移量时共用的,例如我们在writeIn(23)后,此时的dataposition=4,如果我想读取它,简单的通过readInt()是不行的,只能得到0,这时我们只能通过setDataPosition(0)设置为起始偏移量,从起始位置读取四个字节,即可得23。因此,在读取某个值时,需要使用setDataPosition(int)使偏移量偏移到我们的指定位置。
6.3 取值规范
由于可能存在读取值的偏差,一个默认的取值规范为:
- 1、读取复杂对象时:对象匹配时,返回当前偏移位置的对象;对象不匹配时,返回null。
- 2、读取简单对象时:对象匹配时,返回当前偏移位置的对象:对象不匹配时,返回0。
6.4 存放空间图
![](https://img.haomeiwen.com/i5982616/6d46709de51bf35a.png)
7 Int类型数据写入
以writeInt()为例进行数据写入的跟踪
时序图如下:
![](https://img.haomeiwen.com/i5982616/ee889e82efee47b5.jpeg)
7.1 Parcel.writeInt(int)
/**
* Write an integer value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public final void writeInt(int val) {
nativeWriteInt(mNativePtr, val);
}
- 方法翻译如下:
- 在当前的dataPosition()中,将一个int的值写入 Parcel中,如果空间不足,则扩容。
- 通过代码我们知道其实调用的nativeWriteInt(long nativePtr, int val),我们知道nativeWriteInt(long nativePtr, int val)其实对应的是JNI的方法
7.2 (2) android_os_Parcel_writeInt(JNIEnv*,jclass,jlong,jint)函数
代码在android_os_Parcel.cpp 233行
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
const status_t err = parcel->writeInt32(val);
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
}
}
调用的 Parcel-Native类的writeInt32(jint)函数
7.3 Parcel::writeInt32(int32_t val)函数
代码在[Parcel.cpp]http://androidxref.com/6.0.1_r10/xref/frameworks/native/libs/binder/Parcel.cpp) 748行
status_t Parcel::writeInt32(int32_t val)
{
return writeAligned(val);
}
我们看到实际上是调用的 Parcel-Native类的writeAligned()函数
7.4 Parcel::writeAligned(T val)函数
代码在Parcel.cpp 1148行
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));
}
//分支一 刚刚创建Parcel-Native对象
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
- writeAligned看名字就知道是内存对齐
- writeAligned()函数判断当前pos+要写入的数据占用的内存 是否比mDataCapacity大,如果大,就是空间不足,需要自动增长空间,走growData()
- 这里我们假设刚刚创建了 Parcel-Native对象。这时候mDataCapacity=0,mDataPos=0,mData=0,mDataSizeDataSize=0,所这时候不走分支二,走分支一 ,在分支一 里面调用了growData()函数 分配内存
7.5 Parcel::growData(size_t len)函数
代码在Parcel.cpp 1683行
status_t Parcel::growData(size_t len)
{
//如果超过int的最大值
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;
}
size_t newSize = ((mDataSize+len)*3)/2;
//通过上面的代码newSize一般来说都会大于mDataSize
return (newSize <= mDataSize)
? (status_t) NO_MEMORY
: continueWrite(newSize);
}
PS:这里是parcel的增长算法,((mDataSize+len)3/2);*带有一定预测性的增长,避免频繁的空间调整(每次调整都需要重新malloc内存的,频繁的话会影响效率)。然后这里有个newSize< mDataSize就认为NO_MEMORY。这是如果溢出了(是负数),就认为申请不到内存了。
- 因为newSize一般来说都会大于mDataSize,所以函数最后走到了continueWrite(newSize)函数里面去了
7.6 Parcel::continueWrite(size_t desired)函数
代码在Parcel.cpp 1743行
status_t Parcel::continueWrite(size_t desired)
{
if (desired > INT32_MAX) {
// don't accept size_t values which may have come from an
// inadvertent conversion from a negative int.
return BAD_VALUE;
}
// If shrinking, first adjust for any objects that appear
// after the new data size.
size_t objectsSize = mObjectsSize;
if (desired < mDataSize) {
if (desired == 0) {
objectsSize = 0;
} else {
while (objectsSize > 0) {
if (mObjects[objectsSize-1] < desired)
break;
objectsSize--;
}
}
}
//分支一
if (mOwner) {
// If the size is going to zero, just release the owner's data.
if (desired == 0) {
freeData();
return NO_ERROR;
}
// If there is a different owner, we need to take
// posession.
uint8_t* data = (uint8_t*)malloc(desired);
if (!data) {
mError = NO_MEMORY;
return NO_MEMORY;
}
binder_size_t* objects = NULL;
if (objectsSize) {
objects = (binder_size_t*)calloc(objectsSize, sizeof(binder_size_t));
if (!objects) {
free(data);
mError = NO_MEMORY;
return NO_MEMORY;
}
// Little hack to only acquire references on objects
// we will be keeping.
size_t oldObjectsSize = mObjectsSize;
mObjectsSize = objectsSize;
acquireObjects();
mObjectsSize = oldObjectsSize;
}
if (mData) {
memcpy(data, mData, mDataSize < desired ? mDataSize : desired);
}
if (objects && mObjects) {
memcpy(objects, mObjects, objectsSize*sizeof(binder_size_t));
}
//ALOGI("Freeing data ref of %p (pid=%d)", this, getpid());
mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie);
mOwner = NULL;
LOG_ALLOC("Parcel %p: taking ownership of %zu capacity", this, desired);
pthread_mutex_lock(&gParcelGlobalAllocSizeLock);
gParcelGlobalAllocSize += desired;
gParcelGlobalAllocCount++;
pthread_mutex_unlock(&gParcelGlobalAllocSizeLock);
mData = data;
mObjects = objects;
mDataSize = (mDataSize < desired) ? mDataSize : desired;
ALOGV("continueWrite Setting data size of %p to %zu", this, mDataSize);
mDataCapacity = desired;
mObjectsSize = mObjectsCapacity = objectsSize;
mNextObjectHint = 0;
//分支二
} else if (mData) {
if (objectsSize < mObjectsSize) {
// Need to release refs on any objects we are dropping.
const sp<ProcessState> proc(ProcessState::self());
for (size_t i=objectsSize; i<mObjectsSize; i++) {
const flat_binder_object* flat
= reinterpret_cast<flat_binder_object*>(mData+mObjects[i]);
if (flat->type == BINDER_TYPE_FD) {
// will need to rescan because we may have lopped off the only FDs
mFdsKnown = false;
}
release_object(proc, *flat, this, &mOpenAshmemSize);
}
binder_size_t* objects =
(binder_size_t*)realloc(mObjects, objectsSize*sizeof(binder_size_t));
if (objects) {
mObjects = objects;
}
mObjectsSize = objectsSize;
mNextObjectHint = 0;
}
// We own the data, so we can just do a realloc().
if (desired > mDataCapacity) {
uint8_t* data = (uint8_t*)realloc(mData, desired);
if (data) {
LOG_ALLOC("Parcel %p: continue from %zu to %zu capacity", this, mDataCapacity,
desired);
pthread_mutex_lock(&gParcelGlobalAllocSizeLock);
gParcelGlobalAllocSize += desired;
gParcelGlobalAllocSize -= mDataCapacity;
pthread_mutex_unlock(&gParcelGlobalAllocSizeLock);
mData = data;
mDataCapacity = desired;
} else if (desired > mDataCapacity) {
mError = NO_MEMORY;
return NO_MEMORY;
}
} else {
if (mDataSize > desired) {
mDataSize = desired;
ALOGV("continueWrite Setting data size of %p to %zu", this, mDataSize);
}
if (mDataPos > desired) {
mDataPos = desired;
ALOGV("continueWrite Setting data pos of %p to %zu", this, mDataPos);
}
}
//分支三
} else {
// This is the first data. Easy!
uint8_t* data = (uint8_t*)malloc(desired);
if (!data) {
mError = NO_MEMORY;
return NO_MEMORY;
}
if(!(mDataCapacity == 0 && mObjects == NULL
&& 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;
}
- 简单说下三个主要分支
- 分支一:如果设置了release函数指针(即mOwener),调用release函数进行处理
- 分支二:没有设置release函数指针,但是mData中存在数据,需要在原来的数据的基础上扩展存储空间。
- 分支三:没有设置release函数指针,并且mData不存在数据(就是注释上说的第一次使用),调用malloc申请内存块,保存mData。设置相应的设置capacity、size、pos、object的值。
这里应该走分支分支三,分支三很简单,主要是调用malloc()方法分配一块(mDataSize+size(val))3/2大小的内存,然后让mData指向该内存,并且将这里可以归纳一下,growData()方法只是分配了一内存。
- 根据返回值,又回到了Parcel::writeAligned(T val)中,由于返回值是NO_ERROR,所以就走到了goto restart_write ,这样就又到了Parcel::writeAligned(T val) 分支二中
7.7 Parcel::writeAligned(T val)函数 分支二
分支二代码就两行,如下
*reinterpret_cast<T*>(mData+mDataPos) = val;
return finishWrite(sizeof(val));
- 1、reinterpret_cast<T>(mData+mDataPos) = val; 这行代码是直接获取当前地址强制转化指针类型,然后赋值。
- 2、调用finishWrite()函数
7.8 Parcel::finishWrite(size_t len)函数
代码在Parcel.cpp 642行
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;
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;
}
- mDataPos增加到刚刚写入数据的末尾,并且进行一个判断,如果mDataPos>mDataSize的话,就将mDataSize=mDataPos;
- 而实际上mDataSize在赋值前还是0,所以会进行这个赋值操作,因此我们可以知道,其实mDataSize是记录当前mData中写入数据的大小。
8 Int类型数据读出
以readInt()为例进行数据写入的跟踪
时序图如下:
![](https://img.haomeiwen.com/i5982616/2513ae6ab5ba1ffa.jpeg)
8.1 Parcel.readInt(int)
/**
* Read an integer value from the parcel at the current dataPosition().
*/
public final int readInt() {
return nativeReadInt(mNativePtr);
}
private static native int nativeReadInt(long nativePtr);
- 从当前dataPosition()的位置上读取一个Interger值
- 通过代码我们知道其实是调用的nativeReadInt(long nativePtr),我们知道nativeReadInt(long nativePtr)其实对应的是JNI的方法
8.2 android_os_Parcel_readInt(JNIEnv* env, jclass clazz, jlong nativePtr) 函数
代码在android_os_Parcel.cpp 379行
static jint android_os_Parcel_readInt(JNIEnv* env, jclass clazz, jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
return parcel->readInt32();
}
return 0;
}
8.3 Parcel::readInt32() 函数
代码在Parcel.cpp 1168行
int32_t Parcel::readInt32() const
{
return readAligned<int32_t>();
}
8.4 readAligned<int32_t>() 函数
代码在Parcel.cpp 1138行
template<class T>
T Parcel::readAligned() const {
T result;
if (readAligned(&result) != NO_ERROR) {
result = 0;
}
return result;
}
其实内部是有调用了Parcel::readAligned(T *pArg)函数
注意:Parcel::readAligned(T *pArg)和 Parcel::readAligned()的区别,一个是有入参的,一个是无入参的。
8.5 readAligned<int32_t>() 函数
代码在Parcel.cpp 1124行
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);
*pArg = *reinterpret_cast<const T*>(data);
return NO_ERROR;
} else {
return NOT_ENOUGH_DATA;
}
这个就是根据 mData+mDataPos 和具体的类型,进行强制类型转化获取对应的值。
8.6 注意事项:
同进程情况下,数据读取过程跟写入几乎一致,由于使用的是同一个Parcel对象,mDataPos首先要调整到0之后才能读取,同进程数据写入/读取并不会有什么效率提高,仍然会进行内存的考虑和分配,所以一般来说尽量避免同进程使用Parcel传递大数据。
Android跨进程通信IPC之6——Parcel--Binder对象的写入和读出
网友评论