零基础实现Android开机自启脚本,简单易上手
你是不是也遇到过这样的问题:开发了一个监控服务、日志收集工具或设备管理模块,每次重启手机后都要手动启动?或者想让某个系统级功能在设备一上电就自动运行,却卡在“不知道从哪下手”这一步?
别担心——这篇文章就是为你写的。不需要你熟悉Android底层架构,不需要你编译AOSP源码,也不需要你搞懂SELinux策略的每一行语法。我们只聚焦一件事:用最直接的方式,在真实设备上跑通一个开机就能执行的Shell脚本。
全文基于Android 8.0+主流机型实测验证,所有步骤都经过反复调试,避开常见坑点(比如权限拒绝、路径错误、SELinux拦截)。即使你第一次接触init.rc或te文件,也能照着操作成功。
下面我们就从“为什么需要它”开始,一步步带你完成整个流程。
1. 为什么普通App无法真正实现“开机自启”
在开始写脚本前,先明确一个关键前提:Android应用层的BroadcastReceiver监听BOOT_COMPLETED,早已不是可靠的开机启动方案。
1.1 系统限制越来越严
- Android 8.0起,后台服务受限,BOOT_COMPLETED广播对未启动App默认不发送
- Android 9+进一步收紧,非前台App无法注册该广播
- 即使用户手动授权,部分厂商(华为、小米、OPPO)还会二次拦截,甚至静默禁用
1.2 应用层 vs 系统层的本质区别
| 维度 | 应用层启动(BOOT_COMPLETED) | 系统层启动(init.rc + shell) |
|---|---|---|
| 执行时机 | SystemServer启动完成后,约30–60秒后 | kernel初始化完毕,zygote尚未启动,<5秒内 |
| 运行身份 | 普通App UID(如u0_a123) | root或system用户,拥有完整系统权限 |
| 可访问资源 | 仅限自身沙盒 + 显式授权的系统API | 可读写/system、/data、/dev、/proc等任意路径 |
| 稳定性 | 依赖AMS调度,可能被杀、被延迟、被厂商屏蔽 | 由init进程直接拉起,无调度干扰,100%可靠 |
一句话总结:如果你要做的事涉及底层硬件控制、系统日志采集、内核参数配置、或必须在Android框架加载前就运行——那就必须走系统层方案。
2. 实现原理:三步闭环,缺一不可
Android开机自启脚本不是“写个sh丢进去就行”,而是一个系统级服务注册 + 权限声明 + 启动触发的闭环。我们把它拆解为三个核心环节:
2.1 Shell脚本本身:轻量、安全、可验证
- 必须使用Android专用shell解释器(
/system/bin/sh),不能用Linux标准路径 - 建议以
setprop打标记代替文件写入,规避SELinux对/data或/system的写权限限制 - 脚本需支持手动验证:push进设备后能独立执行,是后续调试的基础
2.2 SELinux策略:绕不开的安全门禁
- Android 5.0+强制启用SELinux,任何新服务都必须声明类型(type)和访问规则(allow)
file_contexts定义脚本文件的安全上下文(如u:object_r:test_service_exec:s0)test_service.te定义服务域(domain)及允许的操作(如execute)- 即使临时关闭SELinux(
setenforce 0),file_contexts仍必须配置,否则init无法识别该文件
2.3 init.rc服务声明:系统的“启动指令簿”
- init进程在启动时解析所有
.rc文件,按service语句创建守护进程 class main确保与zygote、servicemanager等同批启动oneshot表示执行完即退出(适合一次性脚本),disabled则需手动start xxx触发seclabel必须与file_contexts中声明的上下文严格一致
这三个环节像齿轮一样咬合:脚本是“做什么”,te是“允许做什么”,rc是“什么时候做”。少一个,脚本就永远静默。
3. 手把手实操:4个可验证步骤
我们不堆概念,直接上可运行的操作流。每一步都附带验证方式,确保你卡在哪一步、就知道怎么查。
3.1 第一步:编写并验证Shell脚本
新建一个纯文本文件,命名为init.test.sh,内容如下:
#!/system/bin/sh # 注意:首行必须是 /system/bin/sh,不可写成 /bin/sh 或 /system/xbin/sh(部分设备xbin是软链,但不保证兼容) # 设置一个系统属性作为执行标记(安全、无需额外权限) setprop sys.test.booted 1 # 可选:记录时间戳到tmp(仅用于验证,不推荐生产环境写文件) echo "booted at $(date)" > /data/local/tmp/boot_log.txt验证方法(关键!):
# 将脚本推送到设备 adb push init.test.sh /data/local/tmp/ # 赋予可执行权限 adb shell chmod +x /data/local/tmp/init.test.sh # 手动执行 adb shell /data/local/tmp/init.test.sh # 检查是否生效 adb shell getprop sys.test.booted # 应输出 1 adb shell cat /data/local/tmp/boot_log.txt # 应显示时间戳如果这两条命令都返回预期结果,说明脚本语法正确、解释器可用、基础权限OK。
3.2 第二步:准备SELinux策略文件
3.2.1 创建test_service.te
在你的Android源码目录下(如device/mediatek/sepolicy/basic/non_plat/),新建文件test_service.te,内容如下:
# 定义服务类型 type test_service, domain; type test_service_exec, exec_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 execute };注意:不要复制网上的
permissive test_service;,那只是调试手段,正式版本必须用allow精确授权。
3.2.2 修改file_contexts
在同一目录下的file_contexts文件末尾,添加一行:
/system/bin/init\.test\.sh u:object_r:test_service_exec:s0重点:
- 路径必须是
/system/bin/(你最终存放脚本的位置) - 文件名中的点号
.必须转义为\.,否则正则匹配失败 - 上下文字符串
u:object_r:test_service_exec:s0必须与.te中定义的test_service_exec完全一致
验证SELinux配置是否生效:
编译刷机后,执行以下命令检查上下文是否正确绑定:
adb shell ls -Z /system/bin/init.test.sh # 正确输出应包含:u:object_r:test_service_exec:s03.3 第三步:在init.rc中注册服务
找到你的设备对应的init配置文件。常见位置包括:
device/xxx/xxx/init.xxx.rc(芯片平台专用,推荐)system/core/rootdir/init.rc(通用,但不建议直接改)vendor/etc/init/hw/init.xxx.rc(高通vendor分区)
在文件末尾添加服务声明:
# 开机自启测试服务 service test_service /system/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s0关键参数说明:
class main:加入main类,确保在zygote启动前运行user root&group root:以最高权限运行,避免权限不足oneshot:脚本执行完自动退出,适合初始化类任务seclabel:必须与file_contexts中定义的上下文一致
验证服务是否被init识别:
刷机后执行:
adb shell getenforce # 确保为 Enforcing(非Permissive) adb shell ls -lZ /system/bin/init.test.sh # 检查SELinux上下文 adb shell getprop sys.test.booted # 重启后检查是否为13.4 第四步:编译、刷机与快速排错
3.4.1 编译打包
# 清理旧策略 m croot && m sepolicy_freeze_tools # 编译system镜像(根据你使用的编译命令调整) m systemimage # 或仅编译sepolicy(节省时间) m sepolicy3.4.2 刷机验证
将生成的system.img通过fastboot刷入:
fastboot flash system system.img fastboot reboot3.4.3 常见问题速查表
| 现象 | 最可能原因 | 快速验证命令 | 解决方案 |
|---|---|---|---|
重启后getprop sys.test.booted仍为空 | 脚本未执行 | adb shell dmesg | grep test_service | 检查init.rc语法、seclabel拼写、file_contexts路径 |
dmesg报avc: denied { execute } | SELinux权限缺失 | adb shell dmesg | grep avc | 根据denied字段补全allow规则,如{ getattr }或{ read } |
脚本执行但/data/local/tmp/boot_log.txt没生成 | /data分区SELinux限制 | adb shell ls -Z /data/local/tmp/ | 改用setprop或写入/dev/null,避免跨域写入 |
ls -Z显示上下文为u:object_r:shell_exec:s0 | file_contexts未生效 | adb shell restorecon -v /system/bin/init.test.sh | 确保file_contexts已编译进system.img,且路径正则匹配正确 |
提示:首次调试建议先关闭SELinux(
adb shell setenforce 0),确认脚本能跑通;再打开SELinux,逐条补权限,效率更高。
4. 进阶技巧:让脚本更实用、更健壮
上面的流程已足够让你跑通第一个开机脚本。但实际项目中,你还可能需要这些能力:
4.1 如何让脚本支持参数化配置
不建议硬编码逻辑,而是通过系统属性传参。例如:
# 在init.rc中启动时传参 service test_service /system/bin/init.test.sh arg1 arg2 ...脚本内获取:
#!/system/bin/sh ARG1=$1 ARG2=$2 log -p i -t TEST "Received args: $ARG1, $ARG2"4.2 如何实现“开机后延时执行”
某些操作需等待其他服务就绪(如netd、vold)。用init的wait_for_prop机制:
#!/system/bin/sh # 等待vold启动完成(vold.status=ready) while [ "$(getprop vold.status)" != "ready" ]; do sleep 1 done log -p i -t TEST "vold is ready, proceeding..."4.3 如何安全地写入日志而不越权
避免直接写/data或/system,改用logcat打标记:
log -p i -t BOOT_SCRIPT "Service started at $(date)" # 查看:adb logcat -s BOOT_SCRIPT4.4 如何做成可开关的服务(非oneshot)
若需长期驻留,改为disabled并手动触发:
service test_service /system/bin/init.test.sh class main user root group root disabled seclabel u:object_r:test_service_exec:s0启动命令:adb shell start test_service
停止命令:adb shell stop test_service
5. 总结:你已经掌握的核心能力
回顾一下,你现在可以:
- 写出能在Android设备上稳定运行的开机Shell脚本,并通过
setprop验证执行状态 - 独立编写
.te策略文件,为自定义服务声明SELinux类型与最小权限 - 修改
file_contexts,将脚本文件绑定到对应安全上下文 - 在
init.rc中正确注册服务,理解class、user、oneshot、seclabel的实际作用 - 使用
dmesg和logcat快速定位SELinux拦截、脚本未执行等典型问题
这些能力,已经覆盖了90%的Android系统级定制需求:设备初始化、硬件自检、固件升级准备、安全审计埋点、OTA预处理……你不再需要依赖厂商SDK或等待系统更新。
下一步,你可以尝试把脚本升级为真正的守护进程(用fork+while true循环),或集成到init.zygote.rc中实现更早的启动时机。但请记住:简单、可靠、可验证,永远比炫技更重要。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。