package com.kotei.overseas.navi.update;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class FileUtils {
private static final String TAG = "FileUtils";
public static final int BUFFER_SIZE = 10 * 1024 * 1024; // 10MB缓冲区
private static long totalSize = 0;
private static long processedSize = 0;
private static long lastCheckTime = 0;
/**
* 带进度压缩目录
*
* @param sourceDir 源目录
* @param outputFile 输出文件
* @param callback 进度回调
* @return 是否成功
*/
public static boolean compressDirectoryWithProgress(File sourceDir, File outputFile,
USBOfflineUpdater.ProgressCallback callback) {
// 检查参数是否为 null
if (sourceDir == null) {
throw new IllegalArgumentException("Source directory cannot be null");
}
if (outputFile == null) {
throw new IllegalArgumentException("Output file cannot be null");
}
totalSize = getDirectorySize(sourceDir);
try (FileOutputStream fos = new FileOutputStream(outputFile);
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos))) {
zos.setLevel(Deflater.BEST_SPEED);
// 压缩目录
boolean compressResult = zipSubDirectoryWithProgress("", sourceDir, zos, callback, true);
// 3. 压缩完成后删除原始数据
if (compressResult) {
Log.i(TAG, "压缩成功,开始删除原始数据...");
return deleteDirectoryContents(sourceDir);
}
return false;
} catch (IOException e) {
Log.e(TAG, "压缩目录失败", e);
return false;
}
}
/**
* 带进度压缩子目录
*
* @param basePath 基础路径
* @param dir 当前目录
* @param zos Zip输出流
* @param callback 进度回调
* @param deleteAfterAdd 添加后是否删除文件
* @return 是否成功
*/
private static boolean zipSubDirectoryWithProgress(String basePath, File dir,
ZipOutputStream zos,
USBOfflineUpdater.ProgressCallback callback,
boolean deleteAfterAdd)
throws IOException {
// 检查参数是否为 null
if (dir == null) {
throw new IllegalArgumentException("Directory cannot be null");
}
if (zos == null) {
throw new IllegalArgumentException("ZipOutputStream cannot be null");
}
byte[] buffer = new byte[BUFFER_SIZE];
File[] files = dir.listFiles();
if (files == null) return true;
// 按文件大小排序,先处理大文件
Arrays.sort(files, (f1, f2) -> {
if (f1.isDirectory() && !f2.isDirectory()) return -1;
if (!f1.isDirectory() && f2.isDirectory()) return 1;
return Long.compare(f2.length(), f1.length()); // 大文件优先
});
for (File file : files) {
if (Thread.currentThread().isInterrupted()) {
return false;
}
String path = basePath + file.getName();
if (file.isDirectory()) {
// 递归处理子目录
if (!zipSubDirectoryWithProgress(path + File.separator, file, zos, callback, deleteAfterAdd)) {
return false;
}
// 删除空目录
if (deleteAfterAdd && file.listFiles().length == 0) {
if (!file.delete()) {
Log.w(TAG, "删除空目录失败: " + file.getAbsolutePath());
}
}
} else {
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis)) {
zos.putNextEntry(new ZipEntry(path));
int length;
while ((length = bis.read(buffer)) > 0) {
zos.write(buffer, 0, length);
processedSize += length;
// 更新进度
if (callback != null) {
callback.onProgress(processedSize, totalSize);
}
}
zos.closeEntry();
// 压缩后立即删除文件
if (deleteAfterAdd) {
if (!file.delete()) {
Log.w(TAG, "删除文件失败: " + file.getAbsolutePath());
} else {
Log.d(TAG, "已删除: " + file.getAbsolutePath());
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
return true;
}
/**
* 计算目录大小
*
* @param directory 目录
* @return 目录大小(字节)
*/
public static long getDirectorySize(File directory) {
long size = 0;
if (directory == null || !directory.exists()) return 0;
if (directory.isDirectory()) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
size += getDirectorySize(file);
}
}
} else {
size = directory.length();
}
return size;
}
// 静态内部类:解压任务
private static class ExtractTask implements Callable<Long> {
private final ZipFile zip;
private final ZipEntry entry;
private final File file;
private final USBOfflineUpdater.ProgressCallback callback;
private final long totalSize;
public ExtractTask(ZipFile zip, ZipEntry entry, File file,
USBOfflineUpdater.ProgressCallback callback, long totalSize) {
this.zip = zip;
this.entry = entry;
this.file = file;
this.callback = callback;
this.totalSize = totalSize;
}
@Override
public Long call() throws IOException {
long entrySize = 0;
try (InputStream in = zip.getInputStream(entry);
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos, 128 * 1024)) {
byte[] buffer = new byte[128 * 1024];
int length;
while ((length = in.read(buffer)) > 0) {
bos.write(buffer, 0, length);
entrySize += length;
// 定期报告进度(每100MB) zwx修改-对应:Skipped 98 frames! The application may be doing too much work on its main thread.
if (callback != null && entrySize % (100 * 1024 * 1024) == 0) {
synchronized (FileUtils.class) {
callback.onProgress(entrySize, totalSize);
}
}
}
bos.flush();
} catch (IOException e) {
Log.e(TAG, "解压文件失败: " + file.getName(), e);
throw e;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return entrySize;
}
}
/**
* 带进度解压ZIP文件
*
* @param zipFile ZIP文件
* @param targetDir 目标目录
* @param callback 进度回调
* @return 是否成功
*/
public static boolean extractZipWithProgress(File zipFile, File targetDir,
USBOfflineUpdater.ProgressCallback callback) {
long totalSize = zipFile.length();
AtomicLong processedSize = new AtomicLong(0); // 使用原子类型保证线程安全
// 创建线程池(4线程)
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<Long>> futures = new ArrayList<>();
try (ZipFile zip = new ZipFile(zipFile)) {
// 第一遍:预创建所有目录
Map<String, Boolean> createdDirs = new HashMap<>();
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.isDirectory()) {
File dir = new File(targetDir, entry.getName());
if (!dir.exists() && !dir.mkdirs()) {
Log.w(TAG, "创建目录失败: " + dir.getAbsolutePath());
}
createdDirs.put(entry.getName(), true);
}
}
// 第二遍:并行解压文件
entries = zip.entries();
while (entries.hasMoreElements()) {
final ZipEntry entry = entries.nextElement();
if (entry.isDirectory()) continue;
// 确保父目录存在
File file = new File(targetDir, entry.getName());
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
if (!parent.mkdirs()) {
Log.w(TAG, "创建父目录失败: " + parent.getAbsolutePath());
}
}
// 提交解压任务
futures.add(executor.submit(new ExtractTask(
zip, entry, file, callback, totalSize
)));
}
// 等待所有任务完成并统计进度
for (Future<Long> future : futures) {
try {
long size = future.get();
processedSize.addAndGet(size);
// 更新总进度
if (callback != null) {
callback.onProgress(processedSize.get(), totalSize);
}
} catch (ExecutionException e) {
Log.e(TAG, "解压任务失败", e.getCause());
}
}
return true;
} catch (IOException | InterruptedException e) {
Log.e(TAG, "解压失败: " + zipFile.getName(), e);
return false;
} finally {
executor.shutdownNow();
}
}
/**
* 带进度拷贝文件
*
* @param src 源文件
* @param dest 目标文件
* @param callback 进度回调
* @return 是否成功
*/
public static boolean copyFileWithProgress(File src, File dest,
USBOfflineUpdater.ProgressCallback callback) {
long totalSize = src.length();
long copiedSize = 0;
try (InputStream in = new BufferedInputStream(new FileInputStream(src));
OutputStream out = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[BUFFER_SIZE];
int length;
while ((length = in.read(buffer)) > 0) {
if (Thread.currentThread().isInterrupted()) {
return false;
}
out.write(buffer, 0, length);
copiedSize += length;
// 优化后的检查逻辑(每秒最多1次)
long currentTime = System.currentTimeMillis();
if (currentTime - lastCheckTime > 1000) {
if (Thread.currentThread().isInterrupted()) {
return false;
}
if (USBOfflineUpdater.getInstance().isCancelled) {
return false;
}
// 更新进度
if (callback != null) {
callback.onProgress(copiedSize, totalSize);
}
lastCheckTime = currentTime;
}
}
return true;
} catch (IOException e) {
Log.e(TAG, "拷贝失败: " + src.getName(), e);
return false;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 压缩目录
*
* @param sourceDir 源目录
* @param outputFile 输出文件
* @return 是否成功
*/
public static boolean compressDirectory(File sourceDir, File outputFile) {
try (FileOutputStream fos = new FileOutputStream(outputFile);
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos))) {
zipSubDirectory("", sourceDir, zos);
return true;
} catch (IOException e) {
Log.e(TAG, "压缩目录失败", e);
return false;
}
}
private static void zipSubDirectory(String basePath, File dir,
ZipOutputStream zos) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
String path = basePath + file.getName();
if (file.isDirectory()) {
zipSubDirectory(path + File.separator, file, zos);
} else {
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis)) {
zos.putNextEntry(new ZipEntry(path));
int length;
while ((length = bis.read(buffer)) > 0) {
zos.write(buffer, 0, length);
}
zos.closeEntry();
}
}
}
}
/**
* 解压ZIP文件
*
* @param zipFile ZIP文件
* @param targetDir 目标目录
* @return 是否成功
*/
public static boolean extractZip(File zipFile, File targetDir) {
return extractZipWithProgress(zipFile, targetDir, null);
}
/**
* 递归删除文件/目录
*
* @param file 要删除的文件或目录
* @return 是否成功
*/
public static boolean deleteRecursive(File file) {
boolean success = true;
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
success &= deleteRecursive(child);
}
}
}
if (!file.delete()) {
Log.w(TAG, "删除失败: " + file.getAbsolutePath());
success = false;
}
return success;
}
/**
* 删除目录内容(保留目录本身)
*
* @param directory 要清空的目录
* @return 是否成功
*/
public static boolean deleteDirectoryContents(File directory) {
if (directory == null || !directory.exists() || !directory.isDirectory()) {
return false;
}
File[] files = directory.listFiles();
if (files == null) return true; // 空目录
boolean success = true;
for (File file : files) {
if (file.isDirectory()) {
success &= deleteRecursive(file);
} else {
if (!file.delete()) {
Log.w(TAG, "删除文件失败: " + file.getAbsolutePath());
success = false;
}
}
}
return success;
}
}
检查目前的删除方式