用测试开机脚本实现属性设置,开机自动触发任务
在嵌入式Android系统开发中,经常需要在设备启动完成的第一时间执行一些初始化操作——比如设置特定系统属性、启动后台服务、挂载调试工具或触发自定义逻辑。这类需求看似简单,但实际落地时容易踩坑:脚本不执行、权限被拦截、SELinux拒绝访问、执行时机错乱……尤其在Android 8.0及以后版本,init进程重构、SELinux策略收紧、vendor分区隔离等机制让传统做法频频失效。
本文聚焦一个轻量、稳定、可验证的实践方案:通过一个精简的开机启动shell脚本,安全地设置系统属性,并确保其在系统就绪后可靠触发。不依赖复杂服务框架,不修改核心init.rc主文件,不绕过SELinux,所有步骤均基于标准Android构建流程,已在主流MTK平台实测通过。你将看到:如何写一个真正能跑起来的脚本、为什么te文件和file_contexts缺一不可、怎样避免“写了却没执行”的隐形失败,以及最关键的——如何用setprop这一最基础的能力,撬动后续自动化任务链。
全文无抽象理论,只有可粘贴、可调试、可复现的操作路径。即使你刚接触Android底层,也能照着一步步完成验证。
1. 核心目标与适用场景
1.1 这篇文章能帮你解决什么问题
- 设备每次开机后自动设置一个系统属性(如
test.prop=111),供其他模块读取判断 - 脚本在
class main阶段启动,确保zygote已就绪、property service可用 - 避免因SELinux拒绝导致脚本静默失败(常见于关闭adb但未关SELinux的调试机)
- 不修改
/system/etc/init/hw/init.rc等主配置,降低升级冲突风险 - 提供快速验证方法:push后手动执行 → 检查属性 → 确认开机生效
1.2 它不是什么
- ❌ 不是通用服务管理方案(如需长期守护进程,请用
service+restart) - ❌ 不涉及App层自动启动(如BroadcastReceiver监听BOOT_COMPLETED)
- ❌ 不提供跨Android大版本的兼容性封装(本文验证环境为Android 8.0,适配思路可迁移)
- ❌ 不绕过SELinux或降级安全策略(所有权限声明清晰、最小化)
1.3 为什么选择setprop作为首个任务
setprop是Android中最轻量、最底层的系统交互方式之一:
- 执行快(毫秒级)、无依赖(仅需property_service运行)
- 效果可验证(
getprop test.prop立即返回) - 是后续任务的“开关”:其他模块可通过轮询或watch该属性,决定是否启动自身逻辑
- 失败反馈明确:
setprop返回非零码即失败,便于日志定位
因此,它是最理想的“开机第一跳”验证点。
2. 脚本编写与本地验证
2.1 编写可执行的init脚本
新建文件init.test.sh,内容如下:
#!/system/bin/sh # 设置一个测试属性,值为字符串"111" setprop test.prop 111 # 可选:写入log便于调试(需确保logd已启动) log -t "INIT_TEST" "setprop test.prop=111 executed"关键细节说明:
- 第一行
#!/system/bin/sh必须严格匹配Android系统实际shell路径。MTK平台通常为/system/bin/sh,高通平台可能为/system/bin/sh或/system/xbin/sh。切勿写成/bin/sh,否则init会因找不到解释器而静默跳过。 setprop命令在Android 8.0+中默认可用,无需额外链接。- 避免在此处创建文件、修改权限、挂载目录等高风险操作。初期验证务必保持脚本极简,排除干扰项。
2.2 手动推送与执行验证
在设备已启用adb调试的前提下,执行以下命令:
# 将脚本推送到/system/bin/(需remount) 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 # 预期输出:111 # 查看log确认执行 adb logcat -t "INIT_TEST" -b events若getprop返回111且logcat可见日志,则脚本本身功能正常。这是后续所有步骤的前提。
❌ 若失败,请检查:
adb remount是否成功(adb shell mount | grep system确认/system为read-write)chmod权限是否正确(adb shell ls -l /system/bin/init.test.sh应显示-rwxr-xr-x)setprop是否被SELinux拦截(adb shell dmesg | grep avc查看拒绝日志)
3. SELinux策略配置(关键!不可跳过)
Android 8.0起,init启动的每个服务都受SELinux域约束。即使setprop本身允许,脚本文件的类型、执行域、访问权限也必须显式声明,否则init会直接拒绝启动。
3.1 定义服务类型与执行文件类型
在device/mediatek/sepolicy/basic/non_plat/目录下(MTK平台路径,其他厂商请对应调整)新建test_service.te文件:
# 定义服务域类型 type test_service, coredomain; # 定义脚本文件类型 type test_service_exec, exec_type, vendor_file_type, file_type; # 允许init_daemon_domain(即init)以test_service域运行该服务 init_daemon_domain(test_service); # 允许test_service域读取、执行该脚本文件 allow test_service test_service_exec:file { read open getattr execute };说明:
coredomain表示该域属于核心服务范畴,可继承init的基本能力。exec_type和vendor_file_type确保该类型能被正确识别为可执行文件。init_daemon_domain()是必需宏,建立init与新服务域的信任关系。- 最后一行
allow规则明确授予执行权限,不可省略。
3.2 关联脚本文件到SELinux类型
在同一目录下的file_contexts文件末尾添加一行:
/system/bin/init\.test\.sh u:object_r:test_service_exec:s0注意正则转义:.sh中的点号需用\.转义,否则匹配失败。
此行作用是告诉SELinux:当系统看到路径为/system/bin/init.test.sh的文件时,将其安全上下文标记为u:object_r:test_service_exec:s0,从而与test_service_exec类型关联。
3.3 构建并验证SELinux策略
执行完整编译(或仅编译sepolicy):
m sepolicy烧录镜像后,检查策略是否生效:
# 查看脚本文件的安全上下文 adb shell ls -Z /system/bin/init.test.sh # 预期输出中应包含:u:object_r:test_service_exec:s0 # 检查test_service域是否存在 adb shell sesearch -s test_service -t test_service_exec -c file -p execute # 应返回允许规则上述两项均通过,表明SELinux策略已正确加载。
4. 在init中注册服务
4.1 创建独立的init配置片段
强烈建议不要直接修改/system/etc/init/hw/init.rc。芯片厂商通常提供专用扩展点,例如MTK平台的init.mtk.rc或init.vendor.rc。在device/mediatek/sepolicy/basic/non_plat/同级目录(如device/mediatek/common/rootdir/etc/)中,新建init.test.rc:
# 测试开机脚本服务 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类服务组中启动,此时property_service、logd等基础服务均已就绪。user root&group root:以root权限运行,满足setprop要求。oneshot:执行完即退出,符合一次性设置属性的语义。seclabel:显式指定SELinux标签,与file_contexts中定义的类型一致。
4.2 引入配置片段
在主init配置(如init.mtk.rc)中添加导入语句:
import /system/etc/init/init.test.rc或在BoardConfig.mk中确保该文件被包含进最终init.rc。
4.3 验证init配置加载
烧录后检查:
# 查看init是否识别到该服务 adb shell getprop | grep init.svc.test_service # 开机后应短暂出现:init.svc.test_service=running,随后变为=stopped # 查看init日志(需串口或开启init log) adb shell dmesg | grep -i "test_service"5. 开机全流程验证与排错指南
5.1 标准验证流程
- 烧录含上述修改的完整镜像
- 冷启动设备(非reboot,确保完整init流程)
- 等待30秒后执行:
adb shell getprop test.prop # 成功:返回 111 # ❌ 失败:返回空或报错
5.2 常见失败现象与精准定位
| 现象 | 可能原因 | 快速定位命令 |
|---|---|---|
getprop test.prop无输出 | 脚本根本未执行 | `adb shell dmesg |
dmesg显示avc: denied | SELinux权限缺失 | `adb shell dmesg |
getprop返回旧值或错误值 | 脚本执行但setprop失败 | adb shell logcat -b events -t "INIT_TEST"查脚本内log;adb shell setprop test.prop 222 && adb shell getprop test.prop手动测试setprop是否可用 |
init.svc.test_service状态为stopping | 脚本执行异常退出 | adb shell cat /proc/$(pidof init)/fd/2(需root)或检查logcat -b events中init的错误输出 |
5.3 进阶:从属性触发后续任务
test.prop设为开关后,其他模块可主动响应:
- Native进程:用
property_get("test.prop", buf, "")轮询或监听 - Java层:
SystemProperties.get("test.prop")(需android.permission.READ_FRAME_BUFFER,仅系统App) - Shell脚本:在其他init服务中加入
while [ "$(getprop test.prop)" != "111" ]; do sleep 1; done等待
这实现了“开机设置→条件触发”的解耦设计,比硬编码启动更灵活。
6. 总结:一个可靠开机任务的最小可行实践
本文带你走通了一条经过实测的Android开机脚本落地路径。它没有炫技,只做三件事:写对脚本、配对SELinux、注册对init。每一步都直指实际开发中最易卡壳的环节。
回顾关键要点:
- 脚本必须用
/system/bin/sh解释器,且仅做setprop等确定性操作,规避权限与路径陷阱; - SELinux不是可选项:
te文件定义域、file_contexts绑定类型、init.rc中seclabel三者缺一不可; class main是黄金时机:它平衡了“足够早”(系统服务就绪)与“足够稳”(无竞态);- 验证必须分层:先手动执行 → 再检查SELinux → 最后看开机效果,层层递进,拒绝黑盒。
当你下次需要在开机瞬间点亮一个标志、启动一个探测、或打开一个开关时,这套方法就是你的起点。它足够小,小到可以今天就集成;也足够坚实,坚实到能支撑起更复杂的自动化链条。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。