Android系统级脚本入门:测试开机启动脚本详细教程
在Android开发和系统定制过程中,经常需要让某些服务或逻辑在设备启动完成时自动运行。比如预装应用的初始化、硬件状态检测、日志收集、网络配置等。这类需求最直接可靠的实现方式,就是编写一个开机启动脚本,并将其集成进系统初始化流程中。
但很多刚接触Android底层的同学会发现:写个shell脚本不难,可一放到init.rc里就报错、卡住、甚至无法开机——问题往往不出在脚本本身,而在于Android 8.0+引入的严格Selinux策略、init语法变更、执行上下文限制等系统级约束。
本文不是泛泛而谈“怎么写shell”,而是聚焦一个真实可用的最小闭环:从零开始,手把手带你完成一个经过实测验证的开机启动脚本,覆盖环境准备、脚本编写、Selinux适配、init集成、调试验证全流程。所有步骤均基于Android 8.0及以上版本(含主流MTK/高通平台),无需root、不依赖ADB shell持久化,真正“烧录即生效”。
你不需要熟悉Selinux规则细节,也不用翻遍AOSP源码;只需要按顺序操作,就能看到getprop test.prop返回你设定的值——这是开机脚本成功执行的最直观证据。
1. 明确目标与前置条件
1.1 你要学会什么
- 编写一个能在Android系统启动早期阶段稳定执行的shell脚本
- 理解Android init机制中
service声明的关键字段含义(oneshot、user、seclabel等) - 掌握Selinux权限适配的最小必要操作(te文件 + file_contexts)
- 学会用
getprop和logcat快速验证脚本是否真正触发 - 避开常见坑点:路径错误、shell解释器不匹配、Selinux拒绝、init语法不兼容
1.2 你需要准备什么
- 一台已解锁Bootloader的Android设备(推荐使用开发板或工程机,避免量产机锁区)
- AOSP或芯片厂商提供的完整Android源码(至少包含
system/core/init、external/sepolicy、device/xxx/sepolicy目录) - 编译环境(Linux主机,安装repo、JDK、Python等基础工具)
- 基础Linux命令能力(
adb、push、reboot、getprop、logcat)
注意:本文不涉及ADB临时启动或
su提权方案。我们做的是真正的系统级开机脚本,它随系统镜像固化,重启后自动生效,是OEM/ODM厂商的标准做法。
2. 编写可执行的开机脚本
2.1 脚本内容与关键细节
新建一个文件,命名为init.test.sh,内容如下:
#!/system/bin/sh # 开机脚本必须以正确的shebang开头 # Android系统默认shell路径为 /system/bin/sh(非 /bin/sh) # 错误路径会导致init直接跳过执行,且无明显报错 # 设置一个系统属性作为执行标记(最轻量、最安全的验证方式) setprop test.prop "init_test_ran_at_$(date +%s)" # 可选:记录日志到kernel log(便于logcat抓取) log -t INIT_TEST "Script executed successfully. Prop set to $(getprop test.prop)" # 可选:创建一个临时标记文件(仅用于调试,生产环境建议避免写文件) # touch /data/local/tmp/init_test_done2.2 为什么这样写?小白也能懂的原理
#!/system/bin/sh是硬性要求:Android的init进程只认这个路径下的解释器。写成/bin/sh或/system/xbin/sh在部分设备上会失败。setprop是首选验证手段:它不依赖文件系统权限、不产生磁盘IO、不会因/data未挂载而失败,且可通过adb shell getprop test.prop秒级确认。log -t比echo更可靠:echo输出到stdout在init上下文中不可见,而log命令会写入kernel log buffer,logcat -b events -t INIT_TEST即可捕获。- 注释掉
touch行是经验之谈:早期Android中/data可能尚未挂载,写文件极易导致init卡死或崩溃。
2.3 手动验证脚本是否可运行
在push进设备前,请先本地测试语法:
# 在Linux主机上模拟执行(需有android-tools或busybox) sh -n init.test.sh # 检查语法错误然后推送到设备手动运行:
adb root adb remount adb push init.test.sh /system/bin/ adb shell chmod 755 /system/bin/init.test.sh adb shell /system/bin/init.test.sh adb shell getprop test.prop # 应输出类似 "init_test_ran_at_1715678901"只有这一步成功,才能进入下一步。如果失败,请回头检查shebang路径、换行符(必须是LF)、是否有多余空格。
3. 为脚本配置Selinux权限
3.1 为什么必须加Selinux?一句话说清
Android 8.0起,默认启用enforcing模式的Selinux。init进程以u:r:init:s0身份运行,它不能随意执行任意文件。你的脚本若没有对应file_type和domain定义,init会静默拒绝执行——连log都不会打。
这不是bug,是设计。所以必须显式声明:“这个脚本允许被init调用”。
3.2 创建te策略文件(test_service.te)
在源码目录下新建文件:device/your_company/your_platform/sepolicy/non_plat/test_service.te
内容如下:
# 定义一个新的域(domain),代表这个服务的执行上下文 type test_service, domain; # 定义该服务可执行的文件类型 type test_service_exec, exec_type, vendor_file_type, file_type; # 允许init域(init_daemon_domain)切换到test_service域 init_daemon_domain(test_service); # 允许test_service域读取/执行自己的文件 allow test_service test_service_exec:file { read open getattr execute }; # 允许test_service向property_service写属性(关键!否则setprop失败) allow test_service property_service:property_service { set };说明:
init_daemon_domain(test_service)是核心宏,它自动赋予test_service域访问/dev/*、/proc/*、property_service等init必需资源的权限,比手动写几十行allow更安全简洁。
3.3 关联文件路径与类型(file_contexts)
编辑device/your_company/your_platform/sepolicy/non_plat/file_contexts,在末尾添加一行:
/system/bin/init\.test\.sh u:object_r:test_service_exec:s0注意:
- 路径必须用正则转义(
\.),否则匹配失败 - 路径要与你实际存放位置完全一致(本文用
/system/bin/,若放/vendor/bin/则需改路径) s0是MLS级别,保持默认即可
3.4 验证Selinux配置是否生效
编译后刷机,执行:
adb shell ls -Z /system/bin/init.test.sh # 正确输出应包含:u:object_r:test_service_exec:s0如果显示u:object_r:shell_exec:s0或报错,说明file_contexts未生效或路径不匹配。
4. 将脚本集成进init启动流程
4.1 不要直接修改init.rc!正确做法是新建init.XXX.rc
主流芯片平台(MTK/高通/展锐)都预留了客户自定义rc文件入口。例如:
- MTK平台:
device/mediatek/common/init/init.mtk.rc或device/mediatek/sepolicy/basic/non_plat/init.mtk.rc - 高通平台:
device/qcom/common/rootdir/etc/init.qcom.rc
找到对应位置,在其中添加:
# service <name> <pathname [argument] ...> # class <name> # user <username> # group <groupname> [groupname] ... # oneshot # seclabel u:object_r:test_service_exec:s0 service test_service /system/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s04.2 各字段作用通俗解读
| 字段 | 作用 | 为什么这么设 |
|---|---|---|
class main | 归入main类,确保在on early-init之后、on init期间启动 | main类是绝大多数服务的默认归属,时机稳妥 |
user root&group root | 以root身份运行 | 开机脚本常需访问硬件节点、设置系统属性,非root不可 |
oneshot | 执行完即退出,不常驻 | 我们的脚本是单次任务(设prop),无需后台守护 |
seclabel | 强制指定执行域,与te文件中的test_service_exec对应 | Selinux强制要求,缺一不可 |
提示:不要加
start test_service。init.rc中service声明本身不触发执行,需由on触发器(如on boot)启动。oneshot服务通常由on property:或on boot隐式触发。
4.3 确保触发时机(可选增强)
如果你希望脚本在某个明确事件后执行(比如sys.boot_completed=1),可在同一rc文件中添加:
on property:sys.boot_completed=1 start test_service但对简单设prop场景,class main已足够——init会在on init阶段自动启动所有main类oneshot服务。
5. 编译、刷机与最终验证
5.1 编译命令(以全编译为例)
# 进入源码根目录 source build/envsetup.sh lunch your_target-userdebug m -j16 # 编译整个系统(含sepolicy、init、system分区)Sepolicy编译会自动合并
non_plat目录下所有.te和.fc文件,无需额外操作。
5.2 刷机与验证步骤
- 将生成的
system.img刷入设备(使用fastboot或烧录工具) - 重启设备:
adb reboot - 等待开机完成(约1~2分钟),执行验证:
# 检查属性是否已设置(最直接证据) adb shell getprop test.prop # 正常应输出:init_test_ran_at_1715678901 # 查看init日志(确认服务是否启动) adb logcat -b events | grep -i "test_service" # 查看kernel log中的自定义日志 adb logcat -b kernel | grep -i "INIT_TEST"5.3 常见问题速查表
| 现象 | 可能原因 | 快速排查方法 |
|---|---|---|
getprop test.prop无输出 | 脚本根本没执行 | `adb logcat -b events |
logcat中出现avc: denied | Selinux权限缺失 | `adb logcat -b events |
| 设备卡在开机动画 | init语法错误或脚本崩溃 | 串口连接,看kernel panic或init segfault信息 |
ls -Z显示类型错误 | file_contexts未生效或路径不匹配 | 检查路径是否带转义、是否在正确non_plat目录、是否clean编译 |
6. 进阶建议与安全提醒
6.1 生产环境优化建议
- 避免硬编码路径:用
/system/bin/sh而非/system/xbin/sh,前者在所有Android版本中稳定存在。 - 增加错误处理:在脚本中加入
if [ $? -ne 0 ]; then log -t INIT_TEST "Failed"; fi。 - 日志分级:用
log -p i(info)、log -p e(error)替代统一-t,便于过滤。 - 精简执行逻辑:开机脚本应尽量短小(<10行),耗时操作建议fork到后台或通过
property触发后续服务。
6.2 绝对不要做的三件事
- ❌ 不要在脚本中调用
reboot、poweroff等危险命令 - ❌ 不要尝试挂载/格式化分区(init上下文无此权限,且极不安全)
- ❌ 不要写入
/system分区(只读挂载,写入失败且可能触发verity校验失败)
6.3 为什么这个方案比“ADB开机启动”更可靠?
| 方案 | 是否随系统固化 | 是否依赖ADB连接 | 是否受USB调试开关影响 | 是否能早于UI启动 |
|---|---|---|---|---|
| 本文方案(init.rc + Selinux) | 是 | ❌ 否 | ❌ 否 | 是(early-init阶段) |
ADB +adb shell | ❌ 否 | 是 | 是 | ❌ 否(需ADB服务已启动) |
pm enable+BroadcastReceiver | ❌ 否 | ❌ 否 | ❌ 否 | ❌ 否(需Launcher进程启动) |
真正的系统级自动化,始于init,止于内核。掌握它,你就拿到了Android底层的第一把钥匙。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。