news 2026/4/20 11:09:49

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/4/19 15:26:49

SDXL-Turbo实战教程:如何用标点/空格触发画面微调而非重绘

SDXL-Turbo实战教程&#xff1a;如何用标点/空格触发画面微调而非重绘 1. 为什么这个“打字即出图”的工具值得你停下来看一眼 你有没有试过在AI绘画工具里输入一段提示词&#xff0c;然后盯着进度条等上十几秒&#xff0c;结果生成的图和你脑中想的差了一截&#xff1f;再改…

作者头像 李华
网站建设 2026/4/17 15:42:46

UNet人脸融合重启方法,run.sh脚本再执行

UNet人脸融合重启方法&#xff1a;run.sh脚本再执行详解与工程化实践 关键词&#xff1a; UNet人脸融合、Face Fusion WebUI、run.sh重启脚本、ModelScope人脸合成、二次开发部署、科哥镜像、本地Web服务恢复、人脸特征迁移、融合比例调控、图像质量调优 摘要&#xff1a; 在…

作者头像 李华
网站建设 2026/4/17 19:24:08

小白必看:全任务零样本学习-mT5中文增强版保姆级教程

小白必看&#xff1a;全任务零样本学习-mT5中文增强版保姆级教程 1. 这不是另一个“调参工具”&#xff0c;而是一个会自己思考的中文文本增强助手 你有没有遇到过这些情况&#xff1f; 写产品文案时卡在第一句&#xff0c;反复删改还是不满意&#xff1b;做用户调研要扩写1…

作者头像 李华
网站建设 2026/4/16 11:09:16

对比测试:Qwen-Image-Layered vs 传统重绘谁更强?

对比测试&#xff1a;Qwen-Image-Layered vs 传统重绘谁更强&#xff1f; 在AI图像编辑的实际工作中&#xff0c;你是否也遇到过这样的困扰&#xff1a;想只把图中那件蓝色T恤换成红色&#xff0c;结果人物手臂变形、背景出现奇怪色块&#xff1b;想给产品图换个背景&#xff…

作者头像 李华
网站建设 2026/4/17 23:37:38

SenseVoice Small语音转文字:5分钟搭建极速听写工具

SenseVoice Small语音转文字&#xff1a;5分钟搭建极速听写工具 1. 为什么你需要一个“开箱即用”的语音转写工具&#xff1f; 1.1 听写这件事&#xff0c;真的没你想得那么简单 你有没有过这些时刻&#xff1f; 会议录音堆了十几条&#xff0c;却一直没时间整理&#xff1b…

作者头像 李华