Android系统开机自动运行脚本,新手入门必看
在Android设备开发和定制过程中,经常需要让某些服务或脚本在系统启动完成时自动运行——比如初始化硬件参数、配置网络环境、启动后台守护进程,或者执行一些诊断检测任务。但很多刚接触Android底层开发的朋友会发现:明明写好了shell脚本,也push进系统了,却始终无法在开机时自动执行。问题往往不出在脚本本身,而在于Android特有的启动机制、权限模型和安全策略。
本文不是讲Linux通用的rc.local方案(那在Android上根本不可用),也不是堆砌晦涩的SELinux术语,而是以一个真实可运行的“测试开机启动脚本”镜像为蓝本,手把手带你走通从写脚本、加权限、改配置到验证成功的完整链路。所有步骤均基于Android 8.0+主流版本实测通过,适配MTK等常见平台,不依赖ADB调试器,也不要求串口支持——你只需要一台已root或可刷机的设备,就能跟着一步步操作成功。
全文没有抽象理论,只有具体命令、明确路径、可复制的代码片段,以及每个环节背后“为什么必须这么做”的直白解释。哪怕你之前没碰过init.rc、没听过te文件、甚至分不清/system/bin/sh和/bin/sh的区别,也能照着做完。
1. 先搞清楚:Android开机启动和Linux有什么不一样
很多人以为Android就是个Linux发行版,所以想当然地把Linux那一套搬过来——比如修改/etc/rc.local、写systemd service、或者直接往init.d里丢脚本。结果发现全都不生效。原因很简单:Android不用sysvinit,也不用systemd,它用的是自己定制的init进程,整套启动逻辑完全不同。
Android的init进程在内核启动后立即运行,它读取的是/system/etc/init/下的.rc文件(如init.rc)或/system/etc/init/目录中按规则命名的配置文件。这些文件定义了哪些服务要启动、以什么用户身份运行、依赖关系如何、是否只运行一次等。
更重要的是,从Android 5.0开始,SELinux默认处于enforcing模式。这意味着:
- 即使你的脚本语法完全正确,
- 即使路径写得毫无错误,
- 即使init.rc里已经声明了service,
只要SELinux策略没放行,脚本就会被静默拦截,连日志都看不到。
所以,真正的开机自启流程只有四步,缺一不可:
- 写一个能在Android上跑起来的shell脚本;
- 告诉SELinux:“这个脚本是可信的,允许它被init调用”;
- 在init配置中注册这个服务;
- 验证它是否真的在开机时执行了。
下面我们就按这个顺序,一项一项来。
2. 第一步:写出真正能跑的Android shell脚本
2.1 脚本位置与解释器选择
Android的shell环境和标准Linux不同。它的/bin/sh通常不存在,系统级脚本必须使用/system/bin/sh(或少数平台用/system/xbin/sh)。如果你写成#!/bin/sh,脚本将直接失败,且不会报错——init只会默默跳过它。
正确写法:
#!/system/bin/sh❌ 错误写法(常见坑):
#!/bin/sh #!/usr/bin/env sh #!/system/xbin/bash # Android默认不带bash小贴士:怎么确认你设备用的是哪个解释器?用adb shell连上去后执行
which sh或ls -l /system/bin/sh就能看到真实路径。
2.2 一个最小可用的测试脚本
我们新建一个名为init.test.sh的脚本,内容极简,只为验证“是否真能执行”:
#!/system/bin/sh # 设置一个系统属性,这是最轻量、最安全的验证方式 # 不涉及文件读写、不触发权限检查、不依赖外部命令 setprop test.boot.started 1 # 可选:再写一条便于调试的日志(需确保logd已就绪) # log -t "INIT_TEST" "Script executed at $(date)"注意事项:
- 不要尝试
touch /data/test.txt或echo "ok" > /sdcard/log.txt——早期init阶段/data和/sdcard可能还未挂载,会失败; - 不要用
sleep 5等待其他服务——init有严格的超时机制,卡住会导致服务被kill; setprop是init自带能力,无需额外权限,且属性值可在任意时刻用getprop test.boot.started查看,是最可靠的验证手段。
2.3 手动测试脚本是否可用
别急着改系统配置,先确保脚本本身没问题:
# 将脚本推送到设备(假设已root) adb push init.test.sh /system/bin/ # 赋予可执行权限(Android对权限更严格) adb shell chmod 755 /system/bin/init.test.sh # 手动执行一次,看是否成功 adb shell /system/bin/init.test.sh adb shell getprop test.boot.started # 应输出 1如果这一步失败,请回头检查:
- 脚本首行是否为
#!/system/bin/sh; - 是否用了Windows换行符(CRLF)?请用Unix换行(LF)保存;
/system分区是否已remount为可写?adb shell mount -o rw,remount /system
只有手动执行成功了,才能进入下一步。
3. 第二步:为脚本添加SELinux策略(绕不开的关卡)
Android 8.0之后,SELinux是硬性门槛。即使你临时关闭SELinux(setenforce 0),脚本仍可能因缺少file_context而无法加载——因为init在解析.rc文件时,会先查文件上下文(file context),找不到就拒绝启动服务。
整个SELinux适配分三部分:定义类型、声明上下文、赋予执行权限。
3.1 定义服务类型(test_service.te)
在厂商SELinux策略目录下(如device/mediatek/sepolicy/basic/non_plat/),新建文件test_service.te:
# 声明一个新的域类型,代表这个服务的运行环境 type test_service, coredomain; # 声明脚本文件的类型,用于匹配file_context type test_service_exec, exec_type, vendor_file_type, file_type; # 让test_service域可以被init_daemon_domain管理(即允许init启动它) init_daemon_domain(test_service); # 允许test_service域执行test_service_exec类型的文件 allow test_service test_service_exec:file { read open getattr execute };关键说明:
coredomain表示这是一个核心服务域,具备基础系统访问能力;exec_type是Android SELinux中对可执行文件的标准分类;init_daemon_domain()是必须调用的宏,它自动赋予test_service域访问init所需资源的权限(如socket、property_service等);- 最后一行
allow ... execute才是让脚本真正跑起来的关键授权。
如果你不确定该加哪条allow规则,可以先注释掉最后一行,开机后用
adb shell dmesg | grep avc查看被拒绝的操作,再针对性添加。
3.2 声明文件上下文(file_contexts)
在同一目录下的file_contexts文件末尾,添加一行:
/system/bin/init\.test\.sh u:object_r:test_service_exec:s0注意:
- 路径必须用正则转义
.(即\.),否则匹配失败; - 类型名必须和
.te文件中定义的test_service_exec完全一致; s0是MLS级别,在大多数设备上固定为s0,无需改动。
这条语句的意思是:“所有路径匹配/system/bin/init.test.sh的文件,其SELinux上下文设为u:object_r:test_service_exec:s0”。
3.3 编译并刷入新策略
修改完SELinux策略后,必须重新编译bootimage或systemimage,并刷入设备。具体命令取决于你的编译环境,典型流程如下:
# 清理旧策略缓存 m clean # 重新编译sepolicy(会生成policy.db) m sepolicy # 编译system.img(包含更新后的file_contexts和policy.db) m systemimage # 刷入system分区(需fastboot模式) fastboot flash system system.img如果你使用的是预编译镜像(如本文提到的“测试开机启动脚本”镜像),这一步已由镜像制作者完成,你只需确认镜像中已包含上述策略即可。
4. 第三步:在init配置中注册服务
Android不推荐直接修改/system/etc/init/init.rc,因为它是AOSP标准文件,容易被OTA升级覆盖。更稳妥的做法是:在厂商专用的init配置中添加,例如:
- MTK平台:
/system/etc/init/init.mtk.rc或device/mediatek/common/rootdir/etc/init.mtk.rc - 高通平台:
/system/etc/init/init.qcom.rc - 或统一放在
/system/etc/init/目录下,以.rc结尾的独立文件(如test_boot.rc)
我们新建一个/system/etc/init/test_boot.rc,内容如下:
# 定义一个名为test_boot的服务,执行我们的脚本 service test_boot /system/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s0 # 指定该服务在main类启动时运行(即系统基本服务就绪后) on property:sys.boot_completed=1 start test_boot各字段含义:
class main:表示该服务属于main启动类,会在init启动main类时准备;user/group root:以root身份运行(必要,普通用户无权设置系统属性);oneshot:执行完即退出,不常驻——适合一次性初始化任务;seclabel:必须与file_contexts中定义的类型一致;on property:sys.boot_completed=1:这是最关键的触发条件!它确保脚本在Android框架层完全启动(即桌面可操作)后再运行,避免因服务未就绪而失败。
验证技巧:你可以把
start test_boot改成exec - -- /system/bin/sh -c 'setprop test.boot.delayed 1',然后用getprop test.boot.delayed确认是否触发。
5. 第四步:验证脚本是否真正在开机时运行
完成以上三步后,重启设备,执行以下命令验证:
# 查看服务是否被init识别 adb shell ls -Z /system/bin/init.test.sh # 应显示 u:object_r:test_service_exec:s0 # 查看服务状态(Android 8.0+支持) adb shell dumpsys activity services | grep test_boot # 检查属性是否已设置(最直接证据) adb shell getprop test.boot.started # 应输出 1 # 查看init日志(确认是否启动过) adb shell dmesg | grep -i "test_boot\|init\.test"如果getprop test.boot.started返回1,恭喜你,脚本已在开机时成功执行!
如果仍是空值,请按顺序排查:
ls -Z是否显示正确的SELinux上下文?→ 检查file_contexts和策略编译;dumpsys activity services是否列出test_boot?→ 检查.rc文件路径和语法;dmesg是否有avc denied日志?→ 根据提示补全SELinux allow规则;getprop sys.boot_completed是否为1?→ 确保触发条件时机正确。
6. 进阶建议:让脚本更健壮、更实用
上面只是一个最小可行示例。在实际项目中,你可能需要:
6.1 支持多平台的路径兼容写法
有些设备/system/bin不可写,或脚本需放在vendor分区。可统一用/vendor/bin/,并在file_contexts中同步修改:
/vendor/bin/init\.test\.sh u:object_r:test_service_exec:s0同时在.rc中改为:
service test_boot /vendor/bin/init.test.sh6.2 添加错误捕获与日志记录
虽然init阶段logd可能未就绪,但你可以用logwrapper兜底:
#!/system/bin/sh logwrapper setprop test.boot.started 1或重定向输出到临时文件(需确保目录存在):
#!/system/bin/sh echo "$(date): boot script started" >> /data/local/tmp/boot.log 2>&1 setprop test.boot.started 16.3 实现条件启动(仅特定设备型号运行)
利用系统属性判断机型:
#!/system/bin/sh if getprop ro.product.model | grep -q "MyDevice"; then setprop test.boot.started 1 fi6.4 启动后唤醒应用或发送广播(需framework层配合)
# 启动Activity(需声明exported) am start -n com.example/.BootReceiver # 发送自定义广播 am broadcast -a com.example.BOOT_COMPLETED这些功能都建立在基础启动能力之上,只要第一步跑通,后续扩展水到渠成。
7. 总结:开机自启不是玄学,而是可复现的工程流程
回顾整个过程,你会发现所谓“Android开机自启”,其实是一套清晰、严谨、可验证的工程链路:
- 脚本本身必须适配Android的shell环境,用
/system/bin/sh,做最小化验证; - SELinux策略不是可选项,而是必答题,它由
te文件定义行为、file_contexts绑定路径、allow规则放开权限; - init配置要选对位置、写对语法、设对触发时机,
oneshot + sys.boot_completed是最稳妥组合; - 验证手段要直接有效,
getprop比看日志更快,ls -Z比猜路径更准。
你不需要成为SELinux专家,也不必读懂整个init源码。只需要理解每一行配置的作用,知道它为什么在这里、不加会怎样、加错会怎样——这就足够在真实项目中稳定落地。
现在,你已经掌握了Android开机自动运行脚本的核心方法。下一步,就是把它用在你的硬件初始化、AI模型预热、IoT设备自检等真实场景中。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。