news 2026/6/15 13:08:47

Android开机启动shell脚本踩坑总结,这些错误别再犯

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android开机启动shell脚本踩坑总结,这些错误别再犯

Android开机启动shell脚本踩坑总结,这些错误别再犯

在Android系统定制开发中,让自定义shell脚本随系统开机自动运行是常见需求——比如初始化硬件参数、配置网络环境、启动后台守护进程等。但看似简单的“写个脚本+加到init.rc”流程,实际落地时却布满隐蔽陷阱:脚本不执行、权限被拒、SELinux拦截、路径失效、执行时机错乱……轻则功能缺失,重则导致系统卡在开机动画甚至无法进入桌面。

本文不是照搬官方文档的理论复述,而是基于真实项目(MTK平台Android 8.0)反复调试、抓取logcat和dmesg、逐行比对sepolicy规则后沉淀出的实操避坑指南。所有内容均经过真机验证,覆盖从脚本编写、SELinux适配、init.rc集成到问题定位的完整链路。你将看到的不是“应该怎么做”,而是“为什么这里会挂”“log里哪一行暴露了真相”“改完这行代码后adb shell里立刻能验证”。

如果你曾遇到过“脚本手动执行OK,一放进开机就静默失败”“setprop没生效”“init.rc语法没错但服务根本没起来”这类问题——这篇文章就是为你写的。

1. 脚本本身:第一道关,90%的人栽在这里

很多人以为shell脚本只要语法正确就能跑,但在Android init上下文中,解释器路径、执行权限、环境变量、输出重定向全部是雷区。下面这些细节,错一个,脚本就彻底静默。

1.1 解释器路径必须精确匹配系统实际路径

Android的init进程调用脚本时,不会读取shebang(#!)后面的路径并自动查找解释器。它只认你写死在service声明里的路径,而脚本内部的#!/xxx/sh只是给开发者看的提示,init根本不解析它。

  • 正确做法:脚本第一行必须是#!/system/bin/sh#!/system/xbin/sh(取决于你的系统是否启用busybox),且这个路径必须与init.rcservice命令指定的路径完全一致。
  • ❌ 常见错误:
    • 写成#!/bin/sh(Linux标准路径,Android里不存在)
    • 写成#!/system/bin/bash(Android默认不带bash,即使有也未被SELinux策略允许)
    • init.rc里写/system/bin/init.test.sh,但脚本里写#!/system/xbin/sh(路径不一致导致init找不到解释器)

验证方法:在adb shell中执行ls -l /system/bin/sh /system/xbin/sh,确认哪个真实存在且可执行;再用readelf -a /system/bin/sh | grep interpreter确认其动态链接器路径,避免因ABI不兼容导致崩溃。

1.2 执行权限和文件位置有硬性约束

init进程以root身份运行,但它只信任特定目录下的可执行文件,且对文件权限极其敏感:

  • 推荐存放路径:/system/bin//vendor/bin/(需对应添加file_contexts规则)

  • ❌ 绝对禁止路径:

    • /data/local/tmp/(init启动时/data可能还未挂载或处于加密状态)
    • /sdcard/(FUSE挂载,init无权限访问)
    • /system/etc/(通常只有读权限,且init不从此处加载可执行文件)
  • 权限必须为-rwxr-xr-x(755),且属主属组为root:root

  • ❌ 常见错误:chmod 777 init.test.sh(过于宽松,SELinux可能拒绝执行)或chown system:system init.test.sh(init以root运行,非root属主会被拒绝)

快速验证:adb shell ls -l /system/bin/init.test.sh,确保输出形如-rwxr-xr-x 1 root root 234 ...

1.3 脚本内避免依赖外部命令和复杂逻辑

init进程启动阶段,系统服务尚未就绪,很多常用命令不可用或行为异常:

  • ❌ 禁止使用:curl,wget,ping,netstat,ps,top(busybox未完全初始化)

  • ❌ 避免长耗时操作:sleep 10会导致init阻塞,影响其他服务启动

  • 安全操作:setprop,getprop,log,echo > /dev/kmsg,mkdir -p,touch

  • 关键提醒:不要在脚本里创建新文件或写入日志到/data/。此时/data分区可能处于encryptingmounted中间态,写入会失败且无错误提示。建议统一用log -t TEST "message"输出到kernel log,再用dmesg | grep TEST查看。

2. SELinux策略:看不见的墙,80%的失败根源

Android 8.0起强制启用SELinux,且init相关服务默认处于enforcing模式。即使你临时setenforce 0,脚本仍可能因缺少file_contexts规则而无法被init识别为合法可执行文件。

2.1 file_contexts规则必须精准匹配路径和正则

这是最容易被忽略却最致命的一环。file_contexts文件定义了每个文件路径对应的SELinux类型,init在加载脚本前会严格校验。

  • 正确规则(以/system/bin/init.test.sh为例):
/system/bin/init\.test\.sh u:object_r:test_service_exec:s0
  • ❌ 致命错误:
    • 写成/system/bin/init.test.sh(缺少转义符.,正则匹配失败)
    • 写成/system/bin/.*\.sh(过于宽泛,违反最小权限原则,编译可能报错)
    • 路径与init.rc中service路径不一致(如rc里写/system/bin/init.test.sh,但file_contexts写/vendor/bin/init.test.sh

验证方法:编译后检查out/target/product/xxx/root/file_contexts是否包含你添加的行;启动后执行adb shell ls -Z /system/bin/init.test.sh,确认输出中u:object_r:test_service_exec:s0已生效。

2.2 te策略要覆盖完整的domain transition链

仅仅定义test_service_exec类型还不够。init进程需要从initdomain切换到你的test_servicedomain才能执行脚本,这需要三重策略:

  1. 声明domain和typetest_service.te):

    type test_service, domain; type test_service_exec, exec_type, vendor_file_type;
  2. 允许init启动该domain(关键!):

    # 必须添加,否则init无法fork出test_service进程 allow init test_service_exec:file { read execute open getattr }; # 允许init进行domain transition allow init test_service:process { transition };
  3. 定义domain能力test_service.te):

    # 让test_service能设置属性(否则setprop失败) allow test_service system_file:file { read open getattr }; allow test_service system_prop:property_service { set }; # 允许写log(调试必需) allow test_service kernel_debug:file { write };

注意:init_daemon_domain(test_service)宏仅适用于init启动的daemon服务,不适用于oneshot脚本。直接使用该宏会导致策略缺失,必须手写上述三条核心规则。

3. init.rc集成:语法正确 ≠ 功能正常

init.rc是Android的启动蓝图,但它的语法和执行逻辑与普通shell差异巨大。一个空格、一个缩进、一个单词拼错,都会让服务消失于无形。

3.1 service声明的字段顺序和值有严格要求

以下是最小可用模板,任何字段缺失或顺序错乱都会导致服务不被加载:

service test_service /system/bin/init.test.sh class main user root group root oneshot seclabel u:object_r:test_service_exec:s0
  • 必填字段(缺一不可):

  • class:必须与on property:触发条件或start <class>命令匹配,main类在早期启动

  • user/group:必须为root,否则无权执行系统级操作

  • oneshot:表示执行完即退出(适合初始化脚本);若省略,init会持续监控进程,脚本退出后立即重启,造成循环

  • seclabel:必须与file_contexts中定义的type完全一致

  • ❌ 常见错误:

    • user system(权限不足,无法setprop)
    • 缺少oneshot(脚本退出后init不断重启,log刷屏)
    • seclabel写成u:object_r:test_service:s0(类型名错误,应为test_service_exec

3.2 启动时机必须显式控制

Android 8.0引入on property:触发机制,比简单放在class main里更可靠:

on property:sys.boot_completed=1 start test_service
  • 优势:确保系统基本服务(如property service)已就绪,setprop命令可用
  • ❌ 风险:若脚本依赖/data分区,需改用on property:ro.crypto.state=unencrypted(解密完成后)

验证服务状态:adb shell getprop | grep test(检查属性是否设置成功);adb shell ps | grep test_service(确认进程是否存在)。

4. 问题定位:不靠猜,靠log精准打击

当脚本不执行时,90%的人第一反应是“改代码”,但真正的问题往往藏在log里。以下是高效排障路径:

4.1 三步锁定问题层级

步骤命令判定依据问题层级
1. 检查init是否加载服务adb shell cat /proc/1/cmdline输出含init.rc路径init解析层
2. 检查服务是否在initctl列表adb shell su -c 'initctl list' | grep test_service有输出且状态为stoppedinit服务注册层
3. 检查SELinux拒绝日志adb shell dmesg | grep avc出现avc: denied { execute } for path=/system/bin/init.test.shSELinux策略层

4.2 关键log解读速查表

  • init: cannot find '/system/bin/init.test.sh'→ file_contexts未生效或路径错误
  • init: starting service 'test_service'...+ 无后续日志 → 脚本执行瞬间崩溃(检查shebang路径或语法错误)
  • avc: denied { execute } for comm="init" name="init.test.sh"→ file_contexts规则缺失或类型名错误
  • avc: denied { set } for property="test.prop"→ te策略缺少allow test_service system_prop:property_service { set };
  • test_service: not foundinitctl list无此服务,init.rc语法错误或未编译进镜像

实用技巧:在脚本开头加入log -t TEST "STARTED at $(date)",结尾加log -t TEST "ENDED"。若只看到STARTED,说明脚本在中间某行崩溃,结合dmesg定位具体错误。

5. 工程化建议:让开机脚本真正可靠

最后分享几个经项目验证的工程实践,帮你把“能跑”升级为“稳跑”:

5.1 使用on property:替代class main启动

# 在init.rc中 on property:sys.boot_completed=1 start test_service # 脚本内增加超时保护 #!/system/bin/sh timeout=30 while [ $timeout -gt 0 ]; do if getprop sys.boot_completed | grep -q 1; then break fi sleep 1 timeout=$((timeout - 1)) done setprop test.prop 111

5.2 用logwrapper包装脚本,捕获stderr

service test_service /system/bin/logwrapper /system/bin/sh /system/bin/init.test.sh

这样脚本内所有echo和错误都会输出到logcat,无需手动log -t

5.3 构建时自动校验,防低级错误

Android.mk中添加检查规则:

$(warning Checking init.test.sh permissions...) $(shell chmod 755 $(LOCAL_PATH)/init.test.sh) $(shell chown root:root $(LOCAL_PATH)/init.test.sh)

获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/14 19:10:52

Qwen3-TTS-Tokenizer-12Hz环境部署:开箱即用镜像免配置快速上手指南

Qwen3-TTS-Tokenizer-12Hz环境部署&#xff1a;开箱即用镜像免配置快速上手指南 你是不是也遇到过这样的问题&#xff1a;想试试最新的音频编解码模型&#xff0c;但光是装依赖、配环境、下载权重就卡了一整天&#xff1f;更别说CUDA版本冲突、PyTorch编译失败、tokenizers报错…

作者头像 李华
网站建设 2026/6/14 18:17:25

Qwen-Image-Edit GPU算力优化实战:显存占用降低50%,推理速度提升3倍

Qwen-Image-Edit GPU算力优化实战&#xff1a;显存占用降低50%&#xff0c;推理速度提升3倍 1. 本地极速图像编辑系统&#xff1a;一句话修图的落地可能 你有没有试过为一张产品图换背景&#xff0c;结果等了两分钟&#xff0c;显卡风扇狂转&#xff0c;最后还弹出“CUDA out…

作者头像 李华
网站建设 2026/6/9 18:36:38

用Qwen-Image-Edit-2511做电商海报,工业设计生成太省心

用Qwen-Image-Edit-2511做电商海报&#xff0c;工业设计生成太省心 你有没有为一张主图反复改稿到凌晨&#xff1f; 上传商品图后&#xff0c;换十次背景、调八遍光影、修五版文字排版&#xff0c;最后发现标题字体在手机端根本看不清…… 更别提工业类产品——机械结构要精准…

作者头像 李华
网站建设 2026/6/6 13:32:11

StructBERT在短视频推荐中的应用:标题语义相似度驱动去重与分发

StructBERT在短视频推荐中的应用&#xff1a;标题语义相似度驱动去重与分发 1. 为什么短视频平台急需“真正懂中文标题”的去重系统 你有没有刷到过这样的情况&#xff1a;同一支口红的五条视频&#xff0c;标题分别是“绝美玫瑰金唇釉”“这支唇釉涂上像在发光”“被问了八百…

作者头像 李华
网站建设 2026/5/30 5:39:24

Qwen3-Embedding-4B效果展示:前50维向量数值分布柱状图动态可视化

Qwen3-Embedding-4B效果展示&#xff1a;前50维向量数值分布柱状图动态可视化 1. 什么是Qwen3-Embedding-4B&#xff1f;语义搜索的底层“翻译官” 你有没有试过在文档里搜“苹果”&#xff0c;结果只找到带“苹果”字样的句子&#xff0c;却漏掉了“iPhone电池续航差”或“M…

作者头像 李华
网站建设 2026/6/10 16:20:53

告别繁琐配置!用Z-Image-Turbo快速搭建AI绘画系统

告别繁琐配置&#xff01;用Z-Image-Turbo快速搭建AI绘画系统 你有没有过这样的经历&#xff1a;花两小时配环境&#xff0c;结果卡在CUDA版本不兼容&#xff1b;下载模型权重等了40分钟&#xff0c;最后发现显存不够直接OOM&#xff1b;好不容易跑起来&#xff0c;WebUI界面卡…

作者头像 李华