【Android】安卓 Java下载ZIP文件并解压(笔记)
写在前面的话
在这篇文章中,我们将详细讲解如何在 Android 中通过 Java 下载 ZIP 文件并解压,同时处理下载进度、错误处理以及优化方案。
以下正文
1.权限配置
在 AndroidManifest.xml 中,我们需要添加相应的权限来确保应用能够访问网络和设备存储。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
⚠️ 注意:Android 10(API 29)及以上,建议使用 getExternalFilesDir() 来处理文件,而避免直接访问 /sdcard。
2.下载 ZIP 文件并保存到本地
文件下载
首先,我们需要编写下载 ZIP 文件的代码。这里使用 HttpURLConnection 来实现文件的下载,并保存到设备的存储中。
public void downloadZipFile(String urlStr, String savePath, DownloadCallback callback) {
new Thread(() -> {
InputStream input = null;
FileOutputStream output = null;
HttpURLConnection connection = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
connection.connect();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
callback.onFailed("Server returned HTTP " + connection.getResponseCode());
return;
}
int fileLength = connection.getContentLength();
input = connection.getInputStream();
File file = new File(savePath);
output = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int bytesRead;
long total = 0;
while ((bytesRead = input.read(buffer)) != -1) {
total += bytesRead;
output.write(buffer, 0, bytesRead);
int progress = (int) (total * 100 / fileLength);
callback.onProgress(progress);
}
callback.onSuccess(file.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
callback.onFailed(e.getMessage());
} finally {
try {
if (output != null) output.close();
if (input != null) input.close();
} catch (IOException ignored) {}
if (connection != null) connection.disconnect();
}
}).start();
}
下载回调
为了处理下载过程中的进度、成功与失败,我们需要定义一个回调接口:
public interface DownloadCallback {
void onProgress(int progress); // 下载进度
void onSuccess(String filePath); // 下载完成
void onFailed(String error); // 下载失败
}
3.解压 ZIP 文件’
文件解压
下载完成后,我们可以解压 ZIP 文件。Android 提供了 ZipInputStream 来处理解压工作。以下是解压代码实现:
public void unzip(String zipFilePath, String targetDirectory, UnzipCallback callback) {
new Thread(() -> {
try {
File destDir = new File(targetDirectory);
if (!destDir.exists()) {
destDir.mkdirs();
}
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry zipEntry;
byte[] buffer = new byte[1024];
while ((zipEntry = zis.getNextEntry()) != null) {
File newFile = new File(destDir, zipEntry.getName());
if (zipEntry.isDirectory()) {
newFile.mkdirs();
} else {
// 确保父目录存在
new File(newFile.getParent()).mkdirs();
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
}
zis.closeEntry();
}
zis.close();
callback.onSuccess(targetDirectory);
} catch (IOException e) {
e.printStackTrace();
callback.onFailed(e.getMessage());
}
}).start();
}
解压回调
为了处理解压过程中的状态,我们也需要一个回调接口:
public interface UnzipCallback {
void onSuccess(String targetPath); // 解压成功
void onFailed(String error); // 解压失败
}
4.断点续传
对于较大的文件下载,可能需要实现断点续传功能。为了实现这一点,我们可以在下载时存储已下载的字节数,并在中断后继续下载。
修改 downloadZipFile 方法,使用 Range 头来支持断点续传:
public void downloadZipFileWithResume(String urlStr, String savePath, DownloadCallback callback) {
new Thread(() -> {
InputStream input = null;
FileOutputStream output = null;
HttpURLConnection connection = null;
try {
File file = new File(savePath);
long downloadedLength = file.exists() ? file.length() : 0;
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
connection.setRequestProperty("Range", "bytes=" + downloadedLength + "-");
connection.connect();
if (connection.getResponseCode() != HttpURLConnection.HTTP_PARTIAL &&
connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
callback.onFailed("Server returned HTTP " + connection.getResponseCode());
return;
}
int fileLength = connection.getContentLength() + (int) downloadedLength;
input = connection.getInputStream();
output = new FileOutputStream(file, true); // 以追加方式写入
byte[] buffer = new byte[4096];
int bytesRead;
long total = downloadedLength;
while ((bytesRead = input.read(buffer)) != -1) {
total += bytesRead;
output.write(buffer, 0, bytesRead);
int progress = (int) (total * 100 / fileLength);
callback.onProgress(progress);
}
callback.onSuccess(file.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
callback.onFailed(e.getMessage());
} finally {
try {
if (output != null) output.close();
if (input != null) input.close();
} catch (IOException ignored) {}
if (connection != null) connection.disconnect();
}
}).start();
}
5.调用示例
以下是如何使用我们实现的功能来下载、解压并处理回调:
// 下载地址和存储路径
String zipUrl = "https://example.com/path/to/file.zip";
File downloadDir = context.getExternalFilesDir(null); // 应用私有目录
String zipFilePath = new File(downloadDir, "downloaded_file.zip").getAbsolutePath();
String unzipTargetDir = new File(downloadDir, "unzipped_folder").getAbsolutePath();
// 下载 ZIP 文件
downloadZipFile(zipUrl, zipFilePath, new DownloadCallback() {
@Override
public void onProgress(int progress) {
Log.d("Download", "Progress: " + progress + "%");
}
@Override
public void onSuccess(String filePath) {
Log.d("Download", "Download completed: " + filePath);
// 解压 ZIP 文件
unzip(filePath, unzipTargetDir, new UnzipCallback() {
@Override
public void onSuccess(String targetPath) {
Log.d("Unzip", "Unzip completed at: " + targetPath);
}
@Override
public void onFailed(String error) {
Log.e("Unzip", "Unzip failed: " + error);
}
});
}
@Override
public void onFailed(String error) {
Log.e("Download", "Download failed: " + error);
}
});
6.常见问题与优化建议
在实际开发中,下载和解压 ZIP 文件的过程中会遇到一些常见的问题。以下是一些优化建议和处理方法:
网络连接中断
在进行大文件下载时,网络连接可能会中断,导致下载失败。为了避免重复下载,建议使用断点续传技术。断点续传技术通过记录文件下载的进度,从而在网络中断后可以从中断位置继续下载,而不是重新开始。
优化建议:
- 使用 Range 请求头来实现文件下载的断点续传(如上文所示)。
- 在网络中断时,将已经下载的字节数保存在本地文件中,以便恢复下载。
文件解压失败
在解压 ZIP 文件时,如果文件结构复杂或者出现损坏,可能会导致解压失败。确保文件完整性是防止解压失败的关键。
优化建议:
- 在解压前,可以验证 ZIP 文件的完整性,确保下载完成且未损坏。
- 使用 ZipFile 类可以简化一些 ZIP 文件的解压操作,并能更好地处理压缩包中的特殊情况(如加密压缩包)。
性能优化
- 缓冲区大小:在下载和解压文件时,使用适当大小的缓冲区(例如 4096 字节)可以提升性能。
- UI 线程阻塞:确保所有的网络和解压操作都在后台线程中执行,避免阻塞 UI 线程,导致应用无响应。
文件存储权限问题
在 Android 10 及以上版本,Google 对外部存储的访问权限做出了更严格的限制。你需要使用 getExternalFilesDir() 方法来存储文件,这个目录是私有的,仅限应用本身访问,且不会在卸载应用时删除。
优化建议:
- 使用 getExternalFilesDir() 或 MediaStore 来确保兼容性。
- 针对 Android 11 及以上版本,需申请 MANAGE_EXTERNAL_STORAGE 权限以便访问共享存储。
文件大小限制
大文件的下载和解压会占用大量存储空间,尤其是当设备存储较满时,可能会导致下载失败或存储不足的问题。
优化建议:
- 在下载前,检查设备存储空间是否足够,并提示用户进行清理。
- 如果需要处理大文件,考虑在下载过程中显示文件大小,并在下载完成前通过 onProgress 更新下载进度。