Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

这篇具有很好参考价值的文章主要介绍了Android 动态分区详解(一) 5 张图让你搞懂动态分区原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

0. 导读

本文主要包含动态分区的物理数据布局,内存核心数据结构和动态分区映射示例 3 个部分。

  • 如果你对动态分区没啥概念,建议直接从头阅读
  • 如果只关心动态分区在设备上是如何存储的,请跳转到第 3 节
  • 如果只关心动态分区在内存中的数据结构,请跳转到第 4 节
  • 如果想看动态分区生成和映射的大致过程,请跳转到第 5 节

1. 动态分区详解的背景

1.1 背景

之前一直没有去了解动态分区,但随着最近重新研究 Android OTA 升级,发现动态分区是一个绕不过去的坎。

从 Android Q 引入动态分区,到 Android R/S 在动态分区之上增加虚拟分区管理, OTA 升级时需要对分区变更进行处理,不了解动态分区就无法深入 Android OTA 升级。

因此最近花了些时间阅读代码,学习 Android 动态分区。学习在 linux device mapper 机制之上,Android 系统对动态分区的各种操作处理。因此,这一系列没有包含 linux 底层 device mapper 驱动解读,后面看情况会考虑深入。

目前这几篇文章的大致规划如下,后续可能会有变动:

  1. Android 动态分区详解(一): 5 张图让你搞懂动态分区原理
  2. Android 动态分区详解(二): 相关工具介绍
  3. Android 动态分区详解(三): 动态分区配置和 super.img 的生成
  4. Android 动态分区详解(四): 动态分区的加载
  5. Android 动态分区详解(五): OTA 中对动态分区的处理

1.2 动态分区的本质

请原谅我总是忍不住想说一些空洞的废话。

动态分区管理的本质是什么?

分区数据是对磁盘上数据分布的描述,系统挂载磁盘时读取数据,在内存中建立相应数据结构,用于后续对磁盘进行管理。可见这里需要先提前生成分区数据,然后读取数据,一旦分区数据写入后就固定了。

动态分区,顾名思义就是分区是动态的,不是一成不变的,可以根据需要改变。动态分区管理的本质就是对分区数据的增删改查操作,操作的对象就是动态分区描述数据 metadata。

围绕 metadata 数据进行增删改查,就是 Android 动态分区管理的核心内容:

  • 增: metadata 是如何生成的?
  • 删: 没有删除 metadata 的需求,不需要删除操作
  • 改: OTA 升级时是如何修改 metadata 的?
  • 查: 系统如何读取(查询) metadata 数据并进行解析,并基于解析结果创建分区?

2. Linux device mapper 驱动

Android 动态分区最底层基于 Linux 的 device mapper 机制,将 super 分区的各个不同部分映射成 device mapper 的 linear 设备。因此,肯定有人会问,学习动态分区前需要先学习 device mapper 驱动吗?

答案是不需要,我就深入没有学习过 device mapper 驱动,但并不妨碍我对 Android 动态分区管理的理解。

注意:

我强调的是对Android 动态分区管理的理解,而不是动态分区底层机制的理解,如果你想知道动态分区最底层实现的细节,还是应该去读一读 device mapper 驱动。

Linux device mapper 相关驱动位于 drivers/md 目录下。

当然,在理解动态分区管理之前,能知道一些 device mapper 的基本原理就更好,我这里所谓的基本原理,是你能看懂下面这张图:

Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

图 1. Device mapper 内核中各对象的层次关系

说下图 1 的重点:

  • 虚拟设备 Mapped Device 基于驱动 Target Driver和内部的映射表 Mapping Table 来实现
  • 一个虚拟设备 Mapped Device 可以由一个或多个 Target Device 映射组成,参与映射的 Target Device 本身也可能是虚拟设备

所以,一个设备可能是真实的,也可能是虚拟的。对虚拟设备的访问会被 Target Driver 拦截,然后通过 Mapping Table 转发给另外一个设备。例如,在 Android Q 上,对 system_avendor_a 分区的访问被驱动拦截并转发为对 super 分区某个区域的访问。而这里的拦截以及转发对用户是透明的,用户根本不知道,也不需要知道他最终访问的到底是哪个设备。

如果不打算读 device mapper 驱动代码,但又希望在总体上对 device mapper 有比较全面的认识,推荐阅读《(转载)device mapper原理》这篇文章。

这篇文章源自 IBM 《Linux 内核中的 Device Mapper 机制》: https://www.ibm.com/developerworks/cn/linux/l-devmapper/,但该文的原始内容已经不可访问。

3. Android 动态分区布局

3.1 动态分区布局

Android 从 Q 10 开始引入动态分区 super,将原来的 system_a, system_b, vendor_a, vendor_b 等打包到到这个分区中。下面是 Android 官方的分区转换布局图:

这个图全网随处可见,好吧,我承认我绕不开这张图了~

Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

来源: https://source.android.google.cn/devices/tech/ota/dynamic_partitions/implement

图 2. 转换为动态分区时的新物理分区表布局

既然新的 super 分区内部包含了多个原来的分区,那要如何才能识别这些内部的分区呢?答案就是引入数据来描述 super 分区的布局。

于是,在 super 分区开头存储用于描述分区布局的 metadata,和磁盘开头存储用于描述磁盘分区布局的 gpt 数据的道理一样。系统加载动态分区时读取 metadata,对其进行解析,在内存中建立 super 分区布局描述的 LpMetadata 结构体,就知道内部的各个分区都位于哪个地方。

换句话说,metadata 就是 LpMetadata 结构在 super 分区上的物理存储数据,而 LpMetadatametadata 在内存中的数据结构。LpMetadata中的信息会被转换成图 1 中的映射表Mappting Table, 基于这个映射表,super 分区对应设备/dev/block/by-name/super 的不同部分被映射成多个虚拟设备,如/dev/block/mapper/system_a, /dev/block/mapper/vendor_a 等。

3.2 metadata 数据布局

在开始研究内存数据结构 LpMetadata 之前,先从宏观上看下物理存储的 metadata 是怎样的。

下图是我基于 Android 官方的布局图 (图 2. 转换为动态分区时的新物理分区表布局),对 metadata 部分细化后的的结构示意图。

Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

图 3. 安卓动态分区布局及其 metadata

对 metadata,可以分层 3 个层次,见图 2 最右侧的 3 列:

第 1 层,super 分区(宏观)

  • super 分区头部存放描述分区内部布局的 metadata 数据
  • metadata 数据之后 1MB 对齐开始的地方依次存放两组槽位(slot)的分区数据

第 2 层,metadata 数据(中观)

  • metadata 数据开始前预留了 4K 的空间,所以 metadata 数据从 4K 的偏移位置开始

  • metadata 数据由 Geometry 和 Metadata 两部分组成

    • Geometry 大小为 4K,数据除自身外,还有个一个备份,紧挨着 Geometry 存放
    • Metadata 大小 64K,按槽位(slot)存放,每个槽位有一份 Metadata 数据,所有槽位的 Metadata 数据结束后开始存放其备份数据
    • 分区加载时,根据分区对应槽位,读取相应槽位的 Metadata 和其备份数据

    为什么槽位有两个,但是这里的 Metadata 有 3 份呢?

    我一开始也有这个疑惑,经过核对传递给 lpmake 的参数后发现确实是 3 份。

    在 Android 编译生成 super.img 时,传递给 metadata 生成工具 lpmake 的参数 “--metadata-slots 3”,所以 Metadata 有 3 份。lpmake 工具只不过是一个干活的苦逼,上层的编译系统下指令说生成几份 Metadata 就生成几份。

    至于为什么要传递 “--metadata-slots 3” 而不是 “--metadata-slots 2” 给 lpmake ?那就是 builder_super_image.py 脚本的事情了,相关代码如下:

    def BuildSuperImageFromDict(info_dict, output):
      cmd = [info_dict["lpmake"],
             "--metadata-size", "65536",
             "--super-name", info_dict["super_metadata_device"]]
      ...
      if ab_update and retrofit:
        cmd += ["--metadata-slots", "2"]
      elif ab_update:
        cmd += ["--metadata-slots", "3"]
      else:
        cmd += ["--metadata-slots", "2"]
    

    只有当 retrofit 为 true 时才为 2,为什么要这么做,去问谷大爷吧~

  • 第 3 层,Geometry 和 Metadata(微观)

    • Geometry 内部是大小为 58 字节的 LpMetadataGeometry 结构数据,填充到 4K 大小
    • Metadata 内部包含 LpMetadataHeader, LpMetadataPartition, LpMetadataExtent, LpMetadataPartitionGroup, LpMetadataBlockDevice 等数据结构,填充到 64K 大小

    至于 Geometry 和 Metadata 内部每个数据结构的具体字段,请参考下一节的介绍。

3.3 metadata 数据小结

  • super 分区前 4K 为保留数据,随后是 metadata 数据,从 1M 偏移开始的地方存储内部分区的 image 数据,每个内部分区的 image 存放位置都按照 1MB 对齐,因此 metadata 位于 super 分区 4KB ~ 1MB 的范围内

  • metadata 包括 Geometry 和 Metadata 两个部分,每个部分都有自己的 Primary 和 Bakcup 两组相同的数据,存放顺序是 Geometry(Primary), Geometry(Backup), Metadata(Primary), Metadata(Backup)。

  • 对于 Metadata,其内部又有 3 份数据,分别对应于 Slot 0, Slot 1, Slot 2。当 Slot 0 启动时,读取 Slot 0 对应的 Metadata, 当 Slot 1 启动时,读取 Slot 1 对应的 Metadata,至于多出来的 Slot 2 什么时候会使用,目前没有系统研究。

    所以,通常情况下,包括 Primary 和 Backup 数据一起,磁盘上可能有 6 份 Metadata。

  • OTA 升级时,如果当前在 Slot 0 上运行,要更新 Slot 1 分区,则在更新之前 Slot 1 前会更新 Slot 1 对应的 Metadata 以反应 Slot 1 的实际布局,反之亦然。

4. Android 动态分区的核心数据结构

上一节说完 Android 动态分区描述数据在 super 分区的分布情况,这一节深入研究 metadata 具体的数据结构,不清楚的请转到 “3.3 metadata 数据小结” 回顾一下。

动态分区的核心数据结构主要定义在以下文件中:

  • system/core/fs_mgr/liblp/include/liblp/metadata_format.h
  • system/core/fs_mgr/liblp/include/liblp/liblp.h

4.1 LpMetadata 结构

启动加载动态分区前,会读取动态分区的 metadata,解析 Geometry 数据,读取当前 Slot 对应的 Metadata,在内存中建立 LpMetadata 结构。

LpMetadata: Logical Partition Metadata

LpMetadata 的定义如下:

/* file: system/core/fs_mgr/liblp/include/liblp/liblp.h */
struct LpMetadata {
    LpMetadataGeometry geometry;
    LpMetadataHeader header;
    std::vector<LpMetadataPartition> partitions;
    std::vector<LpMetadataExtent> extents;
    std::vector<LpMetadataPartitionGroup> groups;
    std::vector<LpMetadataBlockDevice> block_devices;
};

为了方便查看,我花了些时间画了个 LpMetadata 的结构示意框图:

Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

图 4. 动态分区核心数据结构 LpMetadata

LpMetadata 主要分成两个部分,描述 metadata 整体结构的 geometry 和随后描述 metadata 细节部分,包括 header, partitions, extents, groups, block_devices。

接下来详细分析每一部分的结构。

4.2 LpMetadataGeometry 结构

LpMetadataGeometry 结构中最主要的数据有 3 个:

  • metadata_max_size: 单个 Slot 对应的 Metadata 数据大小,默认是 64K
  • metadata_slot_count: 单组 Metadata 包含的 Metadata 数量,加上备份的 Metadata,所以分区一共有 metadata_slot_count * 2 个 Metdata 数据
  • logical_block_size: 逻辑分区大小,确保各分区映射的原始数据按照 logical_block_size 大小对齐

4.2 LpMetadataHeader 结构

LpMetadataHeader 结构的 major_versionminor_version 字段定义了 metadata 数据结构的版本信息,目前 Android Q 版本为 10.0, Android R 和 Android S 的版本为 10.2,新版本在 header 的结尾添加了 flags 字段,并将 header 数据填充到 256 字节,两个版本之间的差别如下:

  • 版本 v10.0, 系统 Android Q, header 大小为 128 字节
  • 版本 v10.2, 系统 Android R/S, header 大小为 256 字节
    • 新增 32 位的 flags 字段,另新增填充 124 字节

除了版本信息之外,LpMetadataHeader 最主要的还通过 LpMetadataTableDescriptor 子结构指出了随后的 partitions, extents, groups, block_devices 等数据所在的偏移位置(offset), 数量(num_entries) 以及单个数据的大小(entry_size) 等信息。

4.3 LpMetadataPartition 结构

LpMetadataPartition 结构存储了动态分区内部的分区信息,可以理解为动态分区的内部分区表。

除了 nameattributes 字段分别表示分区名字和属性外,group_index用于表示分区所属的组别。

first_extent_indexnum_extents 分别用于表示该分区第一个映射的 extents 的索引值,以及随后需要映射的 extents 数量,不过 Android super 分区内单个 image 连续存放,对应于 1 个 extent,所以相应分区的 num_extents 值为 1。

如果 system_a 的 image 在 super 分区内分两段存放,则此时针对这一单一的 image 就会有两个 extents, LpMetadataPartition 结构中的 num_extents 为 2。如果一下理解不了这两个字段到底是如何使用的,参考第 “5. Android 动态分区映射示例” 节关于具体映射示例的说明。

4.4 LpMetadataExtent 结构

LpMetadataExtent 结构中保存了 super 分区内部单个分区(如 system_a) 在 super 内的信息。如果 super 分区中只包含了 syteam_avendor_a 的 image,则会有相应的两个 extent 数据;如果分区中 system_a, system_b, vendor_a, vendor_b 的 image 都存在,则会有四个 extent 数据。

在 Android 动态分区中,单个 extent 的长度按照 512 byte 的 sector 为单位进行计算。

LpMetadataExtent 结构的成员有:

  • num_sectors: 当前 extent 对应 image 包含的 sector 数量
  • target_type: 当前 extent 对应 image 映射的虚拟设备的类型,对于 Android 动态分区,其值为 0 (类型为 linear),即线性映射。
  • target_data: 当前 extent 对应 image 的在动态分区内的偏移地址(按 sector 数量计算)
  • target_source: 当前 extent 对应的 image 位于哪个动态分区设备中, 对应于 block_devices 数组的索引,Android 系统中默认只有一个设备 super,所以 block_devices 数组只有 1 个元素, 因此 target_source 取值为 0。如果你的动态分区中不只有 super 一个设备,则这里的 target_source 可能会是其他值。

4.5 LpMetadataPartitionGroup 结构

LpMetadataPartitionGroup 结构记录了动态分区内部的 group 信息,包括 group 的名称(name),最大长度(maximum_size) 和一些标识(flags) 信息。

4.6 LpMetadataBlockDevice 结构

LpMetadataBlockDevice 结构记录了动态分区内的 device 信息,包括:

  • 动态分区内数据 image 的第一个 sector 的起始信息(first_logical_sector)
  • 动态分区内每个数据 image 的对齐信息 alignmentalignment_offset
  • 动态分区名称 partition_name 和属性 flags

对于默认的 super 动态分区,则 :

  • first_logical_sector = 2048, 即 1MB offset

  • alignment = 1024576, alignment_offset = 0,每个 image 按照 1MB 的边界进行对齐

  • size, super 分区大小,按照 byte 进行计算

  • partition_name = “super”

  • flags, super 分区的设备属性信息,参考各种 LP_BLOCK_DEVICE_* 标识

5. Android 动态分区映射示例

这里以 Broadcom 平台某个产品在 Android Q 下 super 分区的生成和加载作为示例演示动态分区是如何生成,又是如何映射为虚拟设备的。

总体而言,该流程如下面的 图 5 所示,后面会对这个流程的各个部分进行解释:

Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

图 5. 安卓动态分区映射转换示例

5.1 super.img 的编译和生成

在 Android 的编译过程中,系统通过脚本 build/tools/releasetools/build_super_image.py 内部去调用 lpmake 工具生成 super.img 文件。

所以,在编译的 log 中查找 lpmake 就直接看到系统是如何去生成 super.img 的。

$ source build/envsetup.sh
$ lunch inuvik-userdebug
$ make PRODUCT-inuvik-userdebug dist -j40 2>&1 | tee make.log
$ grep -ni "lpmake" make.log

实际上系统中会多次调用 lpmake 去生成相应的 super.img,这几个操作间有什么区别留给你去发现了~这里不再赘述,只选取其中一个生成 super.img 的命令:

$ grep -ni lpmake make.log 
979:2022-02-28 12:52:57 - common.py - INFO    :   Running: "lpmake --metadata-size 65536 --super-name super --metadata-slots 3 --device super:3028287488 --group bcm_ref_a:1509949440 --group bcm_ref_b:1509949440 --partition system_a:readonly:1077702656:bcm_ref_a --image system_a=out/target/product/inuvik/system.img --partition system_b:readonly:0:bcm_ref_b --partition vendor_a:readonly:104992768:bcm_ref_a --image vendor_a=out/target/product/inuvik/vendor.img --partition vendor_b:readonly:0:bcm_ref_b --sparse --output out/target/product/inuvik/super.img"

整理一下这里对 lpmake 的调用,如下:

lpmake --metadata-size 65536 --super-name super --metadata-slots 3 \
    --device super:3028287488 \
	--group bcm_ref_a:1509949440 --group bcm_ref_b:1509949440 \
	--partition system_a:readonly:1077702656:bcm_ref_a \
	--image system_a=out/target/product/inuvik/system.img \
	--partition system_b:readonly:0:bcm_ref_b \
	--partition vendor_a:readonly:104992768:bcm_ref_a \
	--image vendor_a=out/target/product/inuvik/vendor.img \
	--partition vendor_b:readonly:0:bcm_ref_b \
	--sparse --output out/target/product/inuvik/super.img

所以,完全可以不用管安卓下的各种动态分区配置,直接用这样的命令调用 lpmake 生成 super.img。

5.2 super.img 的解析

由于上一节生成 super.img 的命令中带有 --sparse 选项,所以生成的 super.img 为 sparse image 格式,在解析前需要使用工具 simg2img 将其转换成 raw 格式。

$ simg2img out/target/product/inuvik/super.img out/super_raw.img

拿到 super_raw.img 以后就可以使用各种工具甚至手动进行解析了,这里我直接使用 lpdump 进行解析,结果如下:

$ lpdump out/super_raw.img 
Metadata version: 10.0
Metadata size: 592 bytes
Metadata max size: 65536 bytes
Metadata slot count: 3
Partition table:
------------------------
  Name: system_a
  Group: bcm_ref_a
  Attributes: readonly
  Extents:
    0 .. 2104887 linear super 2048
------------------------
  Name: system_b
  Group: bcm_ref_b
  Attributes: readonly
  Extents:
------------------------
  Name: vendor_a
  Group: bcm_ref_a
  Attributes: readonly
  Extents:
    0 .. 205063 linear super 2107392
------------------------
  Name: vendor_b
  Group: bcm_ref_b
  Attributes: readonly
  Extents:
------------------------
Block device table:
------------------------
  Partition name: super
  First sector: 2048
  Size: 3028287488 bytes
  Flags: none
------------------------
Group table:
------------------------
  Name: default
  Maximum size: 0 bytes
  Flags: none
------------------------
  Name: bcm_ref_a
  Maximum size: 1509949440 bytes
  Flags: none
------------------------
  Name: bcm_ref_b
  Maximum size: 1509949440 bytes
  Flags: none
------------------------

对 super.img 进行简单的解析,lpdump 工具已经足够了。如果不能满足要求,也可以自己使用其它方式解析。

重点说下解析的结果:

  • 1 个 device: super
  • 3 个 group: “default”, “bcm_ref_a” 和 “bcm_ref_b”
  • 4 个 partition: “system_a”, “system_b”, “vendor_a”, “vendor_b”, 其中 “system_b” 和 “vendor_b” 在 super.img 中没有镜像文件,因为没有将这两个分区的 “image” 参数传递给 lpmake 命令
  • 2 个 extent: 分别对应于 “system_a” 和 “vendor_a” 的镜像文件,lpdump 的结果直接将 extents 结果附加到对应分区了,所以没有单独列出 extents

解析结果我已经详细列出来放到前面的 图 5 中了。

Android代码中,这部分操作通过调用库 liblp 内的函数完成,结束后在内存中生成 LpMetadata 结构数据。

5.3 super.img 的映射

在解析 super.img 生成 LpMetadata 结构以后,libdm 库内的函数会基于分区 partitions 和 条带 extents 内的信息创建映射表。

对于 system_avendor_a 分区,分别有以下映射:

/* system_a */
{
    0, 2104888,
    "/dev/block/by-id/super",
    2048
},

/* vendor_a */
{
    0, 205064,
    "/dev/block/by-id/super",
    2107392
}

换个表述方式就是:

  • 将 “/dev/block/by-id/super” 分区的 2048 开始的 2104888 个 sector 映射到 “/dev/block/mapper/system_a” 设备的 0 位置。
  • 将 “/dev/block/by-id/super” 分区的 2107392 开始的 205064 个 sector 映射到 /dev/block/mapper/vendor_a 设备的 0 位置。

然后 linux 系统的 device mapper 驱动基于每个设备的映射表生成相应的虚拟设备,这样就可以和虚拟出来的 “system_a”, “vendor_a” 分区尽情的玩耍了,而不用管这些设备到底是真实的还是虚拟的。

6. 总结

画图和组织文字前后花了几天时间,终于快写完了,到总结时还真没想好怎么用几句话把动态分区给总结出来,这里就列举一些重点吧。

6.1 关于 super 分区的布局

  • super 分区前 4K 为保留数据,随后是 metadata 数据,从 1M 偏移开始的地方存储内部分区的 image 数据,每个内部分区的 image 存放位置都按照 1MB 对齐,因此 metadata 位于 super 分区 4KB ~ 1MB 的范围内

  • metadata 包括 Geometry 和 Metadata 两个部分,每个部分都有自己的 Primary 和 Bakcup 两组相同的数据,存放顺序是 Geometry(Primary), Geometry(Backup), Metadata(Primary), Metadata(Backup)。

  • 对于 Metadata,其内部又有 3 份数据,分别对应于 Slot 0, Slot 1, Slot 2。当 Slot 0 启动时,读取 Slot 0 对应的 Metadata, 当 Slot 1 启动时,读取 Slot 1 对应的 Metadata,至于多出来的 Slot 2 什么时候会使用,目前没有系统研究。

    所以,通常情况下,包括 Primary 和 Backup 数据一起,磁盘上可能有 6 份 Metadata。

  • OTA 升级时,如果当前在 Slot 0 上运行,要更新 Slot 1 分区,则在更新之前 Slot 1 前会更新 Slot 1 对应的 Metadata 以反应 Slot 1 的实际布局,反之亦然。

不清楚的地方去看本文图 3。

6.2 关于 LpMetadata 数据结构

详细的数据结构,我也不知道说点啥好,请参考本文图 4。

6.3 动态分区生成,解析和映射流程

  • 编译阶段 build_super_image.py 内部调用 lpmake 工具生成 super.img 文件
  • 安卓启动时系统通过 liblp 库函数解析 super.img 头部的metadata,在内存中建立 LpMetadata 数据结构
  • fs_mgr 基于 LpMetadata 内的分区信息,得到映射表,然后通过 libdm 库调用 linux 的 device mapper 驱动映射设备

详细的示例分析参考本文图 5。

好了,5 张图搞懂动态分区原理就到这里了,剩下几篇将会分模块对安卓动态分区进行分析。

7. 其它

洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。

所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:

  • 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
  • 一个Android OTA的讨论组,请说明加Android OTA群。
  • 一个git和repo的讨论组,请说明加git和repo群。

在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:

Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:

Android 动态分区详解(一) 5 张图让你搞懂动态分区原理文章来源地址https://www.toymoban.com/news/detail-400946.html

到了这里,关于Android 动态分区详解(一) 5 张图让你搞懂动态分区原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一篇文章让你搞懂内存函数

    库函数memcmp介绍 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。 这个函数在遇到 ‘\\0’ 的时候并不会停下来。 如果source和destination有任何的重叠,复制的结果都是未定义的。 库函数memcmp的代码形式 看代码 memcmp将arr1中的内容拷贝到arr2中,总共

    2024年02月17日
    浏览(34)
  • 一篇文章让你搞懂自定义类型-----结构体

    结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量 例如描述一个学生 在声明结构的时候,可以不完全的声明 比如 上面的两个结构在声明的时候省略掉了结构体标签(tag) 那么问题来了 警告: 编译器会把上面的两个声明当成完全不同的两个

    2024年02月16日
    浏览(34)
  • 一篇文章让你搞懂TypeScript中的typeof()、keyof()是什么意思

    知识专栏 专栏链接 TypeScript知识专栏 https://blog.csdn.net/xsl_hr/category_12030346.html?spm=1001.2014.3001.5482 有关TypeScript的相关知识可以前往TypeScript知识专栏查看复习!! 最近在 前端的深入学习过程 中,接触了与 网络请求 相关的内容,于是计划用三个专栏( HTTP 、 Axios 、 Ajax )和零碎

    2023年04月21日
    浏览(47)
  • 一篇文章带你搞懂动态规划(由暴力递归到动态规划)

    ”动态规划“ 的过程相当于 记忆化搜索 , 即在普通 递归 的过程中用二维数组进行记忆化存储一些状态结果, 从而避免重复的计算(剪枝)。 举一个简单的例子:在 递归法 求解 斐波那契 数列的过程中, 就进行了多次的 重复计算 , 而动态规划相当于是对已经计算的状态

    2024年02月20日
    浏览(43)
  • 动态规划太难了?是你没有找对方法,四题带你搞懂动态规划!

    💯 博客内容:动态规划刷题 😀 作  者:陈大大陈 🚀 个人简介:一个正在努力学技术的准前端,专注基础和实战分享 ,欢迎私信! 💖 欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有问题请私信 😘 😘 😘 目录  一.91. 解码方法 - 力扣(LeetC

    2024年02月07日
    浏览(25)
  • Nginx配置详解,一文带你搞懂Nginx

    1 基本概念 1.1 Nginx简介 Nginx是一个高性能的HTTP和反向代理服务器,特点是占用内存少,并发能力强,事实上Nginx的并发能力确实在同类型的网页服务器中表现好。Nginx专为性能优化而开发,性能是其最重要的考量,实现上非常注重效率,能经受高负载的考验,有报告表明能支

    2024年01月16日
    浏览(34)
  • 让你搞懂怎么解决LF、CRLF问题LF will be replaced by CRLF the next time Git touched it

    大家好,我是小饼鹅,让我们一起学习吧   如果我们正在应用的windows系统进行开发工作的话,我们很有可能在对代码进行git add 的时候会看到以下warning: LF will be replaced by CRLF the next time Git touched it 很多人可能并不会特别在意,因为它貌似并没有对我们产生什么影响,可是真

    2024年02月09日
    浏览(33)
  • C/S、B/S架构详解,一文带你搞懂

      CS架构(Client-Server Architecture)是一种分布式计算模型,其中客户端和服务器之间通过网络进行通信。在这种架构中,客户端负责向服务器发送请求,并接收服务器返回的响应。服务器则负责处理客户端的请求,并返回相应的结果。CS架构通常用于构建大型的网络应用程序,

    2024年02月16日
    浏览(76)
  • 一篇文章带你搞懂spring6的概念、spring入门与容器IoC详解(尚硅谷笔记)

    Spring 是一款主流的 Java EE 轻量级开源框架 ,Spring 由“Spring 之父”Rod Johnson 提出并创立,其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring 框架

    2023年04月16日
    浏览(29)
  • 一文带你搞懂二叉树

    目录 一、什么是二叉树 二、创建二叉树 1)二叉树的结构: 2)创建二叉树: 三、二叉树的遍历方式 1)前序遍历: 2)中序遍历: 3)后序遍历: 4)还原二叉树 : 5)层序遍历:  四、二叉树的基本操作: 1)二叉树节点个数: 2)二叉树叶子节点个数: 3)二叉树第K层节点

    2024年02月08日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包