16. 存储管理:A/B分区、SDK沙箱、ASEC容器、App数据目录的创建与迁移
存储管理这块,说实话,是Android系统里最容易出幺蛾子的地方。你想想看,用户手机里几百个App,每个都有自己的数据目录,系统还得保证升级不丢数据、多用户不串数据、外置SD卡兼容老应用……这背后是一整套精密的存储架构在支撑。
我个人习惯把Android的存储管理拆成四个维度来看:分区策略(A/B)、隔离机制(SDK沙箱)、容器技术(ASEC)、数据生命周期(创建与迁移)。咱们一个一个聊。
16.1 A/B分区:无缝升级的基石
A/B分区,也叫无缝更新。说白了就是系统准备了两套分区(slot A 和 slot B),你在用A的时候,系统悄悄把更新写到B。下次重启,直接切到B启动。用户几乎感觉不到升级过程。
我记得第一次接触这个设计时,觉得Google真是被OTA升级搞怕了——早期Android升级一旦断电变砖,用户只能返厂。A/B分区本质上是用空间换可靠性。
- 系统分区、vendor分区、boot分区等都有A/B两份
- 当前活跃的slot标记为active,另一个为unused
- 更新时写入unused slot,完成后切换active标记
- 如果新slot启动失败,自动回滚到旧slot
在代码层面,bootctrl HAL 负责管理slot状态。你可以通过 getprop ro.boot.slot_suffix 查看当前用的是哪个slot。
// 查看当前slot
adb shell getprop ro.boot.slot_suffix
// 输出可能是 _a 或 _b
// 查看slot是否支持A/B
adb shell getprop ro.build.ab_update
// true 表示支持
16.2 SDK沙箱:应用数据的隔离墙
从Android 10开始,Google强制推行分区存储(Scoped Storage)。说白了,每个App只能看到自己的专属目录和用户明确授权的公共目录。这背后的核心机制就是SDK沙箱。
你想想看,以前App可以随便读 /sdcard 下的任何文件,隐私泄露风险极大。SDK沙箱通过文件系统层面的挂载命名空间(mount namespace)来实现隔离。
每个应用进程启动时,Zygote会为它创建一个独立的mount namespace。在这个namespace里,/sdcard 被重新挂载,只暴露该应用自己的数据目录和公共媒体目录。
// 每个App的私有数据目录
/data/data/<package_name>/
// 外部存储上的私有目录
/storage/emulated/0/Android/data/<package_name>/
// 公共媒体目录(需用户授权)
/storage/emulated/0/Pictures/
/storage/emulated/0/Music/
我个人建议,尽早适配分区存储。别等到用户反馈「App打不开文件」才去改。我在项目中就见过一个老牌文件管理器,因为没适配分区存储,在Android 11上直接崩溃——用户骂声一片。
16.3 ASEC容器:加密与迁移的利器
ASEC(Android Secure Container)是个老东西了,但至今仍在某些场景下发挥作用。它本质上是一个加密的容器文件,里面存放App的APK和私有数据。
ASEC最早是为了支持「App2SD」功能——把App安装到外置SD卡上。但外置SD卡可移除、可被其他设备读取,所以必须加密。ASEC容器使用dm-crypt进行块设备级别的加密。
| 属性 | 说明 |
|---|---|
| 文件格式 | .asec 文件,本质是加密的loop设备 |
| 存储位置 | /mnt/asec/ 下挂载 |
| 加密方式 | AES-128 CBC,密钥由设备派生 |
| 主要用途 | App2SD、企业设备管理 |
嗯,这里要注意:ASEC在Android 6.0之后基本被废弃了。因为可适配存储(Adoptable Storage)的出现,系统可以直接把外置SD卡格式化为内部存储的一部分,用F2FS或ext4加密,比ASEC灵活得多。
但我在一些定制ROM和车载系统中,仍然看到ASEC的身影。为什么?因为ASEC容器可以独立迁移——你只需要把.asec文件拷到另一台设备上,挂载就能用。这在某些离线场景下非常实用。
16.4 App数据目录的创建与迁移
App安装时,PackageManager负责创建数据目录。这个过程比你想象的要复杂得多。
核心流程在 PackageManagerService.installPackage() 中触发,最终调用 InstallArgs.createDataDirs()。我简化一下关键步骤:
- 确定存储位置:内部存储还是外部存储?根据manifest和用户设置决定。
- 创建目录结构:
/data/data/<pkg>、/data/user/0/<pkg>(多用户)、/storage/emulated/0/Android/data/<pkg>。 - 设置权限:目录owner为App的UID,权限为
rwx------(700)。 - 初始化SELinux上下文:每个目录打上对应的安全标签。
- 创建CE/DE加密目录:Android 7.0+引入文件级加密,每个用户有凭据加密(CE)和设备加密(DE)两套目录。
// 典型的App数据目录结构(Android 10+)
/data/data/<pkg>/ ← DE目录(设备启动即可访问)
/data/user/0/<pkg>/ ← CE目录(用户解锁后可用)
/data/user_de/0/<pkg>/ ← DE目录(多用户版本)
// 外部存储
/storage/emulated/0/Android/data/<pkg>/
/storage/emulated/0/Android/obb/<pkg>/
我曾经遇到过一个问题:某个App在迁移过程中被杀死了,导致数据目录只拷贝了一半。重启后App直接崩溃,因为数据库文件不完整。后来Google在Android 8.0中加入了迁移锁(migration lock),在迁移期间阻止App启动。但如果你自己实现数据迁移逻辑,一定要做原子性保证——要么全拷贝成功,要么回滚。
16.5 知识体系总览
说了这么多,咱们用一张图来总结本章的核心逻辑:
这张图把四个核心模块串起来了。A/B分区解决系统级存储的可靠性,SDK沙箱解决应用级数据的隔离性,ASEC容器解决历史兼容问题,数据目录创建与迁移则是贯穿所有场景的基础操作。
最后说一句:存储管理没有银弹。每个方案都有取舍——A/B分区占用了双倍空间,SDK沙箱增加了开发复杂度,ASEC容器性能不如原生文件系统。作为工程师,我们的职责就是在这些约束中找到最适合当前场景的平衡点。
公众号:蓝海资料掘金营,微信deep3321