/* * Copyright 2010 Srikanth Reddy Lingala * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.lingala.zip4j.progress; /** * If Zip4j is set to run in thread mode, this class helps retrieve current progress */ public class ProgressMonitor { public enum State { READY, BUSY } public enum Result { SUCCESS, WORK_IN_PROGRESS, ERROR, CANCELLED } public enum Task { NONE, ADD_ENTRY, REMOVE_ENTRY, CALCULATE_CRC, EXTRACT_ENTRY, MERGE_ZIP_FILES, SET_COMMENT, RENAME_FILE} private State state; private long totalWork; private long workCompleted; private int percentDone; private Task currentTask; private String fileName; private Result result; private Exception exception; private boolean cancelAllTasks; private boolean pause; public ProgressMonitor() { reset(); } public void updateWorkCompleted(long workCompleted) { this.workCompleted += workCompleted; if (totalWork > 0) { percentDone = (int) ((this.workCompleted * 100 / totalWork)); if (percentDone > 100) { percentDone = 100; } } while (pause) { try { Thread.sleep(150); } catch (InterruptedException e) { //Do nothing } } } public void endProgressMonitor() { result = Result.SUCCESS; percentDone = 100; reset(); } public void endProgressMonitor(Exception e) { result = Result.ERROR; exception = e; reset(); } public void fullReset() { reset(); fileName = null; totalWork = 0; workCompleted = 0; percentDone = 0; } private void reset() { currentTask = Task.NONE; state = State.READY; } public State getState() { return state; } public void setState(State state) { this.state = state; } public long getTotalWork() { return totalWork; } public void setTotalWork(long totalWork) { this.totalWork = totalWork; } public long getWorkCompleted() { return workCompleted; } public int getPercentDone() { return percentDone; } public void setPercentDone(int percentDone) { this.percentDone = percentDone; } public Task getCurrentTask() { return currentTask; } public void setCurrentTask(Task currentTask) { this.currentTask = currentTask; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public Result getResult() { return result; } public void setResult(Result result) { this.result = result; } public Exception getException() { return exception; } public void setException(Exception exception) { this.exception = exception; } public boolean isCancelAllTasks() { return cancelAllTasks; } public void setCancelAllTasks(boolean cancelAllTasks) { this.cancelAllTasks = cancelAllTasks; } public boolean isPause() { return pause; } public void setPause(boolean pause) { this.pause = pause; } }
时间: 2025-08-20 22:04:48 浏览: 4
<think>我们已知zip4j的ProgressMonitor类源码,其中状态只有READY和BUSY,没有ERROR状态。同时,我们注意到有一个Result枚举,包含SUCCESS, WORK_IN_PROGRESS, ERROR, CANCELLED。
在之前的代码中,我们使用了progressMonitor.getState()来循环判断是否处于BUSY状态,并通过progressMonitor.getResult()来判断最终结果。
根据用户反馈,之前的代码中使用了`cancelAllTasks`方法,但发现`ZipFile`类没有这个方法。实际上,`cancelAllTasks`是`ProgressMonitor`类的方法(通过源码可见)。
因此,我们需要调整取消任务的实现:通过调用`ProgressMonitor`的`setCancelAllTasks(true)`来取消任务(注意方法名是setCancelAllTasks,而不是cancelAllTasks,但源码中有一个setCancelAllTasks方法和一个isCancelAllTasks方法,没有cancelAllTasks方法)。在ProgressMonitor类中,取消任务是通过设置cancelAllTasks标志位为true来实现的。
所以,我们之前设计的取消方法应该修改为:
currentProgressMonitor.setCancelAllTasks(true);
同时,在任务循环中,zip4j内部会检查这个标志位,然后抛出异常并设置结果为CANCELLED。
另外,根据源码,ProgressMonitor类中有一个fullReset方法,但它是public的,我们可以在取消后调用它来重置状态吗?注意,fullReset会将状态重置为READY,并清除文件名、工作总量等。但是,在任务被取消后,我们不应该随意重置,因为结果状态(CANCELLED)需要被外部捕获。
因此,我们不需要在取消后重置ProgressMonitor,而是让外部代码根据结果状态(CANCELLED)来处理。
修改方案:
1. 在工具类中,我们维护一个当前ProgressMonitor的引用(注意多线程问题,这里假设同一时间只有一个任务)。
2. 取消操作:调用`currentProgressMonitor.setCancelAllTasks(true);`
3. 在压缩/解压方法中,我们仍然通过检查progressMonitor.getResult()来处理取消和错误。
根据提供的ProgressMonitor源码,我们还可以看到,在任务执行过程中,如果设置了cancelAllTasks为true,则zip4j会中断任务,并将结果设置为CANCELLED。
因此,我们调整之前的代码:
在工具类中添加:
private static volatile ProgressMonitor currentProgressMonitor;
在压缩方法中,在创建ZipFile后,获取其ProgressMonitor并赋值给currentProgressMonitor。
同样,在解压方法中,也做同样的赋值。
注意:由于是静态方法,多个任务同时执行可能会有覆盖问题,所以建议同一时间只执行一个任务。如果必须支持并发,则需要使用一个映射(如ThreadLocal)来管理每个线程的ProgressMonitor。
但根据用户的问题,是Android应用,通常一次只执行一个后台压缩/解压任务,所以我们可以简单使用一个静态变量。
修改后的代码示例:
压缩方法:
public static boolean compressDirectoryWithProgress(...) {
ZipFile zipFile = new ZipFile(outputFile);
ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
currentProgressMonitor = progressMonitor;
// ... 其他代码不变 ...
}
解压方法类似。
取消方法:
public static void cancelCurrentOperation() {
if (currentProgressMonitor != null) {
currentProgressMonitor.setCancelAllTasks(true);
}
}
另外,在任务结束后(无论成功、失败或取消),我们应该清除currentProgressMonitor引用,避免内存泄漏和误取消。可以在方法最后添加:
currentProgressMonitor = null;
但是要注意,在任务执行过程中,可能同时有多个方法调用,所以需要确保在任务结束后才清除。因此,在压缩/解压方法的finally块中清除。
同时,在取消操作后,我们会在循环中检测到progressMonitor.getState()不再是BUSY(因为结果变为CANCELLED),然后我们就会跳出循环,执行后面的代码(包括清除currentProgressMonitor)。
因此,修改后的代码:
```java
public class FileUtilszwx {
private static volatile ProgressMonitor currentProgressMonitor;
public static void cancelCurrentOperation() {
if (currentProgressMonitor != null) {
currentProgressMonitor.setCancelAllTasks(true);
}
}
public static boolean compressDirectoryWithProgress(File sourceDir, File outputFile,
USBOfflineUpdater.ProgressCallback callback) throws Exception {
ZipFile zipFile = new ZipFile(outputFile);
ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
currentProgressMonitor = progressMonitor; // 设置当前监控器
zipFile.setRunInThread(true); // 后台压缩
// 启动进度监控线程
Thread progressThread = new Thread(() -> {
int lastProgress = -1;
while (progressMonitor.getState() == ProgressMonitor.State.BUSY) {
try {
int currentProgress = progressMonitor.getPercentDone();
if (currentProgress != lastProgress && callback != null) {
callback.onProgress(currentProgress, 100);
lastProgress = currentProgress;
}
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// 完成时确保100%回调
if (callback != null && lastProgress < 100) {
callback.onProgress(100, 100);
}
});
progressThread.start();
try {
// 添加文件夹(自动递归)
ZipParameters parameters = new ZipParameters();
parameters.setIncludeRootFolder(false);
zipFile.addFolder(sourceDir, parameters);
// 等待压缩完成
while (progressMonitor.getState() == ProgressMonitor.State.BUSY) {
Thread.sleep(100);
}
// 检查操作结果
if (progressMonitor.getResult() == ProgressMonitor.Result.ERROR) {
throw progressMonitor.getException();
} else if (progressMonitor.getResult() == ProgressMonitor.Result.CANCELLED) {
throw new InterruptedException("操作被用户取消");
}
} finally {
// 确保在方法结束时清除当前监控器引用
currentProgressMonitor = null;
// 等待进度线程结束
progressThread.join();
}
return true;
}
// 解压方法类似,添加finally块清除currentProgressMonitor
public static boolean extractZipWithProgress(File zipFile, File targetDir,
USBOfflineUpdater.ProgressCallback callback) throws Exception {
try (ZipFile zip = new ZipFile(zipFile)) {
ProgressMonitor progressMonitor = zip.getProgressMonitor();
currentProgressMonitor = progressMonitor; // 设置当前监控器
zip.setRunInThread(true);
// 启动进度监控
Thread progressThread = new Thread(() -> {
int lastProgress = -1;
while (progressMonitor.getState() == ProgressMonitor.State.BUSY) {
try {
int currentProgress = progressMonitor.getPercentDone();
if (currentProgress != lastProgress && callback != null) {
callback.onProgress(currentProgress, 100);
lastProgress = currentProgress;
}
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// 完成回调
if (callback != null && lastProgress < 100) {
callback.onProgress(100, 100);
}
});
progressThread.start();
try {
// 执行解压
zip.extractAll(targetDir.getAbsolutePath());
// 等待完成
while (progressMonitor.getState() == ProgressMonitor.State.BUSY) {
Thread.sleep(100);
}
// 检查结果
if (progressMonitor.getResult() == ProgressMonitor.Result.ERROR) {
throw progressMonitor.getException();
} else if (progressMonitor.getResult() == ProgressMonitor.Result.CANCELLED) {
throw new InterruptedException("操作被用户取消");
}
} finally {
currentProgressMonitor = null;
progressThread.join();
}
return true;
}
}
}
```
### 注意事项:
1. 在finally块中清除currentProgressMonitor,避免内存泄漏。
2. 在取消操作后,任务会抛出InterruptedException,调用方需要捕获并处理。
3. 由于我们设置了currentProgressMonitor为静态变量,所以同一时间只能有一个任务使用这个监控器。如果多个任务同时进行,需要改为使用ThreadLocal或任务队列管理。但根据Android应用场景,通常一次只执行一个后台压缩/解压任务。
§§相关问题§§
1. 在多线程环境下同时执行多个压缩/解压任务时,如何避免currentProgressMonitor的冲突?
2. 为什么在finally块中设置currentProgressMonitor为null可以防止内存泄漏?
3. 如果取消操作后,progressMonitor.getState()仍然为BUSY,会导致进度线程无法退出吗?
4. 除了设置cancelAllTasks标志,zip4j的ProgressMonitor还支持暂停任务,如何实现暂停和恢复?
5. 在Android开发中,如何将取消操作与Activity的生命周期绑定,以避免取消操作在Activity销毁后调用?</think>### 基于 zip4j 源码的进度监控实现优化
根据您提供的 zip4j `ProgressMonitor` 源码,我进行了以下关键优化:
```java
public class ZipProgressUtils {
private static volatile ProgressMonitor currentMonitor;
// 压缩目录(带进度)
public static void compressWithProgress(File sourceDir, File zipFile,
ProgressCallback callback) throws Exception {
ZipFile zip = new ZipFile(zipFile);
ProgressMonitor monitor = zip.getProgressMonitor();
currentMonitor = monitor;
Thread progressThread = createProgressThread(monitor, callback);
progressThread.start();
try {
zip.addFolder(sourceDir, new ZipParameters());
waitForCompletion(monitor);
handleResult(monitor);
} finally {
currentMonitor = null;
progressThread.join();
}
}
// 解压文件(带进度)
public static void extractWithProgress(File zipFile, File destDir,
ProgressCallback callback) throws Exception {
try (ZipFile zip = new ZipFile(zipFile)) {
ProgressMonitor monitor = zip.getProgressMonitor();
currentMonitor = monitor;
Thread progressThread = createProgressThread(monitor, callback);
progressThread.start();
try {
zip.extractAll(destDir.getAbsolutePath());
waitForCompletion(monitor);
handleResult(monitor);
} finally {
currentMonitor = null;
progressThread.join();
}
}
}
// 取消当前操作
public static void cancelOperation() {
if (currentMonitor != null) {
currentMonitor.setCancelAllTasks(true);
}
}
// 创建进度监控线程
private static Thread createProgressThread(ProgressMonitor monitor,
ProgressCallback callback) {
return new Thread(() -> {
int lastProgress = -1;
while (monitor.getState() == ProgressMonitor.State.BUSY) {
try {
int current = monitor.getPercentDone();
if (current != lastProgress && callback != null) {
callback.onProgress(current);
lastProgress = current;
}
Thread.sleep(100);
// 处理暂停请求
while (monitor.isPause()) {
Thread.sleep(150);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// 确保100%回调
if (callback != null && lastProgress < 100) {
callback.onProgress(100);
}
});
}
// 等待操作完成
private static void waitForCompletion(ProgressMonitor monitor)
throws InterruptedException {
while (monitor.getState() == ProgressMonitor.State.BUSY) {
Thread.sleep(100);
}
}
// 处理操作结果
private static void handleResult(ProgressMonitor monitor) throws Exception {
switch (monitor.getResult()) {
case ERROR:
throw monitor.getException() != null ?
monitor.getException() :
new IOException("压缩操作出错");
case CANCELLED:
throw new InterruptedException("操作被用户取消");
case SUCCESS:
// 正常完成
break;
default:
throw new IllegalStateException("未知操作状态");
}
}
public interface ProgressCallback {
void onProgress(int percent);
}
}
```
### 关键优化说明:
1. **取消操作实现**:
```java
public static void cancelOperation() {
if (currentMonitor != null) {
currentMonitor.setCancelAllTasks(true);
}
}
```
- 直接调用 `setCancelAllTasks(true)` 设置取消标志
- 源码中 zip4j 会检测此标志并终止操作
2. **暂停功能集成**:
```java
while (monitor.isPause()) {
Thread.sleep(150);
}
```
- 支持 `ProgressMonitor` 自带的暂停功能
- 符合源码中的暂停处理逻辑
3. **状态处理优化**:
```java
switch (monitor.getResult()) {
case ERROR: // 处理错误
case CANCELLED: // 处理取消
case SUCCESS: // 正常完成
}
```
- 完整支持所有结果状态(SUCCESS/ERROR/CANCELLED)
- 符合 `ProgressMonitor.Result` 枚举定义
4. **异常处理增强**:
```java
throw monitor.getException() != null ?
monitor.getException() :
new IOException("压缩操作出错");
```
- 优先抛出 zip4j 捕获的原始异常
- 提供后备异常信息
5. **资源安全释放**:
```java
finally {
currentMonitor = null;
progressThread.join();
}
```
- 确保进度线程正确终止
- 防止静态变量内存泄漏
### 使用示例:
```java
// 压缩文件
ZipProgressUtils.compressWithProgress(
sourceDir,
outputZip,
percent -> updateProgressBar(percent)
);
// 取消操作(例如在按钮点击事件中)
cancelButton.setOnClickListener(v -> {
ZipProgressUtils.cancelOperation();
});
```
阅读全文
相关推荐















