Android序列化之Parcel源码分析(2)
文章目录
- 1.Parcel.java
- 2.Parcelable和Parcel的关系
- 3.Parcel写入数据源码分析
- 3.1.java层Parcel创建
- 3.2.native层Parcel创建
- 3.3写入IBinder接口标识符
- 3.4写入String数据
- 4.Parcel读取数据源码分析
- 4.1获取IBinder接口标识符
- 4.2读取String数据
1.Parcel.java
Android可以通过Parcel进行数据序列化,IPC时进行数据传输。Parcel类似快递箱,各种数据都支持,从Parcel.java(frameworks\base\core\java\android\os\Parcel.java)可以看到读写方法都是native实现的,支持基本数据类型、数组、集合、map、对象、文件描述符、IBinder等读写。本文基于aosp12进行分析。

2.Parcelable和Parcel的关系
我们平时基本都是创建Parcelable的实现类,很少直接使用Parcel,那两者有什么关系呢?其实,Parcelable也是使用Parcel来进行数据读写。可以看下面示例:
public class MyData implements Parcelable {private String data;protected MyData(Parcel in) {data = in.readString();}// 实现Parcelable必须提供这个字段,表示通过之前写入的那个Parcel创建Parcelable实例public static final Creator<MyData> CREATOR = new Creator<MyData>() {@Overridepublic MyData createFromParcel(Parcel in) {return new MyData(in);}@Overridepublic MyData[] newArray(int size) {return new MyData[size];}};public String getData() {return data;}public void setData(String data) {this.data = data;}// 封装类型描述@Overridepublic int describeContents() {return 0;}// 将数据写入到Parcel@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(data);}
}
3.Parcel写入数据源码分析
Parcel作为数据序列化的容器,就好比快递寄件和取件,双方获取出来的数据格式是一致的。我们可以创建一个简单的aidl,编译查看生成的代码如下:
// IMyData.aidl
package com.example.parcel;interface IMyData {void writeString(String data);
}

接下来我们就分析writeString()/readWrite()过程,java层写入调用代码如下:
// 写入数据
android.os.Parcel _data = android.os.Parcel.obtain(); // 获取Parcel对象,会创建一个本地对象,并进行初始化
_data.writeInterfaceToken(DESCRIPTOR); // 写入IBinder接口标志,一般为全类名,用户数据校验
_data.writeString(data);// 写入数据
3.1.java层Parcel创建
先从缓存池中获取,获取不到就创建新的。可以看到最后调用nativeCreate()来初始化,mNativePtr用来保存Java层Parcel对应的C++层Parcel对象的地址
// frameworks/base/core/java/android/os/Parcel.java
/*** Retrieve a new Parcel object from the pool.*/
public static Parcel obtain() {Parcel res = null;...// When no cache found above, create from scratch; otherwise prepare the// cached object to be usedif (res == null) {res = new Parcel(0);} else { res.mReadWriteHelper = ReadWriteHelper.DEFAULT;}return res;
}private Parcel(long nativePtr) { init(nativePtr);
}private void init(long nativePtr) {if (nativePtr != 0) {mNativePtr = nativePtr;mOwnsNativeParcelObject = false;} else {// 传过来mNativePtr为0,执行这个分支mNativePtr = nativeCreate();mOwnsNativeParcelObject = true;}
}private static native long nativeCreate();
3.2.native层Parcel创建
接下来我们继续看native层操作,native层Parcel构造方法中初始化相关变量,具体的含义如下:
// frameworks\base\core\jni\android_os_Parcel.cpp
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{Parcel* parcel = new Parcel();return reinterpret_cast<jlong>(parcel);
}// frameworks\native\libs\binder\Parcel.cpp
Parcel::Parcel()
{initState();
}
void Parcel::initState()
{mError = NO_ERROR;mData = nullptr;mDataSize = 0;mDataCapacity = 0;mDataPos = 0;...
}
// 含义
status_t mError; // 错误码
const uint8_t* mData; // Parcel中存储的数据
size_t mDataSize; // Parcel中已经存储的数据大小
size_t mDataCapacity; // 最大存储能力
size_t mDataPos; // 数据指针
3.3写入IBinder接口标识符
// frameworks\base\core\jni\android_os_Parcel.cpp
static void android_os_Parcel_writeInterfaceToken(JNIEnv* env, jclass clazz, jlong nativePtr,jstring name)
{Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);if (parcel != nullptr) {InterfaceDescriptorString descriptor(env, name);// 获取接口标识名称,可以不用关心parcel->writeInterfaceToken(reinterpret_cast<const char16_t*>(descriptor.str()),descriptor.size());}
}// frameworks\native\libs\binder\Parcel.cpp
status_t Parcel::writeInterfaceToken(const char16_t* str, size_t len) {... // 判断为maybeKernelFields,先不用管return writeString16(str, len);
}
status_t Parcel::writeString16(const char16_t* str, size_t len)
{if (str == nullptr) return writeInt32(-1);status_t err = writeInt32(len);if (err == NO_ERROR) {len *= sizeof(char16_t);uint8_t* data = (uint8_t*)writeInplace(len+sizeof(char16_t));if (data) {memcpy(data, str, len);// env->GetStringRegion*reinterpret_cast<char16_t*>(data+len) = 0;return NO_ERROR;}err = mError;}return err;
}
从上面知道writeInterfaceToken()调用了writeString16(),下面接着看这个方法。大致有如下几个步骤:
- writeAligned:“对齐”,保证Parcel的容量可用,如果不够会扩展,同时一定mDataPos指针到新的位置
- writeInplace:计算复制数据的目标地址,写入到mData
- memcpy:分配新的数据内存
// frameworks\native\libs\binder\Parcel.cpp
// ------------------------- 步骤1:“对齐”,保证Parcel的容量可用,如果不够会扩展 -------------------------
status_t Parcel::writeInt32(int32_t val)
{return writeAligned(val);
}
template<class T>
status_t Parcel::writeAligned(T val) {static_assert(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));static_assert(std::is_trivially_copyable_v<T>);if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:memcpy(mData + mDataPos, &val, sizeof(val)); // 内存拷贝return finishWrite(sizeof(val));}// 动态增长status_t err = growData(sizeof(val));if (err == NO_ERROR) goto restart_write;return err;
}
// 写入完成后,移动DataPos到新的位置
status_t Parcel::finishWrite(size_t len)
{if (len > INT32_MAX) {return BAD_VALUE;}mDataPos += len;if (mDataPos > mDataSize) {mDataSize = mDataPos;}return NO_ERROR;
}
// ------------------------- 步骤1:计算复制数据的目标地址,写入到mData -------------------------
void* Parcel::writeInplace(size_t len)
{if (len > INT32_MAX) {return nullptr;}// 计算pad_sizeconst size_t padded = pad_size(len);if (mDataPos+padded < mDataPos) {return nullptr;}// 数据填充if ((mDataPos+padded) <= mDataCapacity) {
restart_write:uint8_t* const data = mData+mDataPos;if (padded != len) {
#if BYTE_ORDER == BIG_ENDIANstatic const uint32_t mask[4] = {0x00000000, 0xffffff00, 0xffff0000, 0xff000000};
#endif
#if BYTE_ORDER == LITTLE_ENDIANstatic const uint32_t mask[4] = {0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff};
#endif*reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len];}finishWrite(padded);return data;}// 容量不够,扩容后重新写入status_t err = growData(padded);if (err == NO_ERROR) goto restart_write;return nullptr;
}// 计算pad_size
#define PAD_SIZE_UNSAFE(s) (((s) + 3) & ~3UL)
static size_t pad_size(size_t s) {return PAD_SIZE_UNSAFE(s);
}
3.4写入String数据
可以知道也是和上面相似的步骤,先“对齐”,然后写入数据,分配内存。
// frameworks\base\core\jni\android_os_Parcel.cpp
static void android_os_Parcel_writeString16(JNIEnv *env, jclass clazz, jlong nativePtr,jstring val) {Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);if (parcel != nullptr) {status_t err = NO_ERROR;if (val) {const size_t len = env->GetStringLength(val);const size_t allocLen = len * sizeof(char16_t);err = parcel->writeInt32(len);// “对齐”char *data = reinterpret_cast<char*>(parcel->writeInplace(allocLen + sizeof(char16_t))); // 写入数据if (data != nullptr) {// 分配内存env->GetStringRegion(val, 0, len, reinterpret_cast<jchar*>(data));*reinterpret_cast<char16_t*>(data + allocLen) = 0;} else {err = NO_MEMORY;}} else {err = parcel->writeString16(nullptr, 0);}// 异常情况if (err != NO_ERROR) {signalExceptionForError(env, clazz, err);}}
}
4.Parcel读取数据源码分析
上面aidl生成的代码中通过从Parcel中读取数据,代码如下:
data.enforceInterface(descriptor); // 获取IBinder接口标志
java.lang.String _arg0 = data.readString(); // 读取数据
4.1获取IBinder接口标识符
// frameworks\base\core\jni\android_os_Parcel.cpp
static void android_os_Parcel_enforceInterface(JNIEnv* env, jclass clazz, jlong nativePtr, jstring name)
{Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);if (parcel != nullptr) {InterfaceDescriptorString descriptor(env, name);IPCThreadState* threadState = IPCThreadState::self();const int32_t oldPolicy = threadState->getStrictModePolicy();// 判断是否有效const bool isValid =parcel->enforceInterface(reinterpret_cast<const char16_t*>(descriptor.str()),descriptor.size(), threadState);if (isValid) {const int32_t newPolicy = threadState->getStrictModePolicy();if (oldPolicy != newPolicy) {set_dalvik_blockguard_policy(env, newPolicy);}return;}}// 异常情况jniThrowException(env, "java/lang/SecurityException","Binder invocation to an incorrect interface");
}
与写入类似的,enforceInterface的读取步骤也与写入类似,最后调用了readString16Inplace()。大致步骤包括读取Parcel容器长度、具体数据
// frameworks\native\libs\binder\Parcel.cpp
bool Parcel::enforceInterface(const char16_t* interface,size_t len,IPCThreadState* threadState) const
{// 读取接口标识符size_t parcel_interface_len;const char16_t* parcel_interface = readString16Inplace(&parcel_interface_len);// 比较获取的与传递过来的是否相同if (len == parcel_interface_len &&(!len || !memcmp(parcel_interface, interface, len * sizeof (char16_t)))) {return true;} else {return false;}
}const char16_t* Parcel::readString16Inplace(size_t* outLen) const
{int32_t size = readInt32();if (size >= 0 && size < INT32_MAX) {*outLen = size;const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t));if (str != nullptr) {if (str[size] == u'\0') { // 结束位return str;}}}*outLen = 0;return nullptr;
}
详细步骤如下:
// frameworks\native\libs\binder\Parcel.cpp// ------------------------- 步骤1:“对齐”,先读取长度 -------------------------
int32_t Parcel::readInt32() const
{return readAligned<int32_t>();
}template<class T>
status_t Parcel::readAligned(T *pArg) const {static_assert(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));static_assert(std::is_trivially_copyable_v<T>);if ((mDataPos+sizeof(T)) <= mDataSize) {// 判断maybeKernelFields,先忽略memcpy(pArg, mData + mDataPos, sizeof(T)); // 读取数据mDataPos += sizeof(T); // 移动mDataPosreturn NO_ERROR;} else {return NOT_ENOUGH_DATA;}
}
// ------------------------- 步骤1:读取数据 -------------------------
const void* Parcel::readInplace(size_t len) const
{if (len > INT32_MAX) {return nullptr;}if ((mDataPos+pad_size(len)) >= mDataPos && (mDataPos+pad_size(len)) <= mDataSize&& len <= pad_size(len)) { // 判断需要读取数据是否在Parcel中// 判断maybeKernelFields,先忽略 const void* data = mData+mDataPos;mDataPos += pad_size(len); // 移动mDataPosreturn data;}return nullptr;
}
4.2读取String数据
从下面代码中知道可上面获取IBinder接口标识符的步骤一样
// frameworks\base\core\jni\android_os_Parcel.cpp
static jstring android_os_Parcel_readString16(JNIEnv* env, jclass clazz, jlong nativePtr)
{Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);if (parcel != NULL) {size_t len;const char16_t* str = parcel->readString16Inplace(&len);if (str) {return env->NewString(reinterpret_cast<const jchar*>(str), len);}return NULL;}return NULL;
}// frameworks\native\libs\binder\Parcel.cpp
const char16_t* Parcel::readString16Inplace(size_t* outLen) const
{int32_t size = readInt32();if (size >= 0 && size < INT32_MAX) {*outLen = size;const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t));if (str != nullptr) {if (str[size] == u'\0') {return str;}}}*outLen = 0;return nullptr;
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
