着急的先看结论:Parcelable 的成员变量读写需要严格的遵循 先写先读、后写后读的原则。所以出现数据读取异常,大概率是因为读取顺序和写入顺序不一致。 想学东西的看后文分析 如下例子 public class PackageInfo implements Parcelable { public String packageName="com.exemple.aa"; public int appId = 0; public int appMode = 0; public String apkPath="/data/data/com.eae.fgf/file/a.apk"; @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.packageName); dest.writeInt(this.appId); dest.writeString(this.apkPath); dest.writeInt(this.appMode); } PackageSetting(int version, Parcel in) { this.packageName = in.readString(); this.appId = in.readInt(); this.appMode = in.readInt(); this.apkPath = in.readString(); }
}
写入 appMode的时候写入的是0 ,读取的时候却读到了65535,简直是一头雾水。
原因就是在写入的时候第三个写入的是 dest.writeString(this.apkPath);,而读取的时候第三个读取的却是 this.appMode = in.readInt(); 所以实际上this.appMode = in.readInt()读取到的是apkPath的值。
我们来看一下writeInt源码
public final void writeInt(int val) {
nativeWriteInt(mNativePtr, val);
}
我们可以看到具体的写入是由native层的C代码实现的
private static native void nativeWriteInt(long nativePtr, int val);
第一个参数为 mNativePtr
第二个参数就是我们要写入的值
所以我们需要知道第一个参数是怎么来的,这得从Parcel创建说起
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); }
private Parcel(long nativePtr) { if (DEBUG_RECYCLE) { mStack = new RuntimeException(); } //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack); init(nativePtr); }
private void init(long nativePtr) { if (nativePtr != 0) { mNativePtr = nativePtr; mOwnsNativeParcelObject = false; } else { mNativePtr = nativeCreate(); mOwnsNativeParcelObject = true; } }
我们可以看到parcel池存列表存在 parcel对象的话就将该位置的parcel对象拿出来用,并且池的原位置置空。那样的话mNativePtr就沿用旧对象的。如果parcel池不存在parcel对象那就重新创建一个。那么就 mNativePtr = nativeCreate();
nativeCreate由native方法实现。让我们来看看C层代码
\frameworks\base\core\jni\android_os_Parcel.cpp
static const JNINativeMethod gParcelMethods[] = { ...... {"nativeCreate", "()J", (void*)android_os_Parcel_create}, ...... {"nativeWriteInt", "(JI)V", (void*)android_os_Parcel_writeInt}, {"nativeWriteLong", "(JJ)V", (void*)android_os_Parcel_writeLong}, {"nativeWriteFloat", "(JF)V", (void*)android_os_Parcel_writeFloat}, {"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble}, {"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString}, ...... {"nativeReadInt", "(J)I", (void*)android_os_Parcel_readInt}, {"nativeReadLong", "(J)J", (void*)android_os_Parcel_readLong}, {"nativeReadFloat", "(J)F", (void*)android_os_Parcel_readFloat}, {"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble}, {"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString}, ...... };所以 native long nativeCreate() 的实现即
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz) { // Create a Parcel object for the native layer Parcel* parcel = new Parcel(); // Returns the long value of its pointer return reinterpret_cast<jlong>(parcel); }所以 Java 层的 Parcel 的 mNativePtr 成员变量,实际上是持有着 C层 Parcel 结构体指针地址的整型值。
我们回过头来再看 nativeWriteInt的实现 android_os_Parcel_writeInt
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) { // nativePtr is Android_ os_ Parcel_ The pointer value of the parcel object returned by create. Here, the parcel pointer is strongly returned Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel != NULL) { // Use the Parcel pointer to call its writeInt32() method to write data const status_t err = parcel->writeInt32(val); if (err != NO_ERROR) { signalExceptionForError(env, clazz, err); } } }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)); // Determine whether the memory is sufficient if ((mDataPos+sizeof(val)) <= mDataCapacity) { restart_write: // Move the pointer to the offset position (first address + pointer offset), and then write val to memory *reinterpret_cast<T*>(mData+mDataPos) = val; // Update memory offset return finishWrite(sizeof(val)); } status_t err = growData(sizeof(val)); if (err == NO_ERROR) goto restart_write; return err; }
从源码分析可知 Java 层的 Parcel 创建时,实际上持有了 C层 Parcel 结构体的绝对地址指针。我们在Java层调用 dest.writeInt(), 系统会根据 Java 层 Parcel 持有的 C 层的 Parcel 结构体的指针,找到 C 层 Parcel 结构体的实际对象。并把数据写入到 Parcel 结构体中。
所以实际上数据的读写和存储是在 C层完成的,并且存储在内存中。
那么C层Parcel 结构体是怎么运作的呢?
我们继续来看写入数据
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));
}
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
status_t Parcel::finishWrite(size_t len) { if (len > INT32_MAX) {//数据量大于int最大值,抛出异常 // 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; }
status_t Parcel::growData(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;
}
size_t newSize = ((mDataSize+len)*3)/2; // 每次扩容都增加原来总长度的一半。防止频繁和过度扩容
return (newSize <= mDataSize)
? (status_t) NO_MEMORY
: continueWrite(newSize);
}
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 = nullptr;
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 = nullptr;
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;
mObjectsSorted = false;
} 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->hdr.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);
}
if (objectsSize == 0) {
free(mObjects);
mObjects = nullptr;
mObjectsCapacity = 0;
} else {
binder_size_t* objects =
(binder_size_t*)realloc(mObjects, objectsSize*sizeof(binder_size_t));
if (objects) {
mObjects = objects;
mObjectsCapacity = objectsSize;
}
}
mObjectsSize = objectsSize;
mNextObjectHint = 0;
mObjectsSorted = false;
}
// 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 {
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 == 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;
}
我们再来看看读取数据
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) {
status_t err = validateReadData(mDataPos + sizeof(T));//校验读取的数据
if(err != NO_ERROR) {
// Still increment the data position by the expected length
mDataPos += sizeof(T);//读取错误数据照常指针后移,不妨碍下一个读取
return err;
}
}
const void* data = mData+mDataPos;//读取数据的指针
mDataPos += sizeof(T);
*pArg = *reinterpret_cast<const T*>(data);//reinterpret_cast会逐个比特的复制T数据类型同等长度的数据,超长部分不会读取。
return NO_ERROR;
} else {
return NOT_ENOUGH_DATA;
}
}
status_t Parcel::validateReadData(size_t upperBound) const
{
// Don't allow non-object reads on object data
if (mObjectsSorted || mObjectsSize <= 1) {
data_sorted:
// Expect to check only against the next object
if (mNextObjectHint < mObjectsSize && upperBound > mObjects[mNextObjectHint]) {
// For some reason the current read position is greater than the next object
// hint. Iterate until we find the right object
size_t nextObject = mNextObjectHint;
do {
if (mDataPos < mObjects[nextObject] + sizeof(flat_binder_object)) {
// Requested info overlaps with an object
ALOGE("Attempt to read from protected data in Parcel %p", this);
return PERMISSION_DENIED;
}
nextObject++;
} while (nextObject < mObjectsSize && upperBound > mObjects[nextObject]);
mNextObjectHint = nextObject;
}
return NO_ERROR;
}
// Quickly determine if mObjects is sorted.
binder_size_t* currObj = mObjects + mObjectsSize - 1;
binder_size_t* prevObj = currObj;
while (currObj > mObjects) {
prevObj--;
if(*prevObj > *currObj) {
goto data_unsorted;
}
currObj--;
}
mObjectsSorted = true;
goto data_sorted;
data_unsorted:
// Insertion Sort mObjects
// Great for mostly sorted lists. If randomly sorted or reverse ordered mObjects become common,
// switch to std::sort(mObjects, mObjects + mObjectsSize);
for (binder_size_t* iter0 = mObjects + 1; iter0 < mObjects + mObjectsSize; iter0++) {
binder_size_t temp = *iter0;
binder_size_t* iter1 = iter0 - 1;
while (iter1 >= mObjects && *iter1 > temp) {
*(iter1 + 1) = *iter1;
iter1--;
}
*(iter1 + 1) = temp;
}
mNextObjectHint = 0;
mObjectsSorted = true;
goto data_sorted;
}
结论:
我们大致可以知道 C层里面 Parcel 结构体 的 mData用来实际存储数据,是实际存储数据内存的指针。mDataPos是当前已经存储的数据量,mDataCapacity是当前已经动态分配的内存容量。
所以实际上 Parcel 通过 创建Parcel 结构体来 赋予 Java 层 Parcel 一个可以调用结构体的指针。并且封装了一系列的Java 方法来操控 mData上存储的数据。
当写入数据的时候,根据当前调用的方法类型写入数据,指针向后偏移该类型的数据长度(对齐写入)。所以写入是按调用顺序逐个存入当前调用数据类型的长度。而读取也是一样,按照读取方法的调用顺序,逐个读取当前数据类型长度值个数据,指针往后偏移。
所以例子中的错误可以用示意图表示为