Recovery 升级脚本 updata-script 详解:10 个核心命令与 3 个实战案例

发布时间:2026/7/9 5:56:24
Recovery 升级脚本 updata-script 详解:10 个核心命令与 3 个实战案例 Recovery 升级脚本 updater-script 深度解析10 个核心命令与 3 个实战案例在 Android 系统定制开发领域掌握 Recovery 升级脚本的编写是每位工程师的必修课。作为系统升级过程中的指挥官updater-script 脚本通过一系列精心设计的命令控制着分区操作、文件权限设置、补丁应用等关键流程。本文将深入剖析 10 个最核心的脚本命令并通过 3 个典型场景的实战案例带您全面掌握这一关键技术。1. updater-script 脚本基础架构updater-script 是 Recovery 升级包中的核心控制脚本采用 Edify 脚本语言编写位于升级包的/META-INF/com/google/android/目录下。与普通的 shell 脚本不同它专为系统升级场景设计具有以下显著特点原子性操作每个命令执行结果都会严格验证失败立即终止升级流程进度反馈机制可通过 show_progress 命令实时反馈升级进度分区感知原生支持对 Android 特有分区结构system、vendor 等的操作安全验证在执行前会验证设备状态和升级包完整性典型脚本结构如下# 示例脚本框架 ui_print(开始系统升级); assert(getprop(ro.product.device) Pixel6); show_progress(0.1, 10); mount(ext4, EMMC, /dev/block/by-name/system, /system); package_extract_dir(system, /system); set_perm_recursive(0, 0, 0755, 0644, /system/app); show_progress(0.9, 30); unmount(/system); ui_print(升级完成);2. 10 个核心命令详解2.1 assert安全验证的守门员assert 命令是脚本的第一道防线用于验证各种前提条件是否满足。其典型应用场景包括# 验证设备型号 assert(getprop(ro.product.device) rk3588); # 验证系统版本 assert(less_than_int(20230101, getprop(ro.build.date.utc))); # 验证分区大小 assert(getprop(ro.boot.slot_suffix) _a || getprop(ro.boot.slot_suffix) _b);注意assert 失败会导致升级立即终止因此在条件设置上要兼顾安全性与兼容性2.2 apply_patch增量升级的核心引擎apply_patch 命令实现了高效的增量更新其参数结构复杂但功能强大apply_patch( /system/build.prop, # 目标文件路径 -, # 源文件路径-表示与目标相同 e3b0c44298fc1c14..., # 目标文件期望的SHA1值 1024, # 目标文件大小 a94a8fe5ccb19ba6..., # 源文件SHA1值 package_extract_file(patch/system/build.prop.p)); # 补丁文件关键参数对照表参数位置含义示例值1目标文件路径/system/build.prop2源文件路径- 或 /cache/saved.file3目标文件SHA140位十六进制字符串4目标文件大小字节数5源文件SHA140位十六进制字符串6补丁文件package_extract_file()返回值2.3 set_perm精细化的权限控制set_perm 命令可以精确设置文件权限其参数格式为set_perm(uid, gid, mode, file1, file2, ..., fileN);典型应用示例# 设置单个文件权限 set_perm(0, 2000, 0750, /system/bin/netd); # 批量设置权限 set_perm(0, 0, 0755, /system/bin/reboot, /system/bin/sh, /system/bin/pm);权限模式与Android系统的对应关系模式值含义典型应用0755所有者可读写执行其他用户可读执行系统可执行文件0644所有者可读写其他用户可读配置文件0750所有者可读写执行同组用户可执行系统服务可执行文件0700仅所有者可读写执行敏感数据文件2.4 package_extract升级包内容提取package_extract 系列命令用于从升级包中提取内容# 提取单个文件到指定位置 package_extract_file(boot.img, /tmp/boot.img); # 提取整个目录到系统分区 package_extract_dir(system, /system); # 提取到内存文件系统临时操作 package_extract_file(install/bin/backuptool.sh, /tmp/backuptool.sh);2.5 block_image_update分区镜像更新block_image_update 是 Android 7.0 后引入的高效分区更新命令block_image_update( /dev/block/by-name/system, # 分区设备节点 package_extract_file(system.transfer.list), # 传输列表文件 system.new.dat, # 新数据文件 system.patch.dat); # 补丁数据文件增量更新时使用传输列表文件格式解析版本号 # 当前固定为4 总块数 # 分区总块数 命令1 命令2 ...2.6 mount/umount文件系统操作mount 命令在升级过程中至关重要# 挂载ext4格式的system分区 mount(ext4, EMMC, /dev/block/by-name/system, /system); # 挂载vendor分区 mount(ext4, EMMC, /dev/block/by-name/vendor, /vendor); # 操作完成后卸载 umount(/system);2.7 delete清理冗余文件delete 命令用于移除废弃文件# 删除单个文件 delete(/system/app/DeprecatedApp.apk); # 使用通配符批量删除 delete_recursive(/data/dalvik-cache);2.8 symlink创建符号链接symlink 命令处理文件系统中的符号连接# 创建单个符号链接 symlink(/system/bin/sh, /system/bin/shell); # 批量创建符号链接 symlink(toolbox, /system/bin/cat, /system/bin/chmod, /system/bin/chown);2.9 run_program执行外部程序run_program 允许执行临时程序# 执行备份脚本 run_program(/tmp/backuptool.sh, backup); # 执行文件系统检查 run_program(/sbin/e2fsck, -fy, /dev/block/by-name/userdata);2.10 ifelse条件逻辑控制ifelse 实现脚本的条件分支# 根据AB分区状态执行不同操作 ifelse( getprop(ro.boot.slot_suffix) _a, ui_print(当前在A分区运行), ui_print(当前在B分区运行) ); # 条件更新 ifelse( file_exists(/vendor/lib/modules/wifi.ko), ui_print(保留现有wifi驱动), package_extract_file(wifi.ko, /vendor/lib/modules/wifi.ko) );3. 实战案例解析3.1 案例一系统应用静默更新场景需要在不影响用户数据的情况下更新预装系统应用ui_print(开始更新系统应用); show_progress(0.2, 10); # 挂载system分区为可读写 mount(ext4, EMMC, /dev/block/by-name/system, /system); # 删除旧版应用 delete_recursive(/system/app/OldEmail); delete(/system/priv-app/Calendar/Calendar.apk); # 安装新版应用 package_extract_dir(system/app/Email, /system/app/Email); package_extract_file(system/priv-app/Calendar.apk, /system/priv-app/Calendar/Calendar.apk); # 设置权限 set_perm_recursive(0, 0, 0755, 0644, /system/app/Email); set_perm(0, 0, 0644, /system/priv-app/Calendar/Calendar.apk); show_progress(0.8, 5); unmount(/system); ui_print(系统应用更新完成);关键点使用 delete_recursive 彻底清除旧应用保持应用原始安装路径不变精确设置目录和文件权限3.2 案例二动态分区调整场景在支持动态分区的设备上调整分区布局ui_print(开始调整分区布局); show_progress(0.1, 15); # 更新动态分区表 assert(update_dynamic_partitions( package_extract_file(dynamic_partitions_op_list))); # 更新system分区 block_image_update( /dev/block/by-name/system, package_extract_file(system.transfer.list), system.new.dat, system.patch.dat); # 更新vendor分区 block_image_update( /dev/block/by-name/vendor, package_extract_file(vendor.transfer.list), vendor.new.dat, vendor.patch.dat); show_progress(0.9, 30); ui_print(分区调整完成);动态分区操作文件示例# dynamic_partitions_op_list 内容 remove_group default add_group rockchip_dynamic_partitions 4294967296 add system rockchip_dynamic_partitions 2147483648 add vendor rockchip_dynamic_partitions 1073741824 add product rockchip_dynamic_partitions 5368709123.3 案例三条件差分升级场景根据当前系统版本选择不同的升级策略ui_print(验证系统版本); ifelse( # 检查是否为V1.0版本 apply_patch_check(/system/build.prop, a94a8fe5ccb19ba61c4c0873d391e987982fbbd3), # 如果是V1.0应用增量更新 ( ui_print(检测到V1.0系统执行增量更新); apply_patch( /system/build.prop, -, e3b0c44298fc1c149afbf4c8996fb92427ae41e4, 1024, a94a8fe5ccb19ba61c4c0873d391e987982fbbd3, package_extract_file(patch/system/build.prop.p)); ), # 否则执行全量更新 ( ui_print(执行全量更新); package_extract_file(system.new.dat.br, /tmp/system.new.dat.br); run_program(/system/bin/brotli, -d, /tmp/system.new.dat.br); block_image_update( /dev/block/by-name/system, package_extract_file(system.transfer.list), /tmp/system.new.dat, ); ) );版本检查技巧对关键系统文件做SHA1校验使用 getprop 获取系统属性检查特定文件是否存在或包含特定内容4. 高级技巧与调试方法4.1 错误处理策略完善的错误处理机制能显著提升升级成功率# 尝试挂载分区失败后尝试修复 ifelse( mount(ext4, EMMC, /dev/block/by-name/system, /system), ui_print(成功挂载system分区), ( ui_print(挂载失败尝试修复...); run_program(/sbin/e2fsck, -fy, /dev/block/by-name/system); mount(ext4, EMMC, /dev/block/by-name/system, /system) || abort(无法挂载system分区); ) );4.2 进度反馈优化合理的进度反馈能提升用户体验show_progress(0.05, 5); # 准备阶段5% # ...前置操作... show_progress(0.3, 15); # 写入system分区30% block_image_update(...); show_progress(0.8, 10); # 写入vendor分区80% block_image_update(...); show_progress(1.0, 5); # 收尾工作100%4.3 日志记录技巧# 记录操作日志到临时文件 run_program(/system/bin/sh, -c, echo 开始升级 /tmp/upgrade.log); run_program(/system/bin/sh, -c, date /tmp/upgrade.log); # 重要操作后记录状态 package_extract_file(system.img, /tmp/system.img) run_program(/system/bin/sh, -c, echo 提取system.img成功 /tmp/upgrade.log) || run_program(/system/bin/sh, -c, echo 提取system.img失败 /tmp/upgrade.log);5. 安全规范与最佳实践签名验证确保脚本只在经过验证的升级包中执行assert(verify_signature());双重验证关键操作前验证设备状态assert(getprop(ro.secure) 1); assert(getprop(ro.debuggable) 0);资源清理确保临时文件被正确清理delete(/tmp/tempfile); delete_recursive(/tmp/working_dir);回滚机制关键操作前备份重要数据run_program(/system/bin/bu, backup, /system/etc/apns-conf.xml);性能优化大文件操作使用流式处理package_extract_file(large.dat, /tmp/large.dat); run_program(/system/bin/dd, if/tmp/large.dat, of/dev/block/by-name/userdata); delete(/tmp/large.dat);掌握 updater-script 脚本编程是 Android 系统开发的进阶技能通过本文介绍的 10 个核心命令和 3 个实战案例开发者可以构建出稳定可靠的系统升级方案。在实际项目中建议结合具体硬件平台特性进行针对性优化并建立完善的测试验证流程确保升级过程万无一失。