minizip

本项目基于 minizip_ng 开发。

简介

基于minizip_ng的解压缩库。

下载安装

ohpm install @ohos/minizip

OpenHarmony ohpm 环境配置等更多内容,请参考 如何安装 OpenHarmony ohpm 包

约束与限制

兼容性

在下述版本验证通过:

DevEco Studio: NEXT Beta1-5.0.3.806, SDK: API12 Release (5.0.0.66)。

DevEco Studio: NEXT Developer Beta1-5.0.3.320, SDK: API12(5.0.0.23)。

编译运行

本项目依赖bzip2、minizip-ng、openssl、xz、zstd库,编译产物.so文件和头文件需要自行编译

参考bzip2本地编译脚本

参考minizip-ng本地编译脚本

参考openssl本地编译脚本

参考xz本地编译脚本

参考zstd本地编译脚本

在cpp目录下新增third_party目录,并将编译生成的bzip2_1_0_8、minizip-ng、openssl、xz、zstd库拷贝到该目录下,将bzip2_1_0_8更名为bzip2,如下图所示:

img.png

使用示例

以下示例展示了 minizip 库的四种主要用法:解压文件到内存、解压文件到磁盘、压缩文件到内存、压缩文件到磁盘。

import { MinizipNative, unzipToDirectory, MinizipCompressNative } from '@ohos/minizip';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Index {
  private context: Context = this.getUIContext().getHostContext()!;
  private filesDir: string = this.context.filesDir;
  private resourceDir: string = this.context.resourceDir;
  
  @State selectFilePath: string = this.resourceDir + '/test.zip';
  @State password: string = "123456";
  @State selectFilePathCompress: string = this.context.filesDir + '/test_compress.zip';

  build() {
    Column() {
      Button("解压文件到内存")
        .margin({ bottom: 10 })
        .onClick(() => {
          this.extractFileToJS(this.selectFilePath, this.password);
        })
      
      Button("解压文件到磁盘")
        .margin({ bottom: 10 })
        .onClick(() => {
          this.unzipToDirectory(this.selectFilePath, this.filesDir + "/test", this.password)
        })
      
      Button("压缩文件到内存")
        .margin({ bottom: 10 })
        .onClick(() => {
          this.compressToJS(this.filesDir, [this.filesDir + "/test"], this.password);
        })
      
      Button("压缩文件到磁盘")
        .margin({ bottom: 10 })
        .onClick(() => {
          this.compress(this.selectFilePathCompress, [this.filesDir + "/test"], this.password);
        })
    }
    .width('100%')
      .height('100%')
  }

  // 1. 解压文件到内存
  private async extractFileToJS(selectFilePath: string, password: string): Promise<void> {
    // 导入:创建 MinizipNative 实例
    let minizipEntry = new MinizipNative(selectFilePath);
    // 初始化:设置字符编码(936 为简体中文)
    minizipEntry.SetCharEncoding(936);
    
    if (minizipEntry.Open() === 0) {
      let entryNames = minizipEntry.GetEntryNames();
      for (let i = 0; i < entryNames.length; i++) {
        if (!entryNames[i].endsWith("/")) {
          // 解压指定文件内容到内存
          let arrBuffer = minizipEntry.ExtractFileToJS(entryNames[i], password);
          if (arrBuffer === undefined) {
            console.error(`minizip: 解压文件失败 - ${entryNames[i]}`);
            return;
          }
          // 输出:处理文件内容(此处仅打印文件大小)
          console.info(`minizip: 成功解压文件 ${entryNames[i]},大小:${arrBuffer.byteLength} 字节`);
        }
      }
    } else {
      console.error(`minizip: 打开 ZIP 文件失败`);
    }
  }

  // 2. 解压文件到磁盘
  private unzipToDirectory(selectFilePath: string, targetPath: string, password: string) {
    unzipToDirectory(selectFilePath, targetPath, password, 936).then((data: string) => {
      console.info(`minizip: 解压文件成功 - ${data}`);
    }).catch((err: BusinessError) => {
      console.error(`minizip: 解压文件失败 - ${JSON.stringify(err)}`);
    });
  }

  // 3. 压缩文件到内存
  private compressToJS(selectFilePath: string, targetPath: string[], password: string) {
    // 导入:创建 MinizipCompressNative 实例
    let minizipCompressEntry = new MinizipCompressNative(selectFilePath);
    
    if (minizipCompressEntry.Create() == 0) {
      // 压缩指定文件到内存
      let arrBuffer = minizipCompressEntry.CompressToJS(targetPath, password);
      if (arrBuffer) {
        // 输出:打印压缩成功信息和文件大小
        console.info(`minizip: 压缩文件成功,大小:${arrBuffer.byteLength} 字节`);
      } else {
        console.error(`minizip: 压缩文件到内存失败`);
      }
    } else {
      console.error(`minizip: 创建 ZIP 文件失败`);
    }
    minizipCompressEntry.Close();
  }

  // 4. 压缩文件到磁盘
  private compress(selectFilePath: string, targetPath: string[], password: string) {
    // 导入:创建 MinizipCompressNative 实例,指定输出文件路径
    let minizipCompressEntry = new MinizipCompressNative(selectFilePath, selectFilePath);
    
    if (minizipCompressEntry.Create() == 0) {
      let code: number = minizipCompressEntry.Compress(targetPath, password);
      if (code === 0) {
        console.info(`minizip: 压缩文件到磁盘成功`);
      } else {
        console.error(`minizip: 压缩文件到磁盘失败,错误代码:${code}`);
      }
    } else {
      console.error(`minizip: 创建 ZIP 文件失败`);
    }
    minizipCompressEntry.Close();
  }
}

使用说明

解压 ZIP 包获取文件内容

使用 MinizipNative 类将 ZIP 文件中的文件解压到内存,适用于处理小文件或需要在内存中处理内容的场景。

基本步骤:

  1. 创建 MinizipNative 实例并指定 ZIP 文件路径
  2. 调用 SetCharEncoding() 设置字符编码(根据 ZIP 文件创建时使用的编码)
  3. 调用 Open() 打开 ZIP 文件
  4. 获取文件列表,逐个解压

代码示例:

import { MinizipNative } from '@ohos/minizip';

// 解压文件
let minizipEntry = new MinizipNative(this.selectFilePath);
// 设置字符编码为简体中文
minizipEntry.SetCharEncoding(936);

if (minizipEntry.Open() == 0) {
  // 获取 ZIP 中的所有文件和文件夹
  let entryNames = minizipEntry.GetEntryNames();
  for (let i = 0; i < entryNames.length; i++) {
    console.log('ZIP 文件列表: ' + entryNames[i]);
    // 跳过文件夹,只处理文件
    if (!entryNames[i].endsWith("/")) {
      // 解压文件内容到内存(无密码时传入空字符串)
      let arrBuffer = minizipEntry.ExtractFileToJS(entryNames[i], this.password);
      console.log("文件大小: " + arrBuffer?.byteLength);
    }
  }
} else {
  console.error("打开 ZIP 文件失败");
}

提示: 若 ZIP 文件无密码,调用时将密码参数设为空字符串:ExtractFileToJS(entryName, "")

解压 ZIP 包到磁盘

使用 unzipToDirectory() 函数将 ZIP 文件完整解压到指定磁盘目录,适用于处理大文件或需要保留文件结构的场景。此函数是异步的,返回 Promise

代码示例:

import { unzipToDirectory } from '@ohos/minizip';
import { BusinessError } from '@kit.BasicServicesKit';

unzipToDirectory(zipFile, targetDir, password, 936)
  .then(() => {
    console.info('解压成功');
  })
  .catch((err: BusinessError)
    console.error(`解压失败: ${JSON.stringify(err)}`);
  });

参数说明:

  • zipFile:待解压的 ZIP 文件路径(需包含文件名)
  • targetDir:解压目标目录。若目录不存在,函数将自动创建
  • password:ZIP 文件密码。若无密码,传入空字符串或省略此参数
  • charEncoding:可选参数,字符编码

压缩文件到内存

使用 MinizipCompressNative 类将文件压缩成 ZIP 格式并存储在内存,适用于需要在网络上传输压缩数据的场景。

代码示例:

import { MinizipCompressNative } from '@ohos/minizip';

// 创建压缩对象(第一个参数为临时存储路径,第二个为输出的 ZIP 文件名或路径)
let compressor = new MinizipCompressNative(this.filesDir);

if (compressor.Create() == 0) {
  // 指定要压缩的文件/文件夹列表
  let filesToCompress = [this.filesDir + "/test.txt", this.filesDir + "/image.png"];
  // 压缩文件到内存
  let zipBuffer = compressor.CompressToJS(filesToCompress, this.password);
  
  if (zipBuffer) {
    console.info(`压缩成功,大小:${zipBuffer.byteLength} 字节`);
    // 此时可以上传 zipBuffer 或保存为文件
  } else {
    console.error("压缩失败");
  }
} else {
  console.error("创建压缩文件失败");
}
compressor.Close();

压缩文件到磁盘

使用 MinizipCompressNative 类将文件压缩并直接保存为 ZIP 文件到磁盘,适用于常见的文件压缩需求。

代码示例:

this.compress(this.selectFilePathCompress, [this.filesDir + "/test"], this.password);
// 创建压缩对象,指定输出的 ZIP 文件路径
let compressor = new MinizipCompressNative(this.filesDir, this.filesDir + "/output.zip");

if (compressor.Create() == 0) {
  // 指定要压缩的文件/文件夹列表
  let filesToCompress = [this.context.filesDir + "/test.txt", this.context.filesDir + "/image.png"];
  // 执行压缩
  let result = compressor.Compress(filesToCompress, this.password);

  if (result === 0) {
    console.info("压缩到磁盘成功");
    // ZIP 文件已保存到 this.filesDir + "/output.zip"
  } else {
    console.error(`压缩失败,错误代码:${result}`);
  }
} else {
  console.error("创建压缩文件失败");
}
compressor.Close();

接口说明

minizip 库主要提供两个类和一个函数供应用开发者使用。

MinizipNative

解压类。用于读取和解压 ZIP 文件,支持密码保护文件的解密。

接口 说明 参数 返回值
构造方法 创建 MinizipNative 实例 path: string - ZIP 文件完整路径(包含文件名) MinizipNative 实例
Open() 打开 ZIP 文件 0 - 成功;非 0 - 失败
SetCharEncoding(charEncoding) 设置字符编码 charEncoding: number - 字符编码代码
GetEntryNames() 获取 ZIP 文件中的所有文件和文件夹名称列表 Array<string> - 文件或文件夹路径列表
ExtractFileToJS(entryName, password)** 将指定文件解压到内存 entryName: string - 文件在 ZIP 中的相对路径;password: string - 密码 ArrayBuffer - 文件内容;undefined - 解压失败
IsEncrypto(entryName) 判断指定文件是否加密 entryName: string - 文件在 ZIP 中的相对路径 boolean - true 表示加密
IsUtf8(entryName) 判断指定文件名是否为 UTF-8 编码 entryName: string - 文件在 ZIP 中的相对路径 boolean - true 表示为 UTF-8

unzipToDirectory

将 ZIP 文件完整解压到指定目录。此函数是异步的,返回 Promise。

unzipToDirectory(selectPath: string, targetPath: string, password?: string, charEncoding?: number): Promise<string> 
参数 类型 必填 说明
selectPath string ZIP 文件的完整路径(包含文件名)
targetPath string 解压目标目录。若目录不存在,函数自动创建
password string ZIP 文件密码。若无密码,传入空字符串或省略
charEncoding number 字符编码代码(默认 936,即简体中文)

MinizipCompressNative

压缩类。用于创建新的 ZIP 文件并压缩指定文件,支持多种压缩算法和密码保护。

接口 说明 参数 返回值
构造方法 创建 MinizipCompressNative 实例 catchPath: string - 临时缓存路径;zipFilePath: string - 输出 ZIP 文件路径(包含文件名) MinizipCompressNative 实例
Create() 创建 ZIP 文件容器 0 - 成功;非 0 - 失败
Close() 关闭 ZIP 文件
SetCompressMethod(compressMethod) 设置压缩算法 compressMethod: number - 压缩算法代码(默认 8 - Deflate) 0 - 成功;非 0 - 失败
SetCompressLevel(compressLevel) 设置压缩等级 compressLevel: number - 压缩等级代码 0 - 成功;非 0 - 失败
SetzipFilePath(zipFilePath) 重新设置 ZIP 文件输出路径 zipFilePath: string - 新的 ZIP 文件路径
Compress(entries, password) 压缩文件到磁盘 entries: Array<string> - 待压缩的文件或文件夹路径数组;password: string - ZIP 文件密码 0 - 成功;非 0 - 失败
CompressToJS(entries, password 压缩文件到内存 entries: Array<string> - 待压缩的文件或文件夹路径数组;password: string - ZIP 文件密码 ArrayBuffer - 压缩后的 ZIP 内容;undefined - 压缩失败

常见问题

  1. 解压 ZIP 文件后,中文文件名显示为乱码。

解决方案:使用 SetCharEncoding() 方法设置正确的字符编码。大多数在 Windows 上创建的 ZIP 文件使用 CP936(936)编码,使用以下代码:

minizipEntry.SetCharEncoding(936);  
  1. 调用 ExtractFileToJS() 返回 undefined

可能原因和解决方案:

a. 文件名错误:检查 GetEntryNames() 返回的文件名是否完全匹配。
b. 密码错误:若 ZIP 文件有密码,确保传入正确的密码。
c. 内存不足:若文件过大,内存可能不足,建议使用 unzipToDirectory() 改为解压到磁盘。
d. 未调用 Open():确保在解压前已调用 Open() 方法。

注意事项

  • 创建 MinizipNative 对象需要传入完整的文件路径(包含文件名)。
  • 创建对象之后必须调用 Open() 函数,每个 MinizipNative 实例只能调用一次 Open()。若 Open 返回非 0,表示打开文件失败。
  • 解压后应及时关闭文件句柄,避免资源泄漏。
  • 大文件建议使用 unzipToDirectory() 而非 ExtractFileToJS() 以节省内存。

关于混淆

  • 代码混淆,请查看代码混淆简介
  • 如果希望 minizip 库在代码混淆过程中不会被混淆,需要在混淆规则配置文件obfuscation-rules.txt中添加相应的排除规则:
-keep
./oh_modules/@ohos/minizip

目录结构

ohos_minizip/
├── entry/                          # 示例应用代码
├── library/                        # minizip 库源码
│   ├── src/main/
│   │   ├── cpp/                   # C++ 和 NAPI 代码
│   │   │   ├── minizipAdapter/    # minizip NAPI 逻辑代码
│   │   │   ├── CMakeLists.txt     # CMake 构建脚本
│   │   │   ├── thirdparty/        # 第三方库依赖
│   │   │   └── types/             # TypeScript 接口声明
│   │   └── ets/
│   │       └── pages/Index.ets    # 库对外接口
│   ├── index.ets                  # 库入口文件
│   └── oh-package.json5           # 库配置文件
├── README.md                       # 英文文档
├── README_zh.md                    # 中文文档
└── LICENSE                         # 许可证

贡献代码

使用过程中发现任何问题都可以提 Issue 给我们,当然,我们也非常欢迎你给我们发 PR

开源协议

本项目基于 Apache License 2.0 ,请自由地享受和参与开源。