简介IM安卓开发工具箱imakit 9.13面向Android ROM开发者、刷机包制作者与系统定制爱好者聚焦img镜像备份、刷机包制作、脚本自动化及img/dat/br格式互转等任务可用于设备系统调整前的状态备份、批量处理与跨设备包格式适配。压缩包共283个文件约18.26MB包含h头文件、exe可执行工具、pyd扩展模块、c源码、dll库、py脚本及dat数据文件等既有可直接调用的工具程序也保留了底层源码与脚本接口便于理解流程和二次改造。该资源已有4222人学习下载适合需要一套完整工具链来完成ROM备份、刷机包生成与格式转换的开发者参考也可作为排查镜像处理和脚本执行问题的实践素材。1. IM安卓开发工具箱到底装了什么从一份 ROM 备份说起手里这台机器刷完第三方安卓 ROM 后指纹失灵、基带丢失售后要等两周而我只有一个下午。翻出放在硬盘角落的 IM安卓开发工具箱才把 boot、vendor、super 三个分区从半砖状态里捞回来——那一刻起我对ROM 备份这四个字的理解从复制个文件夹变成了要先知道自己在备份哪块物理区块。这套工具箱的定位很明确把安卓 ROM 的拆、备、改、回四条链路塞进一个可离线运行的目录适合需要反复刷机验证的安卓端开发者以及维护多台测试机的同学。它不是一键救砖软件更像一把手术刀用之前必须清楚分区表和槽位。2. 环境与分区基础为什么 ROM 备份不是复制个文件夹很多人第一次做 ROM 备份会打开文件管理器把/sdcard拖到电脑上然后声称我备份好了。等真出事才发现系统镜像住在/dev/block/by-name/下面跟用户可见的存储完全是两套东西。这套工具箱的第一个门槛就在这里它要求你对块设备、分区表、动态分区有基本概念否则后面的 dd 命令跑起来只是在制造更大的麻烦。2.1 工具链跑起来的最低环境工具箱解压后目录大致分三块宿主机侧的命令行工具adb、fastboot、镜像解包器、设备侧的临时脚本、以及一份记录分区布局的配置模板。宿主机这套东西对环境有硬性要求版本太旧会出现协议不兼容比如老版本 fastboot 刷动态分区时会报Sending sparse super failed这类错误。常见做法是固定一套版本组合不要跟着系统包管理器升级组件建议版本口径用途版本不匹配的典型症状adb / fastboot与设备 SDK 同代设备通信、分区刷写传输大文件中途断开、稀疏镜像识别失败镜像解包器支持当前 boot 头格式拆 boot.img / vendor_boot.img解包后 kernel 段为空payload 提取脚本支持全量 OTA 元数据从 OTA 包取分区镜像报不支持的 op 类型lpunpack / lpmake与设备动态分区版本一致拆合 super.img回包后设备不进系统校验工具系统自带 sha256sum备份完整性核对还原后校验失败但刷写成功装完之后先做连通性验证这一步别省# 确认设备被识别且处于正常系统而非 fastboot 模式 adb devices -l # 查看设备分区布局确认 by-name 映射存在 adb shell ls -l /dev/block/by-name/ | head -n 20 # 确认是否 A/B 双槽设备输出 a 或 b 才是双槽 adb shell getprop ro.boot.slot_suffix第一行adb devices -l输出里如果只有一串序列号加device状态说明授权已经通过出现unauthorized就在设备屏幕上点允许出现offline一般是数据线或驱动问题换根线比反复重装驱动有效。第二行的by-name目录是整篇文章的地基安卓从 8.0 之后越来越多设备依赖它做分区寻址/dev/block/by-name/boot这类路径本质是软链指向真实的mmcblk0pXX或sdaXX。第三行的slot_suffix决定了你后面所有命令要不要带_a/_b后缀带错槽位是还原失败的头号原因。需要提醒的是工具箱里的设备侧脚本依赖 root 权限。没有 root 的设备只能做读取类操作凡是涉及dd写块设备、挂载super分区、修改vbmeta的步骤都会失败而且失败信息往往很含糊只报Permission denied而不说缺什么。我一般会先跑一条最小验证# 验证 root 是否真的可用不是 su 存在而是 su 能提权成功 adb shell su -c id # 期望输出包含 uid0(root)如果这条命令卡住或者返回普通用户 uid后面的备份流程直接停别硬试。2.2 分区表怎么读从 /proc/partitions 到 by-name 映射理解分区表是这套工具箱最值钱的部分。安卓设备的存储布局分三层最底层是 eMMC/UFS 的物理块设备中间是 GPT 分区表最上层是 by-name 软链和动态分区。备份出错十有八九是搞错了层。先看物理层# 列出所有块设备与分区单位是 1K 块 adb shell su -c cat /proc/partitions # 看某个分区的精确字节数比 /proc/partitions 更准 adb shell su -c blockdev --getsize64 /dev/block/by-name/boot_a/proc/partitions的输出里major和minor是内核设备号#blocks是 1K 块数量最后一列才是名字。真正有用的是把名字和 by-name 对上因为不同厂商给同一功能分区起的物理名完全不同只有 by-name 这层是标准化的。第二行的blockdev --getsize64返回字节数这个数字要记下来它是后面校验备份是否完整的基准——如果 dd 出来的文件比它小说明备份被截断了。再往下是槽位和分区属性的判断# 查看当前活动槽位与是否支持无缝更新 adb shell getprop ro.boot.slot_suffix adb shell getprop ro.build.ab_update # 查看 dm-verity / AVB 状态决定了你能不能直接改分区内容 adb shell getprop ro.boot.veritymode adb shell getprop ro.boot.vbmeta.device_statero.build.ab_update为true说明是 A/B 双槽备份时两个槽的 boot、dtbo、vbmeta 都要各存一份不能只存当前槽。ro.boot.veritymode通常是enforcing意思是 dm-verity 在强制校验你往 system 分区里塞任何东西都会导致校验失败进而无法开机这时候要么改vbmeta关掉校验要么走修补 boot 的路子绕开。这套判断逻辑在工具箱的配置模板里有一份默认值但我不建议直接信模板设备型号一换就得重读。2.3 动态分区与 super.img备份对象到底是谁从安卓 10 开始system、vendor、product 这些分区不再各自占用独立的物理分区而是被打包进一个叫super的大分区里内部用 LP 元数据切分。这是 ROM 备份最容易翻车的地方你在 by-name 里看到的system_a其实是个逻辑分区直接 dd 它可能拿到空数据因为真实数据在super里面。判断方法很直接# 看 system 是不是独立块设备还是 dm 映射出来的逻辑卷 adb shell su -c ls -l /dev/block/by-name/system_a # 如果指向 /dev/block/dm-N说明是动态分区内的逻辑卷 # 查看 super 分区本身的大小 adb shell su -c blockdev --getsize64 /dev/block/by-name/super第一行如果软链指向/dev/block/dm-0这类设备就确认了它是 device-mapper 映射出来的逻辑卷备份它等于备份一个正在被内核管理的内存视图数据一致性没有保证。正确的备份对象是整个super分区。第二行返回的字节数通常在两到四 GB 之间具体取决于设备这个值在回包重建时必须原样对上否则fastboot flash super会直接报空间不足。理解了这三点工具箱后面的所有操作才有意义。我见过太多人拿着 dd 命令乱跑备份出来一堆十几 KB 的废文件还以为是自己命令写错了其实是备份了一个被 dm 映射的空壳。3. 备份与还原的实操链路adb、dd 与镜像校验这套工具箱真正高频使用的功能是备份和还原。它的设计思路是把每个分区当成一个独立的二进制对象来处理备份产出的是裸镜像文件还原时按分区原样写回。这种做法的好处是可复现、可校验坏处是对操作精度要求极高——写错分区名、写错槽位、漏掉元数据任何一个都会让设备停在开机动画。3.1 全量分区备份dd 命令与块大小参数先建目录再备份别把文件堆在/sdcard根目录后面拉取的时候会很痛苦。# 在设备上建立备份目录 adb shell su -c mkdir -p /sdcard/rombackup # 备份 boot 分区单槽设备 adb shell su -c dd if/dev/block/by-name/boot of/sdcard/rombackup/boot.img bs1048576 convfsync # 备份 boot 分区双槽设备两个槽都要 adb shell su -c dd if/dev/block/by-name/boot_a of/sdcard/rombackup/boot_a.img bs1048576 convfsync adb shell su -c dd if/dev/block/by-name/boot_b of/sdcard/rombackup/boot_b.img bs1048576 convfsyncif是输入of是输出bs1048576表示每次读写 1 MB这个值不能随便设。设太小比如默认 512 字节会导致备份一个几十 MB 的 boot 分区要跑几分钟设太大比如 64 MB在小内存设备上可能触发 OOM 被系统杀掉表现为 dd 进程无声消失、文件只有几百 KB。1 MB 到 4 MB 之间是比较稳的区间。convfsync是关键参数它要求 dd 在结束前把数据真正刷到磁盘没有这个参数的话命令返回成功但文件可能还在页缓存里你立刻adb pull会拿到一个不完整的镜像。这个坑我踩过一次备份的 super.img 比实际小了 200 MB还原时才发现。super 分区和 userdata 分区的备份单独说因为它们体积大# 备份 super 分区耗时较长建议前台执行并观察进度 adb shell su -c dd if/dev/block/by-name/super of/sdcard/rombackup/super.img bs4194304 convfsync # 备份 metadata 分区FBE 加密元数据换机还原必需 adb shell su -c dd if/dev/block/by-name/metadata of/sdcard/rombackup/metadata.img bs1048576 convfsyncmetadata分区经常被忽略它存的是文件级加密FBE的密钥材料。如果你备份了 userdata 却没备份 metadata还原到同一台设备上大概率还能开机但一旦涉及恢复出厂或者跨设备数据就永远解不开了。这个分区的体积通常只有几 MB备份成本极低没有理由不存。拉回本地# 一次性拉取整个备份目录-p 显示进度 adb pull /sdcard/rombackup ./rombackup_local3.2 分区镜像的完整性校验与元数据记录备份完不校验等于没备份。工具箱里带了一份校验脚本的思路核心是两头比对设备侧算一次哈希本地算一次哈希两者必须一致。# 设备侧对备份文件算 sha256 adb shell su -c sha256sum /sdcard/rombackup/*.img # 拉回本地后再算一次两份输出逐行比对 sha256sum ./rombackup_local/*.img注意这里是对备份出来的文件算哈希不是对块设备算。对块设备算哈希会读取整个物理分区包括未使用区域速度慢且没有必要因为未使用区域的内容在不同次读取之间可能不一致。对文件算就能保证我拉回来的这份字节流和设备上生成的那份字节流完全相同。光有哈希还不够分区大小和槽位信息必须一起记录否则半年后你打开这个目录根本想不起来这是哪台机器的# 生成一份备份清单记录每个分区的字节数与来源设备 adb shell su -c for p in boot_a boot_b super metadata vbmeta_a vbmeta_b; do size$(blockdev --getsize64 /dev/block/by-name/$p 2/dev/null) echo $p $size done ./rombackup_local/partition_sizes.txt # 记录设备指纹与系统版本 adb shell getprop ro.product.device ./rombackup_local/device_info.txt adb shell getprop ro.build.display.id ./rombackup_local/device_info.txt adb shell getprop ro.boot.slot_suffix ./rombackup_local/device_info.txt这段脚本用blockdev拿每个分区的真实字节数写进一个纯文本清单。后面还原时拿备份文件大小和清单里的数字对一下差一个字节都说明备份过程出过问题。第二段把设备代号、系统版本号、当前槽位追加到信息文件里——设备代号尤其重要不同代号的分区布局可能完全不同拿 A 机型的 boot 刷到 B 机型上轻则不开机重则变砖。3.3 还原路径fastboot flash 与 dd 回写的边界还原有两条路走 fastboot 在 bootloader 层刷或者走 adb 在系统内 dd 回写。两者的适用场景完全不同混用会出问题。fastboot 路径适合刷 boot、dtbo、vbmeta 这类小分区# 进入 bootloader adb reboot bootloader # 确认设备连接正常 fastboot devices # 刷入 boot 分区注意槽位后缀 fastboot flash boot_a ./rombackup_local/boot_a.img fastboot flash boot_b ./rombackup_local/boot_b.img # vbmeta 必须跟着刷否则 AVB 校验不过 fastboot flash vbmeta_a ./rombackup_local/vbmeta_a.img fastboot --disable-verity --disable-verification flash vbmeta_a ./rombackup_local/vbmeta_a.imgfastboot flash后面的分区名要和设备实际分区名一致双槽设备不写后缀会默认刷到当前槽这个默认行为在还原场景里很危险——你以为两个槽都还原了实际上只动了一个。--disable-verity和--disable-verification这两个参数只在你改了分区内容时才需要纯粹的原样还原不要加加了等于主动关掉安全校验后续系统更新会失败。dd 回写路径适合 super 这类 fastboot 刷写容易超时的大分区# 先推回设备 adb push ./rombackup_local/super.img /sdcard/rombackup/super.img # 在系统内回写同样是 4MB 块大小 adb shell su -c dd if/sdcard/rombackup/super.img of/dev/block/by-name/super bs4194304 convfsync # 回写后立即校验比对源文件与目标分区前 N 字节 adb shell su -c dd if/dev/block/by-name/super bs4194304 count10 2/dev/null | sha256sumdd 回写的边界在于它绕过 bootloader 的所有保护写错了没有任何拦截。所以回写前必须做两件事一是确认目标分区大小不小于源文件二是确认当前不在 A/B 切换的中间状态。回写完成后那条校验命令只读前 10 个块是个快速抽检不是完整校验完整校验要把 count 去掉跑全量代价是时间翻倍。我一般对 super 做全量校验对 boot 这种小分区直接抽检。4. 镜像解包与重打包boot.img、payload.bin、super.img 的处理方法备份只是保底这套工具箱更高频的用法是改。改分区镜像的流程可以概括成解包、修改、重打包、刷入四步每一步都有对应的工具和格式约束。搞不清楚格式就动手最常见的结果是打包出来的镜像刷进去直接不进系统而且没有任何错误提示。4.1 boot.img 解包与修补流程boot.img 是安卓启动链的第一环它的结构从安卓 10 之后变化很大头部格式从 boot header v2 一路演进到 v4还多出了 vendor_boot 这个独立分区。工具箱里的解包器会自己判断头部版本但你要知道自己在处理哪一代格式。# 解包 boot.img输出目录里会出现 kernel、ramdisk、dtb 等组件 patcher unpack boot.img ./boot_out # 查看解包结果确认关键组件都在 ls -lh ./boot_out/ # 典型输出kernel、ramdisk.cpio、dtb、header.cfgpatcher这里代指工具箱里的修补命令行入口具体名字按你解压后的目录来替换。解包后重点看两样东西kernel的大小是否合理几 MB 到十几 MB 之间ramdisk.cpio是否存在。如果 kernel 是 0 字节说明头部版本判断错了换一个解包器版本重试。header.cfg里记录了原始头部参数重打包时必须原样带回去改一个字段都可能导致启动失败。修补 ramdisk 的典型目的是往里面塞一个提权组件流程是解 cpio、改内容、重新压# 解开 ramdisk mkdir ./ramdisk_root cd ./ramdisk_root cpio -idmv ../boot_out/ramdisk.cpio # 修改完成后重新打包注意保持 cpio 的格式与权限 find . | cpio -o -H newc ../boot_out/ramdisk_new.cpio # 用原头部参数重打包 patcher repack ./boot_out ./boot_new.imgcpio -H newc这个格式参数不能省安卓的 ramdisk 用的是 newc 格式用默认格式打出来的包内核认不出来。find . | cpio的顺序也有讲究文件权限和属主信息会一起被打进去如果你在解包目录里用普通用户身份改过文件打包后属主变成非 root启动时可能出现权限拒绝。稳妥的做法是全程在 root shell 里操作或者打包后用repack工具统一修正属主。重打包出来的镜像先别急着刷本地做个结构检查# 对比新旧镜像的头部信息确认关键字段一致 patcher info ./boot_new.img # 检查文件大小是否在合理范围通常与原始镜像相差不超过 20% ls -l ./boot.img ./boot_new.img4.2 payload.bin 的提取与 OTA 包拆解很多人的 ROM 备份来源是官方 OTA 包里面的分区镜像是打包在payload.bin里的不能直接当镜像用。工具箱带了一份提取脚本思路是解析 payload 的元数据把每个分区的数据段按哈希校验后单独写出来。# 从 OTA zip 里取出 payload.bin 和元数据 unzip ota_package.zip payload.bin payload_properties.txt -d ./ota/ # 提取全部分区镜像到输出目录 python payload_dumper.py ./ota/payload.bin --out ./ota/images # 只提取指定分区节省时间 python payload_dumper.py ./ota/payload.bin --out ./ota/images --partition boot,vbmeta第一条unzip只取两个文件因为 OTA 包里动辄几个 GB全解压没必要。第二条是核心提取命令正常输出会看到每个分区的名字、大小、以及一行校验通过的提示。第三条的--partition参数在只想拿 boot 做修补时非常有用能把提取时间从十几分钟压到几十秒。这里有个格式上的硬约束payload.bin分全量和增量两种。全量包里的每个分区都是完整镜像提取出来可以直接用增量包里的分区是差分的只包含与基准版本的差异数据提取脚本遇到增量操作类型会直接报错退出。判断方法看payload_properties.txt里有没有FILE_HASH之外的分区级差分标记或者看提取时的报错信息里是否出现不支持的 op 类型。遇到增量包不要硬解去找对应版本的全量包这是唯一干净的路子。提取完成后同样要做校验# 比对提取出的镜像哈希与 payload 元数据里记录的值 grep -E ^(boot|vbmeta) ./ota/payload_properties.txt sha256sum ./ota/images/boot.img4.3 super.img 的拆分与回包动态分区的设备上改完逻辑分区必须重新合成super.img再整体刷入。这一步是整条链路里参数最多、最容易错的地方因为lpmake需要你手动指定每个分区的大小、所属 group、以及元数据槽位数。先拆# 拆分 super.img输出目录里会得到 system.img、vendor.img 等逻辑分区 lpunpack --slot0 ./rombackup_local/super.img ./super_split/ # 查看拆分结果与每个分区的大小 ls -lh ./super_split/--slot0表示拆 A 槽双槽设备的 super 里同时存着两个槽的逻辑分区不指定 slot 会拆出带后缀的全部分区目录会很乱。拆分出来的文件名通常带_a后缀这是正常的。改完之后回包参数要一个字段一个字段对lpmake \ --device-size9126805504 \ --metadata-size65536 \ --metadata-slots2 \ --super-namesuper \ --groupmain_a:9126805504 \ --partitionsystem_a:readonly:3221225472:main_a:./super_split/system_a.img \ --partitionvendor_a:readonly:1073741824:main_a:./super_split/vendor_a.img \ --partitionproduct_a:readonly:1073741824:main_a:./super_split/product_a.img \ --output./super_new.img--device-size必须等于原super分区的字节数多一个字节刷不进去少一个字节后面的分区放不下。--metadata-size和--metadata-slots必须和原设备一致这两个值错了会报元数据校验失败。--group定义了一个分区组及其容量上限组名要和分区名的 group 字段对应容量上限一般等于--device-size。每个--partition的格式是名称:属性:大小:组名:镜像路径属性只有readonly和none两种系统分区必须是readonly。大小这一项要填镜像的实际大小向上对齐到 4096 字节填小了 lpmake 直接报错填大了浪费空间可能导致后面的分区分不到位置。回包完成后验证# 用 lpdump 查看新 super 的分区布局 lpdump ./super_new.img # 与原始 super 的布局做对比分区名、大小、group 必须一一对应 lpdump ./rombackup_local/super.img ./super_orig.txt lpdump ./super_new.img ./super_new.txt diff ./super_orig.txt ./super_new.txtlpdump的输出里会列出每个分区的名字、大小、group、以及 extents 位置。diff 出来如果只有哈希或时间戳类的字段不同说明布局是对的如果出现分区大小或 group 不一致别刷回去改参数。5. 避坑与排查五条血泪记录这套工具箱的文档写得比较克制很多错误场景没展开。下面五条是我在实际使用里反复撞上的每条都按现象、原因、解决的顺序记希望能帮你少走几步。第一条备份出来的镜像只有几百 KB但分区明明是几百 MB。现象是ls -l一看文件大小和blockdev --getsize64差了一个数量级。原因通常是 dd 进程跑到一半被系统杀掉了安卓的低内存管理对长时间占用 IO 的进程不友好尤其是 super 这种几 GB 的分区。另一个可能是of路径写在了/sdcard的某个被 FUSE 挂载的子目录下写入被中间层截断。解决办法是备份前先adb shell free -m看一眼可用内存把bs调到 4 MB 减少循环次数备份过程中别让设备息屏并且每备份完一个分区立刻用ls -l和blockdev的数字对一遍。第二条还原后卡在开机动画进 recovery 提示校验失败。现象是 fastboot 刷写全部成功没有任何报错重启后卡在厂商 logo 或者动画循环。原因基本是 AVB 校验没过你刷了修改过的 boot但 vbmeta 里记录的哈希还是旧的。还有一种情况是双槽设备上你只刷了当前槽设备回滚到另一槽时读到的是旧镜像。解决分两步先fastboot getvar current-slot确认当前槽再决定只刷该槽还是两槽都刷如果改过分区内容必须跟着刷 vbmeta 并带上--disable-verity --disable-verification。刷完先用fastboot reboot recovery看一眼能不能进 recovery能进说明 boot 链没问题再正常重启。第三条payload 提取脚本报不支持的 op 类型直接退出。现象是脚本跑到某个分区时抛异常提示未知操作类型前面的分区已经正常输出。原因是这个 OTA 包是增量包只包含与基准版本的差异数据不是完整镜像。很多人第一反应是找补丁工具强行合并但增量包需要完整的基准版本做底缺了基准照样合不出来。正确处理是去查这个系统版本的完整包重新下载不要在同一份增量包上反复折腾。判断增量包有个快办法看 OTA zip 里的payload_properties.txt如果分区条目明显少于全量包或者文件体积只有全量包的三分之一以下基本就是增量。第四条lpmake 回包报空间不足但镜像总大小明明小于 device-size。现象是lpmake执行到一半报Not enough space to allocate partition。原因是--group的容量上限设置和实际分区总和之间没留对齐余量LP 元数据会对每个分区做 4096 字节对齐几十个分区累加下来多占的几百 KB 就足以撑爆一个刚好卡满的 group。另一个常见原因是--metadata-size给得太小元数据槽位放不下所有分区条目。解决办法是把 group 容量设成--device-size减去 1 MB 的元数据余量--metadata-size直接用原设备的 lpdump 输出里的值不要自己估。第五条备份了 userdata还原后能开机但数据全部丢失。现象是刷回 userdata 镜像后系统正常启动但所有应用数据变成了初始状态等于做了次恢复出厂。原因是 FBE 加密的密钥材料存在metadata分区你只还原了加密后的数据块没还原解密所需的密钥。解决是把metadata分区一起备份、一起还原且必须还原到同一台设备上——密钥和设备的硬件密钥绑定跨设备还原必然解不开。这也是为什么我在第 3 章强调 metadata 体积小、没有理由不存。6. 把工具箱用成流水线脚本化校验与版本回滚的固定动作单次备份还原靠手敲命令能应付但如果你像我一样要在同一台设备上反复刷不同版本的 ROM手敲迟早会出错。我的做法是把整条链路脚本化核心是三个固定动作备份前记录分区清单、备份后做双端哈希比对、归档时写一份 manifest。下面这段脚本是我现在每次动手前必跑的把它放在工具箱根目录下用bash backup_all.sh调用。#!/usr/bin/env bash # 全量备份 校验 归档设备需已 root 且 adb 授权通过 set -euo pipefail STAMP$(date %Y%m%d_%H%M%S) DEVICE$(adb shell getprop ro.product.device | tr -d \r) SLOT$(adb shell getprop ro.boot.slot_suffix | tr -d \r) WORK./archive/${DEVICE}_${STAMP} mkdir -p $WORK echo device${DEVICE} slot${SLOT} stamp${STAMP} $WORK/manifest.txt # 需要备份的分区列表双槽设备自动展开 PARTITIONSboot dtbo vbmeta metadata super for p in $PARTITIONS; do # 有槽位后缀的分区优先按当前槽备super 单独处理 if [ $p super ]; then targetsuper else target${p}${SLOT} fi # 记录分区真实大小作为完整性基准 size$(adb shell su -c blockdev --getsize64 /dev/block/by-name/${target} | tr -d \r) echo ${target} ${size} $WORK/manifest.txt # 备份到设备侧临时目录4MB 块大小 fsync 刷盘 adb shell su -c dd if/dev/block/by-name/${target} of/sdcard/rombackup/${target}.img bs4194304 convfsync adb pull /sdcard/rombackup/${target}.img $WORK/ /dev/null done # 双端哈希比对任何一条不一致直接退出 adb shell su -c sha256sum /sdcard/rombackup/*.img | sed s#/sdcard/rombackup/## | sort $WORK/remote.sha256 (cd $WORK sha256sum *.img | sort local.sha256) diff $WORK/remote.sha256 $WORK/local.sha256 # 清掉设备侧临时文件避免占满存储 adb shell su -c rm -rf /sdcard/rombackup echo backup done - $WORKset -euo pipefail这三项一定要加。-e保证任何一条命令失败就停避免备份到一半继续往下跑生成半成品-u让未定义变量报错防止槽位变量为空时命令变成boot而不是boot_apipefail保证管道中任意环节失败都能被捕获。STAMP用时间戳做归档目录名是为了让同一台设备的多次备份彼此独立回滚时能精确挑版本而不是在一堆同名文件里猜。PARTITIONS这个列表是我根据自己的设备调整过的你可以按实际需要增减但metadata和vbmeta不建议删。循环里对super做了特殊处理因为 super 不带槽位后缀硬加_a会找不到设备。blockdev --getsize64的结果写进 manifest这行的作用是给半年后的自己留证据——还原时拿文件大小和这个数字对一比就知道备份有没有被截断。最后那段哈希比对是整个脚本的价值所在。设备侧算一遍、本地算一遍、diff 一遍任何一条不一致就退出绝不进入下一步。我在这上面吃过亏早期图省事跳过校验结果一个备份了三次的 super 镜像里两次都是截断的等到真要用的时候才发现全是废的。归档目录的结构建议固定下来方便回滚时按时间点找路径内容回滚时的用途archive/设备_时间/manifest.txt分区名与字节数校验还原目标大小archive/设备_时间/*.img裸分区镜像直接刷写或 dd 回写archive/设备_时间/local.sha256本地哈希清单还原后比对archive/设备_时间/remote.sha256设备侧哈希清单交叉验证备份来源回滚的动作也固定成两步先diffmanifest 里的分区大小和当前设备blockdev的输出确认设备没有被 OTA 改过分区布局再按镜像逐个刷写每刷一个立刻用sha256sum抽检。设备布局变了的情况下还原必失败这时候正确做法是重新备份当前状态再决定要不要回滚而不是硬刷旧镜像。这套流程跑顺之后单次全量备份加校验大概十分钟回滚一次五分钟以内。比起出问题后花两小时找救砖方案这十分钟花得非常值。从那以后我每次动分区之前都强制走一遍这个脚本不管多急校验那一步绝不跳过——备份这件事只有在你还不需要它的时候做才叫备份。希望帮到你。本文还有配套的精品资源点击获取