基于 react-native-zip-archive 的 OpenHarmony 适配版,ZIP 文件压缩与解压
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 10 天前 | ||
| 10 天前 | ||
| 25 天前 | ||
| 3 个月前 | ||
| 6 年前 | ||
| 3 个月前 | ||
| 8 年前 | ||
| 25 天前 | ||
| 3 个月前 | ||
| 3 个月前 | ||
| 25 天前 | ||
| 25 天前 | ||
| 25 天前 | ||
| 25 天前 | ||
| 2 年前 | ||
| 3 个月前 | ||
| 1 个月前 | ||
| 25 天前 | ||
| 25 天前 | ||
| 25 天前 | ||
| 10 天前 |
文档模板:v0.4.2
react-native-zip-archive
本项目基于 react-native-zip-archive 开发。
该第三方库的仓库已迁移至 Gitcode,并支持直接从 npm 下载,新的包名为:@react-native-ohos/react-native-zip-archive。各版本对应关系如下:
| 第三方库名称 | 第三方库版本(npm 地址) | 发布信息 | 支持的 RN 版本 | Autolink | 编译 API 版本 | 社区基线版本 | 源码地址 |
|---|---|---|---|---|---|---|---|
| @react-native-ohos/react-native-zip-archive | ~ 9.0.0 | Gitcode Releases | 0.84.* | 是 | API12+ | 9.0.0 | master |
| @react-native-ohos/react-native-zip-archive | ~ 8.0.0 | Gitcode Releases | 0.82.* | 是 | API12+ | 8.0.0 | br_rnoh0.82 |
| @react-native-ohos/react-native-zip-archive | ~ 7.1.0 | Gitcode Releases | 0.77.* | 否 | API12+ | 7.0.2 | br_rnoh0.77 |
| @react-native-ohos/react-native-zip-archive | ~ 6.1.2 | Gitcode Releases | 0.72.* | 是 | API12+ | 6.1.1 | br_rnoh0.72 |
| @react-native-oh-tpl/react-native-zip-archive | <= 6.1.1-0.1.0@deprecated | GitHub Releases(已弃用) | 0.72.* | 否 | API12+ | 6.1.1 | sig |
简介
react-native-zip-archive 是一个用于处理 React Native 应用中 ZIP 文件的实用库。它支持对文件或文件夹进行压缩与解压,并可按需添加密码保护。
下载安装
进入工程目录,输入以下命令:
npm
npm install @react-native-ohos/react-native-zip-archive
yarn
yarn add @react-native-ohos/react-native-zip-archive
链接
| 是否支持 autolink | RN框架版本 | |
|---|---|---|
| ~ 9.0.0 | 是 | 0.84 |
使用AutoLink的工程需要根据该文档完成配置,Autolink框架指导文档:https://gitcode.com/CPF-RN/ohos_react_native/blob/master/docs/zh-cn/Autolinking.md
如您使用的版本支持 Autolink,并且工程已接入 Autolink,可跳过ManualLink配置。
ManualLink: 本步骤为手动配置原生依赖项的指引
首先需要使用 DevEco Studio 打开项目中的 HarmonyOS 工程 harmony。
1. 覆盖 RN SDK
为了让工程依赖同一版本的 RN SDK,需要在工程根目录的 oh-package.json5 添加 overrides 字段,指向工程所需使用的 RN SDK 版本。用于替换的版本既可以是一个具体的版本号,也可以是一个模糊版本,还可以是本地已存在的 HAR 包或源码目录。
关于该字段的作用,请阅读官方说明
{
"overrides": {
"@rnoh/react-native-openharmony": "^0.84.3" // ohpm 在线版本
// "@rnoh/react-native-openharmony" : "./react_native_openharmony.har" // 指向本地 har 包的路径
// "@rnoh/react-native-openharmony" : "./react_native_openharmony" // 指向源码路径
}
}
2. 接入原生端代码
目前有两种方式:
- 通过 har 包引入;
- 直接引用源码。
方法一:通过 har 包引入(推荐)
har 包位于三方库安装路径的 `harmony` 文件夹下。
打开 entry/oh-package.json5,添加以下依赖:
"dependencies": {
"@react-native-ohos/react-native-zip-archive": "file:../../node_modules/@react-native-ohos/react-native-zip-archive/harmony/zipArchive_package.har"
}
点击右上角的 sync 按钮
或者在命令行终端执行:
cd entry
ohpm install
方法二:直接链接源码
如需使用直接链接源码,请参考[直接链接源码说明](https://gitcode.com/CPF-RN/usage-docs/blob/master/zh-cn/link-source-code.md)
3. 配置 CMakeLists 并引入 RNZipArchivePackage
打开 entry/src/main/cpp/CMakeLists.txt,添加:
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")
set(LOG_VERBOSITY_LEVEL 1)
set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use
add_compile_definitions(WITH_HITRACE_SYSTRACE)
add_subdirectory("${RNOH_CPP_DIR}" ./rn)
# RNOH_BEGIN: manual_package_linking_1
add_subdirectory("../../../../sample_package/src/main/cpp" ./sample-package)
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-zip-archive/src/main/cpp" ./zipArchive-package)
# RNOH_END: manual_package_linking_1
file(GLOB GENERATED_CPP_FILES "./generated/*.cpp")
add_library(rnoh_app SHARED
${GENERATED_CPP_FILES}
"./PackageProvider.cpp"
"${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_link_libraries(rnoh_app PUBLIC rnoh)
# RNOH_BEGIN: manual_package_linking_2
target_link_libraries(rnoh_app PUBLIC rnoh_sample_package)
+ target_link_libraries(rnoh_app PUBLIC rnoh_zipArchive)
# RNOH_END: manual_package_linking_2
打开 entry/src/main/cpp/PackageProvider.cpp,添加:
#include "RNOH/PackageProvider.h"
#include "SamplePackage.h"
+ #include "RNZipArchivePackage.h"
using namespace rnoh;
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
return {
std::make_shared<SamplePackage>(ctx),
+ std::make_shared<RNZipArchivePackage>(ctx)
};
}
4. 在 ArkTs 侧引入 RNZipArchivePackage
打开 entry/src/main/ets/RNPackagesFactory.ts,添加:
...
+ import {RNZipArchivePackage} from '@react-native-ohos/react-native-zip-archive';
export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
return [
new SamplePackage(ctx),
+ new RNZipArchivePackage(ctx)
];
}
运行
点击右上角的 sync 按钮
或者在命令行终端执行:
cd entry
ohpm install
然后编译并运行即可。
约束与限制
兼容性
本文档内容已基于以下版本验证通过:
- RNOH: 0.84.2; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.2.636; ROM: 6.0.0.125;
编译运行API要求
当前第三方库的所有版本均已实现版本隔离,支持在 `API12+` 工程中编译,并在 `API12+` ROM 上运行。
使用示例
以下代码展示了该库的基本使用场景:
使用时,import 的库名保持不变。Demo 中使用的 react-native-blob-util 可参考[react-native-blob-util.md](https://gitcode.com/CPF-RN/usage-docs/blob/master/zh-cn/react-native-blob-util.md?init=initTree)进行配置。
import React, { useState, useEffect, useRef, useCallback, createContext, useContext } from 'react';
import { View, ScrollView, Button, StyleSheet, TextInput, Alert, Text, ActivityIndicator, Keyboard, UIManager, Dimensions } from 'react-native';
import { zip, unzip, zipWithPassword, unzipWithPassword, isPasswordProtected, unzipAssets, getUncompressedSize, subscribe } from 'react-native-zip-archive';
import { Tester, TestSuite, TestCase } from '@rnoh/testerino';
import ReactNativeBlobUtil from 'react-native-blob-util';
const fs = ReactNativeBlobUtil.fs;
type ZipProgressTask = 'zip' | 'passwordZip' | 'unzip';
const KeyboardAvoidContext = createContext<{ onFocusInput?: (event: any) => void }>({});
//demo入口
export default function ZipArchiveDemo() {
const scrollRef = useRef<ScrollView>(null);
const scrollYRef = useRef(0); // 当前 ScrollView 垂直滚动偏移
const kbHeightRef = useRef(0); // 最新键盘高度(ref 保证异步回调读到最新值)
const [kbHeight, setKbHeight] = useState(0);
useEffect(() => {
// 鸿蒙(RNOH) 上 KeyboardAvoidingView 通常不生效,这里手动监听键盘事件:
// 弹起时给 ScrollView 底部留出键盘高度的 padding,并滚动到被聚焦的输入框。
const showSub = Keyboard.addListener('keyboardDidShow', (e: any) => {
const h = (e && e.endCoordinates && e.endCoordinates.height) || 320;
kbHeightRef.current = h;
setKbHeight(h);
});
const hideSub = Keyboard.addListener('keyboardDidHide', () => {
kbHeightRef.current = 0;
setKbHeight(0);
});
return () => {
showSub.remove();
hideSub.remove();
};
}, []);
const onFocusInput = useCallback((event: any) => {
const target = event?.nativeEvent?.target;
if (!target) return;
setTimeout(() => {
try {
UIManager.measure(target, (_x: number, _y: number, _w: number, h: number, _pageX: number, pageY: number) => {
const win = Dimensions.get('window');
const keyboardTop = win.height - kbHeightRef.current; // 键盘上沿在屏幕的 Y 坐标
const safeGap = 48;
const overflow = pageY + h - (keyboardTop - safeGap); // 输入框底部超出键盘上沿的像素
if (overflow > 0) {
scrollRef.current?.scrollTo({
y: scrollYRef.current + overflow + 12,
animated: true,
});
}
});
} catch {
// measure 不可用时不强制滚动,仅靠 paddingBottom 保证可手动滑出
}
}, 300);
}, []);
return (
<KeyboardAvoidContext.Provider value={{ onFocusInput }}>
<ScrollView
ref={scrollRef}
contentContainerStyle={{ flexGrow: 1, paddingBottom: kbHeight }}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="interactive"
scrollEventThrottle={16}
onScroll={(e) => { scrollYRef.current = e.nativeEvent.contentOffset.y; }}
>
<View>
<ZipArchiveDemoTest_></ZipArchiveDemoTest_>
</View>
</ScrollView>
</KeyboardAvoidContext.Provider>
);
};
const ZipArchiveDemoTest_ = () => {
const [fileName, setFileName] = useState('');
const [fileContent, setFileContent] = useState('');
const [createdFilePath, setCreatedFilePath] = useState('');
const [compressedFilePath, setCompressedFilePath] = useState('');
const [newZipPath, setNewZipPath]: any = useState();
const [newSourcePath, setNewSourcePath]: any = useState();
const [newFolder, setNewFolder]: any = useState();
const [password, setPassword] = useState('');
const [showInput, setShowInput] = useState(false);
const [zipPassword, setZipPassword] = useState('');
const [zipProgress, setZipProgress] = useState(0);
const [passwordZipProgress, setPasswordZipProgress] = useState(0);
const [unzipProgress, setUnzipProgress] = useState(0);
const [loading, setLoading] = useState(false);
const [unzipStatus, setUnzipStatus] = useState('');
const [uncompressSize, setUncompressSize] = useState('');
// 从外层 ScrollView 拿到键盘避让的聚焦滚动回调(鸿蒙上 KeyboardAvoidingView 不生效)
const { onFocusInput } = useContext(KeyboardAvoidContext);
/** 当前进度的 subscribe 句柄,便于切换任务/卸载时彻底清理 */
const progressSubRef = useRef<{ remove: () => void } | null>(null);
/** 当前绑定的进度任务,用于把 subscribe 回调里的进度写到对应进度条 */
const activeTaskRef = useRef<ZipProgressTask | null>(null);
// 拿到当前任务对应的进度 setter
const getProgressSetter = useCallback((task: ZipProgressTask) => {
return task === 'zip'
? setZipProgress
: task === 'passwordZip'
? setPasswordZipProgress
: setUnzipProgress;
}, []);
const unbindProgress = useCallback(() => {
if (progressSubRef.current) {
progressSubRef.current.remove();
progressSubRef.current = null;
}
activeTaskRef.current = null;
}, []);
useEffect(() => {
return () => {
unbindProgress();
};
}, [unbindProgress]);
const [unzipPassword, setUnzipPassword] = useState('');
useEffect(() => {
console.log('-----pathParameters');
let filesDir = "/data/storage/el2/base/haps/entry/files"; // 获取鸿蒙应用文件路径
let newZipPath: any = filesDir + '.zip';
let newSourcePath: any = filesDir;
let newFolder: any = filesDir + 'Out';//解压时新建个文件夹
setNewZipPath(newZipPath);//存储压缩包
setNewSourcePath(newSourcePath);//原文件路径
setNewFolder(newFolder);//解压时新建个文件夹
if (!showInput) {
setPassword(''); // 隐藏输入框时清空密码
}
}, [showInput]);
// 创建文件
const createFile = async () => {
if (!fileName || !fileContent) {
Alert.alert('文件名和内容不能为空');
return;
}
const filePath = `${newSourcePath}/${fileName}.txt`;
if (fileName && fileContent) {
await fs.writeFile(filePath, fileContent, 'utf8');
setCreatedFilePath(filePath);
Alert.alert('文件创建成功');
setTimeout(() => {
setFileName('');
setFileContent('');
}, 100);
} else {
Alert.alert('请输入文件名和内容');
}
console.log('-----creteFile');
};
// 密码压缩
const handleZipPress = () => {
if (password === '') {
Alert.alert('错误', '请输入密码');
return;
}
if (createdFilePath) {
handleProgress('passwordZip');
zipWithPassword(newSourcePath, newZipPath, password)
.then(() => {
console.log(`password--11:${password}`)
setZipPassword(password);
setCompressedFilePath(newZipPath)
finishProgress('passwordZip');
Alert.alert('成功', '已使用密码创建压缩');
})
.catch(error => {
unbindProgress();
Alert.alert('错误', `创建压缩文件失败: ${error}`);
});
console.log('-----zipWithPassword');
} else {
Alert.alert('无文件可供压缩');
}
};
// 解压时是否需要密码
const isUnzipWithPassword = () => {
if (unzipPassword) {
if (unzipPassword === zipPassword) {
handleGetUncompressedSize();
handleProgress('unzip');
unzipWithPassword(newZipPath, newFolder, unzipPassword)
.then(() => {
finishProgress('unzip');
setShowInput(false);
Alert.alert('成功', '已使用密码解压文件');
})
.catch(error => {
unbindProgress();
Alert.alert('错误', `解压文件失败: ${error}`);
});
console.log('-----unzipWithPassword');
} else {
Alert.alert('密码输入错误');
}
} else {
Alert.alert('错误', '请先输入密码');
}
}
// 密码解压&解压
const handleUnzipPress = () => {
isPasswordProtected(newZipPath)
.then((res) => {
if (res) {
// 有密码:弹出密码输入,走密码解压分支
setShowInput(true);
isUnzipWithPassword();
return 'success';
} else {
// 无密码:直接解压
if (compressedFilePath) {
setShowInput(false);
handleGetUncompressedSize();
handleProgress('unzip');
unzip(newZipPath, newFolder, 'UTF-8')
.then(() => {
console.log(`unzip success`)
finishProgress('unzip');
Alert.alert('成功', '已解压');
return 'success';
})
.catch(error => {
unbindProgress();
Alert.alert('错误', '解压失败');
console.log(`unzip error: ${error}`);
return 'failed';
})
console.log('-----unzip');
return 'success';
} else {
Alert.alert('无压缩文件可供解压');
return 'failed';
}
}
})
.catch(error => {
Alert.alert('无压缩文件可供解压');
return 'failed';
})
console.log('-----isPasswordProtected');
return 'success';
}
// 仅更新与当前操作对应的进度条。使用 subscribe 监听 progress 事件 (0~1),转换成百分比写入 state。
const handleProgress = (task: ZipProgressTask) => {
unbindProgress();
const setForTask = getProgressSetter(task);
setForTask(0);
activeTaskRef.current = task;
const sub = subscribe(({ progress: p, filePath }: { progress: number, filePath: string }) => {
const currentTask = activeTaskRef.current;
if (currentTask !== task) {
return;
}
const pct = Math.round(p * 100);
const setter = getProgressSetter(currentTask);
setter(pct);
console.log(`[${task}] progress event: ${pct}%`);
});
progressSubRef.current = sub;
}
// 操作成功后强制把进度条拉满 100%(鸿蒙侧 progress 事件可能不派发到 JS)
const finishProgress = (task: ZipProgressTask) => {
const setForTask = getProgressSetter(task);
setForTask(100);
unbindProgress();
}
// unzipAssets解压到指定目录
const handleUnzipAssets = async () => {
setLoading(true);
let filesDir = "/data/storage/el2/base/haps/entry/files";
//解压files.zip文件到系统中的 destinationFolder 目录中
let assetPath = filesDir + '.zip';
let targetPath = filesDir + 'destinationFolder';
if (compressedFilePath) {
try {
await unzipAssets(assetPath, targetPath);
setUnzipStatus('解压完成');
console.log(`unzipAssets success`);
} catch (err) {
setUnzipStatus(`解压失败:${err}`);
console.log(`unzipAssets err: ${err}`);
} finally {
setLoading(false);
}
console.log('-----unzipAssets');
} else {
Alert.alert('无压缩文件可供解压');
}
}
// getUncompressedSize解压文件大小
const handleGetUncompressedSize = () => {
getUncompressedSize(newZipPath)
.then((uncompressSize: any) => {
setUncompressSize(uncompressSize);
console.log(`uncompressSize success:${uncompressSize}`)
})
.catch((err) => {
console.log(`getUncompressedSize err:${err}`)
})
console.log('-----getUncompressedSize');
}
return (
<Tester>
<TestSuite name="创建文件">
<TestCase tags={['C_API']} itShould="创建文件"
initialState={''}
arrange={({ setState }) =>
<View >
<View >
<TextInput
placeholder="请输入文件名"
value={fileName}
onChangeText={setFileName}
onFocus={onFocusInput}
style={{ borderWidth: 1, padding: 10, width: '70%' }}
/>
<TextInput
style={styles.input}
onChangeText={setFileContent}
value={fileContent}
placeholder="文件内容"
onFocus={onFocusInput}
/>
<Button title="创建文件" onPress={() => { Keyboard.dismiss(); createFile(); setState('success'); }} />
</View>
</View>
}
assert={({ expect, state }) => {
expect(state).to.be.eq('success');
}}>
</TestCase>
</TestSuite>
<TestSuite name="压缩文件">
<TestCase tags={['C_API']} itShould="压缩文件"
initialState={''}
arrange={({ setState }) =>
<View style={styles.buttonSix}>
<Text >压缩进度</Text>
<View style={styles.progressBar}>
<View style={{ width: `${zipProgress}%`, backgroundColor: '#00AEEF', height: '100%' }}></View>
</View>
<Text style={styles.percentageText}>{zipProgress}%</Text>
<Button title='压缩' onPress={() => {
Keyboard.dismiss();
if (createdFilePath) {
handleProgress('zip');
zip(newSourcePath, newZipPath)
.then(() => {
setCompressedFilePath(newZipPath)
finishProgress('zip');
Alert.alert('成功', '已压缩');
setState('success');
})
.catch(error => {
unbindProgress();
Alert.alert('错误', `压缩失败: ${error}`);
})
console.log('-----zip');
} else {
Alert.alert('无文件可供压缩');
}
}} />
</View>
}
assert={({ expect, state }) => {
expect(state).to.be.eq('success');
}}>
</TestCase>
</TestSuite>
<TestSuite name="设置密码压缩文件">
<TestCase tags={['C_API']} itShould="设置密码压缩文件"
initialState={''}
arrange={({ setState }) =>
<View>
<Text >压缩进度</Text>
<View style={styles.progressBar}>
<View style={{ width: `${passwordZipProgress}%`, backgroundColor: '#00AEEF', height: '100%' }}></View>
</View>
<Text style={styles.percentageText}>{passwordZipProgress}%</Text>
<TextInput
style={styles.input}
placeholder="设置压缩密码"
onChangeText={setPassword}
value={password}
onFocus={onFocusInput}
/>
<View style={styles.buttonSix}>
<Button title='密码压缩' onPress={() => { Keyboard.dismiss(); handleZipPress(); setState('success'); }} />
</View>
</View>
}
assert={({ expect, state }) => {
expect(state).to.be.eq('success');
}}>
</TestCase>
</TestSuite>
<TestSuite name="解压文件">
<TestCase tags={['C_API']} itShould="解压文件"
initialState={''}
arrange={({ setState }) =>
<View>
<Text >压缩进度</Text>
<View style={styles.progressBar}>
<View style={{ width: `${unzipProgress}%`, backgroundColor: '#00AEEF', height: '100%' }}></View>
</View>
<Text style={styles.percentageText}>{unzipProgress}%</Text>
<TextInput
style={styles.input}
placeholder="输入解压密码"
value={unzipPassword}
onChangeText={setUnzipPassword}
onFocus={onFocusInput}
/>
<View style={styles.buttonSix}>
<Button title="解压" onPress={() => { Keyboard.dismiss(); var result = handleUnzipPress(); setState(result); }} />
</View>
<Text>解压缩后的大小:{uncompressSize ? uncompressSize : '0'}字节</Text>
</View>
}
assert={({ expect, state }) => {
expect(state).to.be.eq('success');
}}>
</TestCase>
</TestSuite>
<TestSuite name="unzipAssets解压">
<TestCase tags={['C_API']} itShould="解压到指定目录"
initialState={''}
arrange={({ setState }) =>
<View>
<Button title='unzipAssets解压' onPress={() => { Keyboard.dismiss(); handleUnzipAssets(); setState('success'); }} />
{loading ? (
<View>
<ActivityIndicator size="large" color='#0000ff' />
<Text>正在解压文件...</Text>
</View>
) : (
<Text>{unzipStatus}</Text>
)}
</View>
}
assert={({ expect, state }) => {
expect(state).to.be.eq('success');
}}>
</TestCase>
</TestSuite>
</Tester>
)
}
const styles = StyleSheet.create({
content: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginTop: 56,
backgroundColor: 'white'
},
buttonSix: {
width: '65%',
marginBottom: 10,
marginTop: 20
},
input: {
borderWidth: 1,
padding: 10,
width: 300
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
progressBar: {
width: 250,
height: 20,
backgroundColor: '#E0E0E0',
borderRadius: 10,
overflow: 'hidden',
marginTop: 10
},
percentageText: {
marginTop: 5,
fontSize: 16
}
})
使用说明
以下示例中的 `sourcePath`、`targetPath` 等路径须为鸿蒙应用可访问的沙箱路径,例如 `/data/storage/el2/base/haps/entry/files`。路径中的 `file://` 前缀会自动移除,无需手动处理。
压缩文件或文件夹
import { zip } from 'react-native-zip-archive';
// 压缩单个文件或文件夹
zip(sourcePath, targetZipPath)
.then((path) => console.log('压缩完成:', path))
.catch((err) => console.log('压缩失败:', err));
// 压缩多个文件:source 传数组
zip([file1, file2, file3], targetZipPath)
.then((path) => console.log('多文件压缩完成:', path));
// 指定压缩等级:0=不压缩,1=最快,9=最高压缩,-1=默认
zip(sourcePath, targetZipPath, 9).then(...);
文件解压
import { unzip } from 'react-native-zip-archive';
// 解压到指定目录,charset 默认 'UTF-8'
unzip(zipPath, targetDir)
.then((path) => console.log('解压完成:', path));
// 指定编码(处理非 UTF-8 文件名)
unzip(zipPath, targetDir, 'GBK').then(...);
密码压缩与解压
import { zipWithPassword, unzipWithPassword, isPasswordProtected } from 'react-native-zip-archive';
// 密码压缩:encryptionMethod 可选,compressionLevel 可选
zipWithPassword(sourcePath, targetZipPath, '123456')
.then((path) => console.log('密码压缩完成:', path));
// 密码压缩多文件并指定加密方式
zipWithPassword([file1, file2], targetZipPath, '123456', 'AES-256', 9).then(...);
// 解压前先判断是否加密,再决定走密码解压还是普通解压
isPasswordProtected(zipPath).then((encrypted) => {
if (encrypted) {
unzipWithPassword(zipPath, targetDir, '123456')
.then((path) => console.log('密码解压完成:', path));
} else {
unzip(zipPath, targetDir).then(...);
}
});
将资源文件解压到指定目录
import { unzipAssets } from 'react-native-zip-archive';
// 将资源 zip 解压到目标目录(部分平台不支持,会抛出 "unzipAssets not supported on this platform")
unzipAssets(assetPath, targetDir)
.then((path) => console.log('解压完成:', path))
.catch((err) => console.log('解压失败:', err));
获取解压后文件大小
import { getUncompressedSize } from 'react-native-zip-archive';
// 返回解压后总大小(字节),charset 默认 'UTF-8'
getUncompressedSize(zipPath)
.then((size) => console.log('解压后大小:', size, '字节'));
监听压缩与解压进度
import { subscribe, zip } from 'react-native-zip-archive';
// subscribe 返回一个订阅句柄,progress 取值 0~1
const sub = subscribe(({ progress, filePath }) => {
console.log(`${filePath}: ${Math.round(progress * 100)}%`);
});
zip(sourcePath, targetZipPath)
.then(() => {
console.log('完成');
sub.remove(); // 操作结束后移除监听,避免内存泄漏
})
.catch(() => sub.remove());
`subscribe` 返回的是全局事件监听,多个压缩/解压任务会复用同一事件通道。切换任务或组件卸载时,请务必调用 `sub.remove()` 进行清理;否则回调可能会串扰到错误任务的进度展示。
接口说明
"Platform" 列表示该属性在原第三方库中支持的平台。
"OpenHarmony Support" 列为 yes 时,表示 OpenHarmony 平台支持该属性;no 表示不支持;partially 表示部分支持。其使用方法跨平台保持一致,效果与 iOS 或 Android 对齐。
API
| 名称 | 类型 | 参数类型 | 返回值 | 必填 | 平台 | OpenHarmony平台支持 | 描述 |
|---|---|---|---|---|---|---|---|
| zip | function | (source: string | string[], target: string, compressionLevel?: number) |
Promise<string> |
否 | 全部 | 是 | 压缩文件或目录;当 source 传入数组时,可压缩多个文件。compressionLevel 默认值为 -1,取值范围为 -1 到 9,成功后返回压缩文件的路径。 |
| unzip | function | (source: string, target: string, charset?: string) |
Promise<string> |
否 | 全部 | 是 | 解压到 target 目录。charset 用于解码压缩包内文件名,默认值为 UTF-8,支持 UTF-8/GBK/GB2312/Big5/Shift_JIS/sjis。 |
| zipWithPassword | function | (source: string | string[], target: string, password: string, encryptionMethod?: string, compressionLevel?: number) |
Promise<string> |
否 | 全部 | 是 | 使用密码进行压缩。encryptionMethod 支持 STANDARD/AES-128/AES-256;compressionLevel 默认值为 -1,取值范围为 -1 到 9,成功后返回压缩文件的路径。 |
| unzipWithPassword | function | (source: string, target: string, password: string) |
Promise<string> |
否 | 全部 | 是 | 使用密码进行解压。 |
| isPasswordProtected | function | (source: string) |
Promise<boolean> |
否 | 全部 | 是 | 检查压缩包是否设置了密码保护。 |
| unzipAssets | function | (source: string, target: string) |
Promise<string> |
否 | 全部 | 是 | 将资源 zip 文件解压到指定目录。 |
| getUncompressedSize | function | (source: string, charset?: string) |
Promise<number> |
否 | 全部 | 是 | 获取压缩包解压后的总大小(字节)。charset 默认值为 UTF-8,支持 UTF-8/GBK/GB2312/Big5/Shift_JIS/sjis;读取失败时返回 -1。 |
| subscribe | function | (callback: ({ progress, filePath }: { progress: number, filePath: string }) => void) |
NativeEventSubscription |
否 | 全部 | 是 | 订阅压缩/解压进度事件。回调参数为 { progress, filePath },其中 progress 取值范围为 0 到 1,filePath 为当前正在处理的 zip 文件路径。返回 NativeEventSubscription(包含 remove() 方法);该事件为全局单通道且多任务共享,切换任务或卸载组件时须调用 remove() 清理。 |
枚举
| 名称 | 值 | 平台 | HarmonyOS 平台支持 | 描述 |
|---|---|---|---|---|
| DEFAULT_COMPRESSION | -1 | 全部 | 支持 | 默认压缩等级 |
| NO_COMPRESSION | 0 | 全部 | 支持 | 只打包,不压缩 |
| BEST_SPEED | 1 | 全部 | 支持 | 最快速度压缩 |
| BEST_COMPRESSION | 9 | 全部 | 支持 | 最高压缩等级 |
遗留问题
无
其他
- 编译报错“Duplicated files found in module entry. This may cause unexpected errors at runtime. ERROR: 2 file found in 'lin\arm64-v8a\libz.so.1'...”
解决方案:该报错是由于从不同包中收集到同名 so 包(名称为 libz.so.1),导致 so 包冲突。可在模块级 build-profile.json5 文件中添加以下配置:
"buildOption": {
"nativeLib": {
"filter": {
"pickFirsts": ["**/libz.so.1"]
}
}
}
目录结构
/rntpc_react-native-zip-archive # 项目根目录
├── harmony # 鸿蒙适配代码
│ └─ zipArchive_package.har # har 包
│ └─ zipArchive_package # 鸿蒙适配核心代码
│ └─ Index.ets # 鸿蒙适配代码入口
│ └─ ts.ets # ArkTS 侧类型导出入口
│ └─ libs/arm64-v8a # 原生 so 库(libz/libminizip/liblzma/libzstd)
│ └─ src/main
│ └─ ets
│ └─ RNZipArchiveModule.ets # 鸿蒙侧 TurboModule 实现
│ └─ RNZipArchivePackage.ets # 鸿蒙侧 Package
│ └─ cpp
│ └─ RNZipArchiveModule.cpp/.h # C++ 侧 TurboModule 实现
│ └─ RNZipArchivePackage.h # C++ 侧 Package
│ └─ generated # codegen 生成代码
│ └─ thirdparty/minizip-ng # 压缩第三方库
├── index.js # RN 侧入口(导出 zip/unzip 等 API)
├── index.d.ts # RN 侧类型定义
├── specs
│ └─ NativeZipArchive.ts # codegen TurboModule 规范定义
├── example # 示例工程
├── README.md # 中文文档
├── README_en.md # 英文文档
贡献代码
如您在使用过程中遇到任何问题,欢迎提交 Issue,也诚挚欢迎提交 PR 。
开源协议
本项目基于 The MIT License (MIT) ,欢迎自由使用并参与开源共建。