Android序列化之Parcel源码分析(2)

news2024/11/24 2:55:24

文章目录

      • 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80016.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【OpenCV学习】第15课:处理卷积边缘问题

仅自学做笔记用,后续有错误会更改 &#xff08;卷积的概念可以看看第14课&#xff09; 理论 卷积边缘问题&#xff1a;从下图最右方的结果可以看出&#xff0c;卷积操作之后&#xff0c; 剩余的绿色像素部分&#xff0c; 我们是没有处理到的 那么如何处理这个问题呢&#xf…

论文3:查找文献在指定期刊的引用格式

文章目录说明&#xff1a;1.谷歌学术搜索&#xff08;可以用一些国内的镜像&#xff09;&#xff0c;并点击被引用次数2.勾选在引用文章中搜索&#xff0c;并在搜索框搜索指定期刊的关键词3.这里指定期刊是RAL即IEEE Robotics and Automation Letters4.任意点开上图中的一篇文章…

支付宝当面付网站对接支付教程

有很多人会开支付宝当面付但是不会配置它老会出现一下情况 第二种情况如下&#xff1a; 如果遇到以上情况可以按照我的步骤就可以解决 详细步骤&#xff1a; 一、应用APPID获取方法 1.打开网站&#xff1a;https://openhome.alipay.com/platform/developerIndex.htm&#x…

Canal配置多个实例以及将Mysql指定表的binlog导入指定的Kafka的Topic

Canal配置多个实例以及将Mysql指定表的binlog导入指定的Kafka的Topic 进入Canal的conf目录 复制模板配置文件 cp -r example/ Ordercp -r example/ Orderdetail修改canal.propertieswenjain vim canal.properties修改内容如下&#xff0c;指定输出模式为kafka canal.serverM…

【元胞自动机】心房颤动/扑动模型研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

(附源码)ssm学校疫情服务平台 毕业设计 291202

ssm学校疫情服务平台 摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对学校疫情服务平台等问…

【数电实验】触发器及其应用

实验三 触发器及其应用 一 实验目的 1 了解触发器的触发方式&#xff08;上升沿触发、下降沿出发&#xff09;及其触发特点&#xff1b; 2 测试常用触发器的逻辑功能&#xff1b; 3 掌握用触发器设计同步时序逻辑电路的方法。 二 实验内容 1 测试双D触发器74HC74的逻辑功能…

手工编译konsole备忘

背景 系统自带的终端弱爆了&#xff0c;本来想编译深度终端的&#xff0c;但DTK风格的程序在非DDE桌面&#xff08;应该是dde_kwin这个窗管的问题&#xff09;巨难看&#xff0c;无意中添加了Konsole&#xff0c;发现已经有我需要使用的右键打开当前目录文件管理器的功能。 …

Go context.Context的学习

一、前言 Golang context是Golang应用开发常用的并发控制技术&#xff0c;它与WaitGroup最大的不同点是context对于派生goroutine有更强的控制力&#xff0c;它可以控制多级的goroutine。 context翻译成中文是”上下文”&#xff0c;即它可以控制一组呈树状结构的goroutine&a…

java计算机毕业设计ssm疫情期间校园车辆入校预约管理服务系统1171a(附源码、数据库)

java计算机毕业设计ssm疫情期间校园车辆入校预约管理服务系统1171a&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe…

没有二十年功力,写不出 Thread.sleep(0) 这一行“看似无用”的代码

这篇文章要从一个奇怪的注释说起&#xff0c;就是下面这张图&#xff1a; 我们可以不用管具体的代码逻辑&#xff0c;只是单单看这个 for 循环。 在循环里面&#xff0c;专门有个变量 j&#xff0c;来记录当前循环次数。 第一次循环以及往后每 1000 次循环之后&#xff0c;进…

ssm+vue基本微信小程序的校园二手商城系统 计算机毕业设计

在当今社会的高速发展过程中&#xff0c;产生的劳动力越来越大&#xff0c;提高人们的生活水平和质量&#xff0c;尤其计算机科技的进步&#xff0c;数据和信息以人兴化为本的目的&#xff0c;给人们提供优质的服务&#xff0c;其中网上购买二手商品尤其突出&#xff0c;使我们…

211大数据专业大四学生,放弃字节转正,选择老家大型国企,听听他怎么说?...

点击上方 "大数据肌肉猿"关注, 星标一起成长点击下方链接&#xff0c;进入高质量学习交流群今日更新| 1052个转型案例分享-大数据交流群分享学习群一位大数据专业同学的秋招学习和求职经历&#xff0c;他是211大四学生&#xff0c;年初才开始学习&#xff0c;但还好赶…

181.基于Django的云文件存储使用方式——七牛云存储

1.文件云存储 1.1 概述 在Django项目中&#xff0c;用户上传的文件以及项目中使用的静态文件&#xff0c;默认读书存储本地&#xff0c;保存在服务器中&#xff0c;但是&#xff0c;其实我们也可以将他们保存在云存储中&#xff0c;譬如七牛云存储、阿里云存储、亚马逊云存储…

【网络安全】提防黑客来“敲门”

前言 互联网在给我们带来便捷高效的同时&#xff0c;也给一些不法分子提供了可乘之机。网络诈骗、窃取个人信息等花样层出不穷&#xff0c;骚扰电话、垃圾短信扰乱着我们的正常生活&#xff0c;使网络空间抹上一笔灰色。网络安全与每个人都息息相关&#xff0c;所以我们必须理…

Python测试进阶(三)

文章目录性能测试JMeter测试计划模拟并发结果分析分布式性能监控grafanaFluxPrometheus小结性能测试 为什么做性能测试&#xff1f;主要是解决这些问题 什么是性能测试 模拟多个用户的操作&#xff0c;看对服务器性能的影响 指标 TPS&#xff1a;transaction per secondRT&…

基于Kubeadm快速部署一个K8s集群

目录kubeadm概述安装要求准备环境安装kubelet、kubeadm、kubectl使用kubeadm引导集群下载各个机器需要的镜像初始化主节点安装网络组件常用shell命令测试kubernetes集群部署dashboardkubeadm概述 kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具。 这个工具能通…

索引创建、删除的sql语句

目录 创建索引 使用ALTER TABLE 语句创建索引 使用CREATE TABLE 语句创建索引 删除索引 使用ALTER TABLE 语句删除索引 使用DROP INDEX 语句删除索引 创建索引 1、创建表的同时&#xff0c;指定给某个字段创建索引&#xff08;name&#xff09; create table cat(id …

SaaS 产品的文档策略

作者&#xff1a;Vaijayanti Nerkar 和 Priya Shetye&#xff0c;BMC Software 几年前&#xff0c;当 BMC Software 决定进军 SaaS 市场时&#xff0c;该公司开始投资开发基于云的产品。20多年来&#xff0c;BMC Software 产品都是典型的本地产品&#xff0c;因此&#xff0c;…

2022.12.11-YOLOv5使用NCNN将模型部署到Android端教程(1)部署自己的训练模型到Android实现静态图片检测

文章目录1. 前言2. 模型转换2.1. NCNN2.1.1. 简介2.1.2. ncnn2.1.3. ncnn-android-yolov52.2. 项目准备2.2.1. 安装Android studio2.2.2. 下载解压源码2.3. 安卓源码重新编译2.3.1. 构建工程2.3.2. 修改源码2.3.2.1. 修改CMakeLists.txt中的路径2.3.2.2. 重新重新ysnc project2…