news 2026/4/23 12:26:43

如何正确编写Android开机shell脚本?看这篇就行

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何正确编写Android开机shell脚本?看这篇就行

如何正确编写Android开机shell脚本?看这篇就行

在Android系统开发中,让一段自定义逻辑在设备启动时自动运行,是很多定制化需求的基础能力。比如自动挂载特定分区、初始化硬件模块、设置系统属性、启动后台服务等。但很多开发者第一次尝试时会发现:脚本明明写好了,也放进系统了,却始终不执行——不是权限报错,就是SELinux拦截,或是init.rc语法出错,甚至脚本路径写错导致根本找不到文件。

这不是你不够细心,而是Android从8.0(Oreo)开始,对开机启动流程做了更严格的管控:init进程不再无条件执行任意脚本;SELinux策略默认拒绝未声明的域访问;/system分区默认只读;shell解释器路径与Linux发行版不同……这些细节稍有疏忽,脚本就“静默失败”。

本文不讲抽象理论,也不堆砌源码片段,而是以一个真实可运行的最小闭环为例,手把手带你完成从脚本编写→权限配置→init集成→验证调试的全流程。所有步骤均基于Android 8.0+主流平台(高通/MTK通用),已在实机反复验证。你不需要理解SELinux策略编译原理,也能让脚本稳稳跑起来。


1. 明确目标:我们要实现什么

在Android设备完成bootloader加载、kernel启动、init进程初始化后,自动执行一段shell逻辑,并能通过adb快速验证是否生效。

成功标志:

  • 设备重启后,getprop test.prop能稳定返回111
  • ps -A | grep init.test.sh可查到进程(因使用oneshot,仅执行一次)
  • 无SELinux avc denied日志(dmesg | grep avclogcat -b events | grep avc

注意:本文不涉及root权限获取、不修改ro.build.type、不关闭SELinux(不推荐也不安全),所有操作均符合Android官方安全模型。


2. 编写可执行的shell脚本

2.1 脚本内容与关键细节

新建文件init.test.sh,内容如下:

#!/system/bin/sh # 注意:必须使用 /system/bin/sh,不是 /bin/sh,也不是 /system/xbin/sh(除非你确认存在) # Android的sh是toybox或ash的精简版,不支持bash特有语法(如数组、[[ ]]、let等) # 设置一个测试属性,用于快速验证脚本是否执行 setprop test.prop 111 # 可选:记录时间戳到临时文件(仅调试用,避免写入只读分区) # echo "init.test.sh ran at $(date)" > /data/local/tmp/init_test.log # 可选:启动一个简单后台进程(如ping网关,验证网络可用性) # nohup ping -c 3 192.168.1.1 > /dev/null 2>&1 &

2.2 为什么这些细节决定成败

  • 解释器路径必须准确:Android系统中/bin/sh通常不存在,/system/xbin/sh在部分旧版本存在,但8.0+统一使用/system/bin/sh(指向toybox)。写错将直接导致init: cannot execv错误。
  • 禁止使用bash语法:init进程调用的是/system/bin/sh,它不识别[[ ]]$(( ))source等bash特性。若需复杂逻辑,应改用awksed或提前编译为二进制。
  • 属性设置是最安全的验证方式:相比创建文件(需处理目录权限、SELinux上下文),setprop无需额外权限,且getprop命令在任何adb shell中都可立即检查。
  • 注释行不能干扰执行#开头的行会被忽略,但确保第一行#!/system/bin/sh严格顶格、无空格、无BOM,否则内核无法识别shebang。

2.3 本地验证:先手动运行,再交给init

将脚本push到设备并手动执行,是排除脚本自身问题的最快方法:

# 推送脚本(需adb root) adb root adb remount adb push init.test.sh /system/bin/init.test.sh # 添加可执行权限(关键!) adb shell chmod 755 /system/bin/init.test.sh # 手动执行并检查结果 adb shell /system/bin/init.test.sh adb shell getprop test.prop # 应输出 111

如果这一步失败,请回头检查脚本语法、权限、路径——不要跳过此步直接进init集成


3. 配置SELinux策略:让init有权运行它

从Android 5.0起,SELinux处于enforcing模式,init进程只能在明确授权的SELinux域中执行程序。未经声明的脚本会被静默阻止。

3.1 定义服务类型与执行文件类型

在厂商SELinux策略目录(如device/mediatek/sepolicy/basic/non_plat/device/qcom/sepolicy/common/)下,新建文件test_service.te

# 声明一个新服务域,继承init_daemon的基本能力 type test_service, domain; type test_service_exec, exec_type, vendor_file_type, file_type; # 允许该域由init_daemon_domain管理(标准做法) init_daemon_domain(test_service); # 允许test_service域执行test_service_exec类型的文件 allow test_service test_service_exec:file { read open getattr execute };

说明:init_daemon_domain()宏已预定义了init服务所需的基础权限(如访问/dev、/proc、socket等),我们只需补充“执行权”。避免使用permissive test_service(仅调试用,上线必须删除)。

3.2 关联文件路径与SELinux上下文

在同目录下的file_contexts文件中,添加一行(注意路径匹配规则):

/system/bin/init\.test\.sh u:object_r:test_service_exec:s0

重点:

  • 使用正则转义\., 否则init.test.sh会被误匹配为initXtestXsh
  • 路径必须与脚本实际存放位置完全一致(本文用/system/bin/,若放/vendor/bin/则需改路径);
  • s0是MLS级别,标准Android用mlsconstrain限制,此处保持默认即可。

3.3 策略编译与验证

修改完策略后,必须重新编译并刷入system.img。验证是否生效:

# 查看文件SELinux上下文 adb shell ls -Z /system/bin/init.test.sh # 正常输出应包含:u:object_r:test_service_exec:s0 # 检查init是否加载了该服务(重启后) adb shell ps -Z | grep test_service # 应看到类似:u:r:test_service:s0 root 1234 ... /system/bin/init.test.sh

ls -Z显示u:object_r:shell_exec:s0,说明file_contexts未生效或未刷入;若ps -Z看不到进程,说明init未启动或SELinux拒绝。


4. 在init.rc中注册服务

Android 8.0+采用HIDL和Treble架构,init.rc被拆分为多个片段。切勿直接修改system/core/rootdir/init.rc,而应使用厂商提供的扩展点。

4.1 标准集成位置(按平台选择其一)

平台类型推荐路径说明
MTK平台device/mediatek/sepolicy/basic/non_plat/init.mtXXXX.rcXXXX为芯片型号,如mt6765
高通平台device/qcom/sepolicy/common/init.qcom.rcinit.target.rc
通用方案system/etc/init/目录下新建.rc文件Android 8.0+支持,需确保init能加载

4.2 编写service定义

在选定的.rc文件末尾,添加以下内容:

# 定义test_service服务 service test_service /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_service

关键参数解析:

  • class main:归入main服务组,确保在关键系统服务(如surfaceflinger、zygote)之后启动;
  • user/group root:以root权限运行(必要,因需设置系统属性);
  • oneshot:执行一次即退出,避免常驻消耗资源;
  • seclabel:显式指定SELinux标签,与file_contexts中定义一致;
  • on property:sys.boot_completed=1:比单纯on early-init更可靠,确保Zygote已启动、property service就绪。

4.3 验证init.rc语法

编译前,用init自带工具检查语法:

# 在编译环境中执行(非adb) ./out/host/linux-x86/bin/ckati -f build/make/core/main.mk # 或直接查看编译日志中是否有"init: parse error"

常见错误:seclabel后多空格、on触发器缩进不一致、路径含中文或特殊字符。


5. 编译、刷机与最终验证

5.1 编译步骤(简化版)

# 1. 编译SELinux策略(生成plat_sepolicy.cil) m mm -j32 vendor/sepolicy # 2. 编译init.rc相关模块(触发system.img重建) m mm -j32 system/core/init # 3. 生成完整system.img m snod # 4. 刷入设备(fastboot模式) fastboot flash system system.img fastboot reboot

提示:若使用make installclean && m全量编译,耗时较长;建议仅编译变更模块。

5.2 重启后三步验证法

设备重启后,立即执行:

# 第一步:检查属性是否设置成功(最直接) adb shell getprop test.prop # 期望输出 111 # 第二步:检查init日志中是否有启动记录 adb logcat -b events | grep -i "test_service" # 期望看到:init: starting service 'test_service'... # 第三步:排查SELinux拦截(如有失败) adb shell dmesg | grep avc # 若出现avc denied,复制完整行,用audit2allow生成补丁

5.3 常见问题速查表

现象可能原因快速解决
getprop无输出脚本未执行检查logcat -b events中service是否start
dmesg有avc deniedSELinux策略缺失复制avc行,audit2allow -p out/target/product/xxx/obj/ETC/sepolicy_neverallows_intermediates/sepolicy*
ps查不到进程oneshot已退出改用disabled+start test_service手动触发
init: cannot execv脚本路径错误或无x权限adb shell ls -l /system/bin/init.test.sh,确认权限为-rwxr-xr-x
脚本执行但属性未生效setprop被权限限制确认user root,或改用write /proc/sys/kernel/msgmax 12345等root可写节点

6. 进阶建议:让脚本更健壮、更实用

6.1 日志记录(不依赖logcat)

在脚本中添加轻量日志,便于离线分析:

# 将输出重定向到/data/local/tmp(用户可读写) LOG_FILE="/data/local/tmp/init_test.log" echo "[$(date)] init.test.sh started" >> $LOG_FILE setprop test.prop 111 echo "[$(date)] setprop done" >> $LOG_FILE

6.2 条件执行(避免重复)

利用系统属性做开关,防止多次启动:

# 检查是否已执行过 if [ "$(getprop test.prop.executed)" = "1" ]; then exit 0 fi setprop test.prop.executed 1 # ... 主逻辑 ...

6.3 启动守护进程(非oneshot场景)

若需长期运行,改为:

service test_daemon /system/bin/init.test.sh class main user root group root # 删除oneshot,添加restart restart seclabel u:object_r:test_service_exec:s0

并在脚本末尾加while true; do sleep 3600; done保持进程存活。


7. 总结:开机脚本落地的四个铁律

写好一个Android开机shell脚本,本质是与系统安全机制的协同,而非对抗。牢记这四条经验,可避开90%的坑:

  • 路径即生命/system/bin/sh是唯一可靠的解释器,/system/bin/是唯一无需额外SELinux上下文的可执行目录;
  • 属性即凭证:用setprop验证比写文件更安全、更快速,它是init阶段最可信的通信通道;
  • SELinux非障碍,是说明书:每一条avc denied都在告诉你“这里需要什么权限”,而不是“禁止你做”;
  • init.rc是契约,不是清单on property:触发器比on early-init更精准,class main确保依赖满足,seclabel是强制要求而非可选。

现在,你已经掌握了从零构建一个稳定、合规、可验证的Android开机脚本的完整链路。下一步,可以尝试将硬件初始化、网络配置、或自定义服务启动逻辑,封装进这个框架中。

--- > **获取更多AI镜像** > > 想探索更多AI镜像和应用场景?访问 [CSDN星图镜像广场](https://ai.csdn.net/?utm_source=mirror_blog_end),提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 14:29:56

mT5中文-base零样本增强模型GPU算力适配:7860端口服务低延迟部署

mT5中文-base零样本增强模型GPU算力适配:7860端口服务低延迟部署 1. 什么是mT5中文-base零样本增强模型 你可能遇到过这样的问题:手头只有一小段中文文本,想快速生成语义一致但表达多样的多个版本,却苦于没有标注数据、没有训练…

作者头像 李华
网站建设 2026/4/23 11:11:39

Qwen3-Embedding-0.6B在电商搜索中的实际应用案例

Qwen3-Embedding-0.6B在电商搜索中的实际应用案例 1. 为什么电商搜索需要更聪明的嵌入模型? 你有没有遇到过这样的情况:在电商平台搜“轻薄长续航笔记本”,结果首页跳出一堆游戏本、二手翻新机,甚至还有键盘膜?或者搜…

作者头像 李华
网站建设 2026/4/18 7:00:26

AI应用架构师实战:虚拟展览中的3D重建技术应用

AI应用架构师实战:虚拟展览中的3D重建技术应用 1. 引入与连接 1.1 引人入胜的开场 想象一下,你身处一个古老的博物馆,想要欣赏一件珍贵的文物。然而,这件文物由于年代久远,保存状况不佳,无法在现实中完美地…

作者头像 李华
网站建设 2026/4/19 1:04:45

VibeVoice Pro从零开始:基于CUDA 12+PyTorch 2.1的流式语音引擎搭建

VibeVoice Pro从零开始:基于CUDA 12PyTorch 2.1的流式语音引擎搭建 1. 为什么你需要一个“会呼吸”的语音引擎? 你有没有遇到过这样的场景:在做实时客服对话系统时,用户刚说完问题,AI却要等两秒才开口?或…

作者头像 李华
网站建设 2026/4/18 9:45:39

Clawdbot+Qwen3:32B部署教程:Web端WebSocket长连接与心跳保活配置

ClawdbotQwen3:32B部署教程:Web端WebSocket长连接与心跳保活配置 1. 为什么需要WebSocket长连接与心跳保活 你有没有遇到过这样的情况:网页聊天界面突然卡住,发送消息没反应,刷新页面后对话历史全没了?或者模型响应中…

作者头像 李华