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 这种格式。设备端做版本比较时,语义化版本解析最稳定。

好了,全量升级包的制作就讲到这里。说白了,工具使用是基础,脚本编写是效率,完整性校验是底线。这三样都做好了,升级包这块基本不会出大问题。下一章咱们聊聊增量升级包,那个更有意思。