综合实战:从零开始定制一个优化启动且安全的Android系统

好了,终于到了这个章节。前面我们聊了那么多启动优化的技巧,也深入分析了SELinux的策略写法,现在是时候把它们串起来了。

这一章,我会带你走一遍完整的流程——从拿到AOSP源码开始,到最终烧录出一个既启动快、又安全的Android系统。嗯,说白了就是“既要马儿跑,又要马儿不吃草”的实战版本。

1. 整体思路:先优化,再加固

我个人习惯把这件事分成两大阶段:

  • 第一阶段:启动优化——减少不必要的服务、精简init脚本、优化Zygote预加载。
  • 第二阶段:SELinux策略定制——收紧权限、修补漏洞、确保优化后的系统依然安全。

你想想看,如果先做安全策略,结果发现某个服务被删了导致策略冲突,回头还得改,多折腾。所以我的建议是:先瘦身,再穿盔甲

核心原则:每一次修改都要可追溯。我在项目中吃过亏——改完代码忘了记录,结果出问题只能从头排查。所以,请务必用Git做好版本管理。

2. 启动优化实战:从init.rc开刀

拿到AOSP源码后,第一步不是急着改代码,而是先分析现有的启动流程。我通常会先看这几个文件:

  • system/core/rootdir/init.rc
  • system/core/rootdir/init.${ro.hardware}.rc
  • system/core/rootdir/init.zygote*.rc

为什么要看它们?因为Android的启动顺序全写在这里面。说白了,init进程就是按照这些脚本一条一条执行的。

2.1 禁用不必要的服务

我记得有一次给一个低端设备做优化,发现系统里居然启动了adbdlogddumpsys这些调试服务。对于量产设备来说,这些完全没用。

做法很简单:在init.rc里找到对应的service定义,直接注释掉或者加上disabled标志。

# 原始定义
service adbd /system/bin/adbd
    class main
    disabled

# 优化后:直接注释掉
# service adbd /system/bin/adbd
#     class main
#     disabled

小技巧:不要直接删除,先注释。万一后面需要回退,省得重新写。

2.2 精简Zygote预加载类

Zygote启动时会预加载大量Java类和资源。这部分是启动时间的大头。我曾经在一个项目里发现,预加载列表里居然有几十个第三方应用的类——这明显是之前开发阶段留下的。

修改文件:frameworks/base/config/preloaded-classes

把不需要的类删掉,只保留系统核心类。注意:不要删掉android.os.*、java.lang.*这些基础类,否则系统直接崩给你看。

# 保留的核心类
android.os.Binder
android.os.Handler
android.os.Looper
java.lang.String
java.lang.Object
# ... 其他核心类

# 可以删除的示例(如果确定不用)
# com.thirdparty.sdk.SomeClass

2.3 优化服务启动顺序

有些服务之间没有依赖关系,完全可以并行启动。我在init.rc里调整过class mainclass late_start的划分,把一些非关键服务推迟到后期启动。

# 将非关键服务移到 late_start
service noncritical_service /system/bin/noncritical
    class late_start
    user system
    group system

3. SELinux策略定制:收紧权限

启动优化做完后,系统确实变快了。但安全呢?别忘了,我们删掉了一些服务,调整了启动顺序,这些改动可能会破坏原有的SELinux策略。

嗯,这里要注意:每次修改init.rc或添加/删除服务,都必须同步更新SELinux策略。否则系统启动时会出现avc: denied错误,严重时直接起不来。

3.1 分析现有的avc日志

我习惯先跑一遍优化后的系统,然后抓取avc日志:

adb shell dmesg | grep avc
adb logcat -b events | grep avc

把这些日志保存下来,一条一条分析。比如:

avc: denied { read } for pid=1234 comm="my_service" name="config.conf" dev="mmcblk0p1" ino=5678 scontext=u:r:my_service:s0 tcontext=u:object_r:system_file:s0 tclass=file

这条日志告诉我们:my_service进程想读取system_file类型的文件,但没有权限。解决方案就是给my_service添加对应的allow规则。

3.2 编写自定义te文件

device/你的厂商/你的设备/sepolicy/目录下,创建或修改my_service.te文件:

# 允许my_service读取system_file
allow my_service system_file:file read;

# 如果还需要写,加上write
allow my_service system_file:file write;

警告:不要滥用allow * *:* *;这种通配规则。我曾经见过有人为了省事直接开放所有权限,结果系统被植入恶意应用后完全失控。SELinux的精髓就是最小权限原则。

3.3 处理新服务的域转换

如果你在优化过程中新增了自定义服务,还需要定义它的域(domain)。比如:

# 在 new_service.te 中
type new_service, domain;
type new_service_exec, exec_type, file_type;

# 域转换规则
init_daemon_domain(new_service)

# 允许它访问必要的资源
allow new_service system_file:file read;
allow new_service self:capability { net_admin };

4. 验证与测试

改完策略后,重新编译并烧录系统。然后做两件事:

  1. 启动时间测试:用bootchartadb shell dumpsys bootstats查看启动耗时。
  2. SELinux模式测试:先设为permissive模式跑一遍,收集所有avc日志,确认没有遗漏的规则。然后切回enforcing模式。
# 临时切换为permissive模式
adb shell setenforce 0

# 收集日志
adb shell dmesg | grep avc > avc_log.txt

# 切回enforcing
adb shell setenforce 1

我的经验:在permissive模式下跑至少24小时,覆盖各种使用场景。否则切回enforcing后,用户一操作就触发denied,体验极差。

5. 整体流程SVG图

下面这张图概括了整个流程,你可以把它当作检查清单:

阶段一:启动优化 1. 分析 init.rc 脚本 2. 禁用/延迟非关键服务 3. 精简 Zygote 预加载 4. 调整服务启动顺序 阶段二:SELinux策略定制 1. 抓取 avc 日志 2. 编写/修改 te 文件 3. 定义新服务的域 4. 最小权限原则收紧 验证:permissive 模式测试 → enforcing 模式 发现问题? 返回修改

6. 避坑指南

最后,分享几个我踩过的坑:

  • 不要一次性改太多:每次只改一个服务或一条策略,编译测试通过后再改下一个。否则出了问题根本不知道是哪一步引起的。
  • 保留原始策略备份:我在一个项目里把system_app.te改坏了,导致所有系统应用都无法启动。幸好有备份,否则得重新下载整个AOSP。
  • 注意策略的叠加效果:SELinux策略是累加的,多个te文件里的规则会合并。我曾经因为两个文件里定义了冲突的规则,导致编译报错,排查了半天。

我的习惯:每次修改后,在BoardConfig.mk里加上BOARD_USE_ENFORCING_SELINUX := true,这样编译出来的系统默认就是enforcing模式,避免开发阶段忘记切换。

好了,这一章的内容就是这些。从init.rc的优化到SELinux策略的定制,整个流程走下来,你手里的系统应该既轻快又安全了。记住:优化和安全不是对立面,而是可以兼得的——只要你愿意花时间去分析和调整。


公众号:蓝海资料掘金营,微信deep3321