文章目录
- 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>() {
@Override
public MyData createFromParcel(Parcel in) {
return new MyData(in);
}
@Override
public MyData[] newArray(int size) {
return new MyData[size];
}
};
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
// 封装类型描述
@Override
public int describeContents() {
return 0;
}
// 将数据写入到Parcel
@Override
public 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);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AGVq6eiS-1670742335762)(assets/android/system/IMyData.png)]
接下来我们就分析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
用来保存nativei层结果
// 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 used
if (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_size
const 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_ENDIAN
static const uint32_t mask[4] = {
0x00000000, 0xffffff00, 0xffff0000, 0xff000000
};
#endif
#if BYTE_ORDER == LITTLE_ENDIAN
static 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); // 移动mDataPos
return 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); // 移动mDataPos
return 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;
}