4、System分区详解:系统镜像结构、只读文件系统特性、system分区定制策略
4.1 系统镜像结构:不只是个文件夹
System分区,说白了就是Android系统的「大脑」。所有核心系统服务、框架层代码、预装应用都塞在这里。我第一次接触AOSP时,以为它就是个普通文件夹,后来踩了坑才明白——它是个精心设计的只读文件系统镜像。
典型的system分区结构长这样:
system/
├── app/ # 预装用户应用(.apk)
├── priv-app/ # 特权应用(系统级权限)
├── framework/ # 框架jar包(services.jar, framework.jar)
├── etc/ # 配置文件(permissions, sysconfig)
├── lib/ # 原生库(.so文件)
├── lib64/ # 64位原生库
├── bin/ # 可执行文件(shell工具)
├── xbin/ # 扩展可执行文件(调试工具)
├── media/ # 开机动画、铃声
├── fonts/ # 系统字体
├── usr/ # 键盘布局、ICU数据
└── build.prop # 系统属性文件
嗯,这里要注意:app/和priv-app/的区别。priv-app里的应用拥有更高的权限等级,可以申请signatureOrSystem级别的权限。我在项目中遇到过,把普通应用放到priv-app里,结果权限检查直接报错——因为签名不对。
4.2 只读文件系统特性:为什么不能随便改?
System分区默认挂载为只读,这是有原因的。你想想看,如果系统文件能被随意修改,那恶意应用改个services.jar,整个系统就崩了。Android从设计上就把它锁死了。
具体来说,只读特性体现在:
- 挂载参数:
mount -o ro,强制只读 - 文件系统类型:早期用yaffs2,现在主流是ext4或EROFS
- dm-verity:内核级别的完整性校验,防止篡改
- AVB(Android Verified Boot):启动时校验system镜像的哈希树
核心概念:只读不是「不能写」,而是「写完后要重新签名」。定制system分区,本质上就是修改镜像文件,重新打包,再刷入设备。
我记得有一次,客户要求预装一个企业安全应用。我直接改了system/app目录,把apk塞进去,然后重新打包system.img。刷机后设备启动卡在bootloader——因为dm-verity检测到镜像哈希不匹配。从那以后,我每次改system分区都会先关掉AVB,或者重新计算哈希。
4.3 System分区定制策略:实战经验
定制system分区,说白了就是三件事:删、改、加。但每一步都有坑。
4.3.1 删除预装应用
有些厂商预装了一堆垃圾应用,你想删掉。直接删apk文件?不行。因为AndroidManifest.xml里可能声明了sharedUserId,删掉后其他应用会崩溃。
我的做法是:
- 先查
packages.xml,看应用有没有依赖关系 - 用
adb shell pm list packages -f确认路径 - 在
device.mk里用PRODUCT_PACKAGES排除掉
# 在device.mk中排除预装应用
PRODUCT_PACKAGES += \
Launcher3 \
Settings
# 排除不需要的应用
PRODUCT_PACKAGES -= \
Email \
Calendar \
Music
警告:不要直接删除system/app下的apk然后重新打包。这样做会导致OTA升级失败,因为增量包会检查文件哈希。我曾经因为这个被测试组骂了一周。
4.3.2 修改系统配置文件
比如你想改build.prop,增加一些自定义属性。直接改?可以,但要注意:
build.prop是在编译时生成的,修改后要重新打包- 有些属性是只读的(如
ro.build.version.sdk),改了也没用 - 建议在
device.mk里用PRODUCT_PROPERTY_OVERRIDES添加
# 在device.mk中添加自定义属性
PRODUCT_PROPERTY_OVERRIDES += \
ro.custom.version=1.0 \
ro.custom.feature=true
我个人习惯把自定义属性放在system/etc/extra.prop里,然后在init.rc中加载。这样方便维护,不会污染build.prop。
4.3.3 添加预装应用
添加预装应用有两种方式:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 直接放apk到system/app | 简单粗暴 | 无法卸载,占用system空间 |
| 通过PRODUCT_PACKAGES编译 | 可管理,支持卸载 | 需要源码编译 |
如果你只是临时测试,直接放apk到system/app目录,然后重新打包镜像就行。但如果是正式产品,我建议用编译方式:
# 在Android.mk中定义预装应用
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := MyApp
LOCAL_SRC_FILES := MyApp.apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_TAGS := optional
LOCAL_CERTIFICATE := PRESIGNED
include $(BUILD_PREBUILT)
小技巧:预装应用最好用PRESIGNED签名,这样不会覆盖原有签名。如果你用platform签名,那这个应用就只能在你的系统上运行了。
4.4 定制后的验证流程
改完system分区后,别急着刷机。先做这几步:
- 检查文件大小:system分区有大小限制,超了会刷不进去
- 验证签名:用
avbtool重新计算哈希 - 模拟挂载:用
mount -o loop挂载镜像,检查文件权限 - 测试启动:先刷到测试机,别直接上量产机
我曾经有一次,改完system分区后忘了重新签名,结果刷了20台设备全部变砖。嗯,从那以后我写了个自动化脚本,每次打包完自动跑一遍验证流程。
总结一下:System分区定制,核心就是理解只读文件系统的特性,然后通过编译系统或直接修改镜像的方式,安全地添加、删除、修改文件。记住,每次修改后都要重新签名和验证,否则就是给自己挖坑。