news 2026/4/28 22:07:42

如何正确编写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/23 15:49:00

GLM-4-9B-Chat-1M保姆级教程:模型权重校验+SHA256完整性验证

GLM-4-9B-Chat-1M保姆级教程:模型权重校验SHA256完整性验证 1. 为什么校验模型权重这件事不能跳过? 你花两小时下载完 GLM-4-9B-Chat-1M 的模型权重,解压、配置环境、启动 Streamlit,结果一问就崩,或者回答明显胡说八…

作者头像 李华
网站建设 2026/4/28 15:04:28

ClawdBot惊艳案例:手写笔记图片→PDF+多语种翻译一体化生成

ClawdBot惊艳案例:手写笔记图片→PDF多语种翻译一体化生成 你有没有过这样的经历:会议结束,满纸潦草笔记;课堂下课,拍了一堆模糊的手写板书;出差归来,零散的便签贴满笔记本——可这些内容&…

作者头像 李华
网站建设 2026/4/28 16:29:37

ccmusic-database算力优化部署:VGG19_BN+CQT模型TensorRT加速实践指南

ccmusic-database算力优化部署:VGG19_BNCQT模型TensorRT加速实践指南 1. 为什么需要对音乐流派分类模型做TensorRT加速 你有没有试过在本地跑一个466MB的VGG19_BN模型?打开网页界面,上传一首30秒的音频,等上5到8秒才看到结果——…

作者头像 李华
网站建设 2026/4/20 15:08:58

轻量型服务器和云服务器的区别

轻量型服务器与云服务器(CVM)的核心差异,本质是“简化易用”与“灵活专业”的定位区分,二者在适用场景、配置弹性、运维难度等维度差异显著,具体区别如下: 轻量型服务器主打“极简运维、开箱即用”&#…

作者头像 李华
网站建设 2026/4/19 23:49:52

GLM-4-9B-Chat-1M开发者案例:API集成实现智能搜索

GLM-4-9B-Chat-1M开发者案例:API集成实现智能搜索 1. 为什么你需要一个“能读完200万字”的搜索助手? 你有没有遇到过这样的场景: 法务同事发来一份87页的并购协议PDF,要求30分钟内找出所有违约责任条款;运营团队甩…

作者头像 李华