新手必看!Android开机启动脚本避坑指南与实测记录
你是不是也遇到过这样的问题:写好了开机启动脚本,push进设备手动执行一切正常,可一重启就完全没反应?adb shell里查不到进程,logcat里翻遍也找不到日志,selinux报错信息还藏在dmesg深处……别急,这不是你代码写错了,而是Android系统启动机制和权限管控比Linux桌面环境严格得多。
本文不是照搬官方文档的复读机,也不是泛泛而谈的理论堆砌。它来自我在MTK平台真实项目中反复调试、踩坑、验证的完整实录——从第一行#!/system/bin/sh的写法,到file_contexts里一个空格引发的启动失败;从init.rc中oneshot与disabled的区别,到dmesg | grep avc里那条被忽略的关键拒绝日志。所有步骤都经过Android 8.0+真机实测,不讲虚的,只说能跑通的。
如果你正卡在“脚本写好了但系统不认”这一步,这篇文章就是为你写的。
1. 为什么手动能跑,开机却静默失败?
很多新手的第一反应是:“我脚本都没问题,手动执行秒出结果”。但Android开机启动远不止“执行一个sh文件”这么简单。它是一套由init进程驱动、受SELinux策略约束、依赖服务声明和上下文标记的完整链路。任何一个环节出错,脚本都会被无声拦截。
我们先理清这个链路上最关键的四个节点:
- 脚本本身:路径、shebang、权限、内容逻辑
- SELinux类型定义(.te):告诉系统“这是个什么角色的服务”
- 文件上下文标记(file_contexts):告诉系统“这个文件该用什么类型运行”
- init服务声明(.rc):告诉init进程“什么时候、以什么身份、怎么启动它”
这四者必须严丝合缝,缺一不可。而其中最容易被忽略、最常出错的,恰恰是后三项。
关键认知:Android的init不是Linux的systemd。它不自动扫描目录,不解析shebang以外的元信息,更不会绕过SELinux检查。你写的每一行配置,都必须精准匹配内核加载的策略。
2. 脚本编写:从第一行开始就别踩雷
2.1 shebang必须写对,且只能写对
Android的shell解释器路径和Linux发行版不同。常见错误写法:
#!/bin/sh # ❌ 错误!Android没有/bin/sh #!/usr/bin/sh # ❌ 错误!路径不存在 #!/system/xbin/sh # 部分旧版本可用,但不推荐正确写法(Android 8.0+通用):
#!/system/bin/sh为什么?因为Android的/system/bin/sh是mksh(MirBSD Korn Shell)的软链接,专为init环境优化。而/bin/sh在Android根文件系统中根本不存在——即使你用busybox挂载了,init也不会走那条路径。
2.2 脚本内容要“轻量、可验证、无副作用”
新手常犯的第二个错误:在脚本里直接创建文件、修改系统属性、调用复杂命令。这极易因权限、路径或依赖未就绪而失败,且失败时无任何提示。
推荐写法(仅做最小验证):
#!/system/bin/sh # 第一步:设置一个测试属性(最安全的验证方式) setprop sys.boot.test_script 1 # 第二步:写入一行日志到/dev/kmsg(无需权限,init进程可写) echo "[INIT] test_script executed at $(date)" > /dev/kmsg # 第三步:可选——创建一个临时标记文件(仅用于调试,非生产) touch /data/local/tmp/test_script_ran注意事项:
setprop是最稳妥的验证手段,属性值可通过getprop sys.boot.test_script立即检查/dev/kmsg是内核日志缓冲区,echo写入后可用dmesg | tail -5查看,比logcat更早、更底层/data/local/tmp/是唯一对root用户开放写权限的公共目录,避免使用/system或/vendor下的路径
2.3 权限必须设为可执行,且仅root可写
chmod 755 /system/bin/init.test.sh755:owner=rwx, group=rx, other=rx —— init以root身份运行,不需要other可执行- 绝对不要用
chmod 777:SELinux会拒绝执行world-writable脚本 - 脚本文件必须放在
/system/bin/或/vendor/bin/下(需对应file_contexts配置),不能放/data/或/sdcard/
3. SELinux策略:te文件与file_contexts的黄金配对
Android 8.0引入了sepolicy v25+,强制要求所有自定义服务必须有明确的SELinux类型。跳过这步?脚本连init的面都见不到。
3.1 te文件:定义服务域与执行类型
在device/mediatek/sepolicy/basic/non_plat/下新建test_service.te:
# 定义服务域(domain) type test_service, domain; # 定义可执行文件类型(exec_type) type test_service_exec, exec_type, vendor_file_type, file_type; # 允许init以test_service身份启动该服务 init_daemon_domain(test_service); # 允许test_service域执行test_service_exec类型的文件 allow test_service test_service_exec:file { read open getattr execute }; # 允许test_service向property_service写属性(对应setprop) allow test_service property_service:property_service set;关键点解析:
type test_service, domain;:声明这是一个服务域,不是普通进程init_daemon_domain(test_service);:这是核心宏,它自动赋予test_service访问/dev/kmsg、/proc等init必需资源的权限allow ... execute;:必须显式声明执行权限,否则即使文件上下文正确,也会被拒绝property_service set;:没有这行,setprop命令会静默失败(dmesg里报avc: denied { set } for property=sys.boot.test_script)
3.2 file_contexts:给文件“贴标签”
在device/mediatek/sepolicy/basic/non_plat/file_contexts中添加:
/system/bin/init\.test\.sh u:object_r:test_service_exec:s0严格注意:
- 路径必须用正则转义:
.sh中的点要写成\.,否则匹配失败 - 行尾不能有多余空格,哪怕一个空格都会导致整个file_contexts解析失败,脚本无法启动
- 类型名
test_service_exec必须与te文件中定义的完全一致(大小写、下划线) - 如果脚本放在
/vendor/bin/,路径应改为/vendor/bin/init\.test\.sh
实测教训:曾因
file_contexts里多了一个不可见的UTF-8 BOM头,导致整条规则失效,排查耗时3小时。建议用vim -b打开检查,或用xxd file_contexts | head确认无BOM。
4. init.rc服务声明:位置、语法与生命周期
4.1 不要硬改/system/etc/init.rc
主流芯片平台(MTK、Qualcomm、Exynos)都提供了客户定制入口,如:
- MTK:
/system/etc/init/hw/init.mtXXX.rc或device/mediatek/.../init.project.rc - Qualcomm:
/system/etc/init/hw/init.qcom.rc - Exynos:
/system/etc/init/hw/init.exynos.rc
正确做法:在对应平台的init.XXX.rc末尾添加:
service test_service /system/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s04.2 每个字段的含义与避坑点
| 字段 | 含义 | 常见错误 | 正确写法 |
|---|---|---|---|
service name path | 服务名与脚本路径 | 名字含.或-,路径写错 | test_service /system/bin/init.test.sh |
class main | 归属启动类 | 漏写或写成default | 必须写main,确保随系统主服务启动 |
user/group root | 运行身份 | 写成shell或system | 开机早期只有root有足够权限 |
oneshot | 执行一次即退出 | 漏写或写成disabled | oneshot是开机脚本首选;disabled需手动start test_service |
seclabel | SELinux上下文 | 类型名拼错、漏写 | u:object_r:test_service_exec:s0(与file_contexts一致) |
特别提醒:seclabel字段必须显式声明。即使你在file_contexts里标好了,init仍会按此字段二次校验。漏写=拒绝启动。
5. 调试与验证:四步定位失败根源
脚本没启动?别猜,按顺序查这四步:
5.1 第一步:确认init.rc是否加载成功
adb shell "grep -A5 'service test_service' /system/etc/init/hw/init.mt*.rc"如果无输出,说明你的rc文件没被编译进镜像,或路径写错。
5.2 第二步:检查SELinux是否启用及策略加载
adb shell getenforce # 应返回 Enforcing adb shell ls -Z /system/bin/init.test.sh # 应显示 u:object_r:test_service_exec:s0如果ls -Z显示u:object_r:shell_exec:s0,说明file_contexts未生效。
5.3 第三步:抓取SELinux拒绝日志(最关键!)
adb shell dmesg | grep avc | tail -10典型失败日志:
avc: denied { execute } for path=/system/bin/init.test.sh dev="dm-0" ino=123456 scontext=u:r:init:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=0
→ 问题:file_contexts未匹配,或seclabel写错avc: denied { set } for property=sys.boot.test_script scontext=u:r:test_service:s0 tcontext=u:object_r:property_socket:s0 tclass=property_service permissive=0
→ 问题:te文件缺少property_service set权限
5.4 第四步:手动触发并观察行为
adb shell start test_service adb shell getprop sys.boot.test_script # 应输出 1 adb shell dmesg | grep "\[INIT\]" # 应看到时间戳日志 adb shell ls /data/local/tmp/test_script_ran # 应存在如果手动start成功,但开机不启动 → 90%是init.rc未在正确时机加载,或class设置错误。
6. 实测对比:不同Android版本的关键差异
我们在Android 8.1、9.0、10.0、11.0四台真机上对同一脚本配置做了验证,发现以下关键差异:
| Android版本 | init.rc语法支持 | SELinux策略位置 | file_contexts路径 | 备注 |
|---|---|---|---|---|
| 8.1 | 支持oneshot | non_plat/ | non_plat/file_contexts | 最稳定,推荐基准 |
| 9.0 | oneshot需配合disabled才可靠 | plat_*目录优先级更高 | plat_public/file_contexts | 若non_plat不生效,尝试plat_public |
| 10.0 | oneshot行为一致 | sepolicy v28+,init_daemon_domain宏更严格 | plat_private/file_contexts | test_service_exec必须加vendor_file_type |
| 11.0 | 强制seclabel,否则启动失败 | plat_private为默认策略源 | plat_private/file_contexts | dmesg日志更详细,报错直接指出缺失权限 |
统一适配建议:
- te文件中始终包含
vendor_file_type(即使脚本在/system/bin/) file_contexts同时在non_plat/和plat_private/中添加(双保险)init.rc中seclabel字段绝不省略
7. 总结:一份可立即复用的检查清单
把这篇指南变成你的行动手册。每次部署前,对照这份清单逐项确认:
- [ ] 脚本shebang为
#!/system/bin/sh,权限755,存放于/system/bin/ - [ ]
test_service.te已添加init_daemon_domain和property_service set权限 - [ ]
file_contexts中路径正则转义正确,类型名与te文件完全一致,无多余空格 - [ ]
init.XXX.rc中服务声明包含class main、user root、oneshot、seclabel四要素 - [ ] 编译后
adb shell ls -Z /system/bin/init.test.sh返回test_service_exec:s0 - [ ]
adb shell dmesg | grep avc无相关拒绝日志 - [ ]
adb shell start test_service可成功执行并验证效果
记住:Android开机启动不是“写个脚本就行”,而是一次对系统启动流程、SELinux模型和init机制的综合实践。每一次成功的启动,都是对这些底层逻辑的一次确认。
你现在拥有的,不再是一个可能失效的教程片段,而是一套经过多版本真机验证、覆盖全部关键陷阱的完整方法论。接下来,就是把它用在你的项目里。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。