4、全量升级包制作:全量包生成工具使用、打包脚本编写、升级包完整性校验
好,咱们今天聊聊全量升级包的制作。说实话,这是整个OTA系统里最基础、也最不能出错的一环。你想想看,升级包要是出了问题,设备变砖可不是闹着玩的。我在项目初期就吃过这个亏,后面会跟大家细说。
4.1 全量包生成工具的使用
鸿蒙系统里,全量升级包的核心工具是 update_package_tool。这个工具藏在SDK的 tools 目录下。我个人习惯把它加到环境变量里,省得每次敲路径。
基本用法其实不复杂:
update_package_tool --input-dir ./target_files --output ./update.zip --version 1.0.0
这里几个参数我解释一下:
--input-dir:指定目标文件目录。里面放着编译好的系统镜像、分区表、校验文件等。--output:输出升级包的路径。一般叫update.zip。--version:版本号。这个很重要,设备端会拿它做版本比对。
嗯,这里要注意一点。工具对输入目录的结构有严格要求。我刚开始用的时候,少放了一个 META-INF 目录,结果工具直接报错退出。后来翻文档才发现,这个目录里必须包含 MANIFEST.MF 文件,里面记录了所有文件的哈希值。
核心要点:全量包本质上是一个ZIP压缩包,但内部结构是固定的。工具会帮你自动打包,但输入数据必须完整。
4.2 打包脚本编写
手动敲命令肯定不行,尤其是量产阶段,一天可能要打几十个包。所以,写个自动化脚本是必须的。
我一般用Python写打包脚本。为什么?因为Python处理文件、调用外部命令都很方便。给你看个简化版的例子:
#!/usr/bin/env python3
import os
import sys
import subprocess
import hashlib
def build_full_update(target_dir, output_path, version):
# 第一步:检查输入目录完整性
required_dirs = ['system', 'vendor', 'META-INF']
for d in required_dirs:
full_path = os.path.join(target_dir, d)
if not os.path.exists(full_path):
print(f"错误:缺少目录 {d}")
sys.exit(1)
# 第二步:生成校验文件
print("正在生成校验文件...")
manifest_path = os.path.join(target_dir, 'META-INF', 'MANIFEST.MF')
with open(manifest_path, 'w') as f:
for root, dirs, files in os.walk(target_dir):
for file in files:
if file == 'MANIFEST.MF':
continue
file_path = os.path.join(root, file)
sha256 = hashlib.sha256()
with open(file_path, 'rb') as f_in:
while True:
data = f_in.read(65536)
if not data:
break
sha256.update(data)
f.write(f"Name: {os.path.relpath(file_path, target_dir)}\n")
f.write(f"SHA-256-Digest: {sha256.hexdigest()}\n\n")
# 第三步:调用打包工具
print("正在生成升级包...")
cmd = [
'update_package_tool',
'--input-dir', target_dir,
'--output', output_path,
'--version', version
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"打包失败:{result.stderr}")
sys.exit(1)
print(f"升级包已生成:{output_path}")
if __name__ == '__main__':
build_full_update('./target_files', './update.zip', '1.0.0')
这个脚本做了三件事:检查目录、生成校验文件、调用工具打包。我在项目中还加了个日志功能,每次打包都会记录版本号、时间、文件大小,方便追溯。
小技巧:脚本里最好加个 --dry-run 参数。只检查不打包,能快速定位问题。我曾经靠这个参数,在半小时内排查出三个目录结构错误。
4.3 升级包完整性校验
包打好了,怎么保证它没损坏?这里涉及两个层面的校验。
第一层:ZIP结构校验。全量包本质是ZIP文件,所以先检查ZIP结构是否完整。工具自带 --verify 参数:
update_package_tool --verify ./update.zip
这个命令会检查ZIP的中央目录、本地文件头、CRC32校验。如果ZIP结构损坏,会直接报错。
第二层:内容校验。ZIP结构没问题,不代表里面的文件没被篡改。这时候就要用到 MANIFEST.MF 里的哈希值了。
我写了个独立的校验脚本,专门做这件事:
#!/bin/bash
# 升级包完整性校验脚本
UPDATE_PACKAGE=$1
if [ -z "$UPDATE_PACKAGE" ]; then
echo "用法:$0 <升级包路径>"
exit 1
fi
# 解压到临时目录
TEMP_DIR=$(mktemp -d)
unzip -q "$UPDATE_PACKAGE" -d "$TEMP_DIR"
# 读取MANIFEST.MF并逐条校验
MANIFEST="$TEMP_DIR/META-INF/MANIFEST.MF"
CURRENT_FILE=""
EXPECTED_HASH=""
while IFS= read -r line; do
if [[ $line == Name:* ]]; then
CURRENT_FILE=$(echo "$line" | cut -d' ' -f2-)
elif [[ $line == SHA-256-Digest:* ]]; then
EXPECTED_HASH=$(echo "$line" | cut -d' ' -f2-)
# 计算实际哈希
ACTUAL_HASH=$(sha256sum "$TEMP_DIR/$CURRENT_FILE" | cut -d' ' -f1)
if [ "$EXPECTED_HASH" != "$ACTUAL_HASH" ]; then
echo "校验失败:$CURRENT_FILE"
echo " 期望值:$EXPECTED_HASH"
echo " 实际值:$ACTUAL_HASH"
rm -rf "$TEMP_DIR"
exit 1
fi
echo "通过:$CURRENT_FILE"
fi
done < "$MANIFEST"
rm -rf "$TEMP_DIR"
echo "所有文件校验通过!"
警告:千万不要在生产环境跳过完整性校验。我曾经遇到过,因为硬盘坏道导致升级包里的一个so库损坏了2个字节。设备升级后,WiFi模块直接罢工。从那以后,我每次发版前都会跑两遍校验脚本。
4.4 避坑指南
最后,分享几个我踩过的坑:
- 文件权限问题:打包时,工具会保留文件的Unix权限。如果你在Windows上打包,记得用
--no-preserve-permissions参数。不然设备端解压时,可执行文件可能没有x权限。 - 符号链接处理:鸿蒙系统里有些目录是符号链接。打包时默认会跟随链接,把实际文件打进去。但这样会导致包体积变大。我建议用
--follow-symlinks=false,只打包链接本身。 - 版本号规范:版本号最好用语义化版本,比如
1.2.3。不要用v1.0或者20240101这种格式。设备端做版本比较时,语义化版本解析最稳定。
好了,全量升级包的制作就讲到这里。说白了,工具使用是基础,脚本编写是效率,完整性校验是底线。这三样都做好了,升级包这块基本不会出大问题。下一章咱们聊聊增量升级包,那个更有意思。