基于android编写一个下载管理工具类
作者:IT乐手
相信大家在项目开发的过程中会用到下载相关的操作,下面小编为大家介绍了一个基于android编写的工具类,支持下载和取消下载,进度监听功能,有需要的可以了解下
相信大家在项目开发的过程中会用到下载相关的操作,下面是我在工作中用到的下载逻辑管理类,支持下载和取消下载,进度监听功能,能满足大多数场景需求,希望对大家有帮助
以下是完整代码:
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 描述: 下载管理器
* 创建者: IT 乐手
* 日期: 2025/3/27
*/
public class DownloadManager {
private static final String TAG = "DownloadManager";
private DownloadManager() {
}
private static class SingletonHolder {
private static final DownloadManager INSTANCE = new DownloadManager();
}
public static DownloadManager getInstance() {
return SingletonHolder.INSTANCE;
}
public MutableLiveData<String> unzipModelLiveData = new MutableLiveData<>();
// 添加当前任务跟踪
private final Map<String, DownloadTask> currentTasks = new ConcurrentHashMap<>();
private final Handler mHandler = new Handler(Looper.getMainLooper());
public DownloadTask downloadFile(String url, String version, String key, DownloadCallback callback) throws Exception {
// 检查是否已有相同任务正在执行
String taskKey = generateTaskKey(url, key);
if (currentTasks.containsKey(taskKey)) {
DownloadTask existingTask = currentTasks.get(taskKey);
if (existingTask != null && !existingTask.isCanceled) {
AtotoLogger.e(TAG, "downloadFile: task for " + key + " is already in progress" );
return existingTask;
}
}
File cacheDir = new File(AppGlobalUtils.getApplication().getExternalFilesDir(null), "model");
if (!cacheDir.exists()) {
cacheDir.mkdir();
}
String modelDirName = VoskCachedManager.getInstance().getModelByLang(key);
if (modelDirName == null || modelDirName.isEmpty()) {
throw new Exception(key + " is not supported");
}
File destination = new File(cacheDir, modelDirName + ".zip");
AtotoLogger.d(TAG, "downloadFile: " + url + " to " + cacheDir.getAbsolutePath() + ", version: " + version + ", destination: " + destination.getAbsolutePath());
DownloadTask task = downloadFile(url,version, destination, callback);
currentTasks.put(taskKey, task);
return task;
}
private static @NonNull Map<String, String> buildHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", AccountUseCase.getToken());
headers.put("deviceSeries", DeviceUtil.getPlatform());
return headers;
}
public DownloadTask downloadFile(String url, String version, File destination, DownloadCallback callback) {
DownloadTask task = new DownloadTask(url, destination);
OkHttpClient client = new OkHttpClient();
Request.Builder requestBuilder = new Request.Builder().url(task.url);
buildRequestHeader(requestBuilder, buildHeaders());
Call call = client.newCall(requestBuilder.build());
task.setCall(call);
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
AtotoLogger.e(TAG, "downloadFile error: " + e);
removeTask(task);
mHandler.post(() -> {
if (task.isCanceled) {
if (callback != null) {
callback.onCancel(task.url);
}
} else {
if (callback != null) {
callback.onError(task.url, e.getMessage());
}
}
});
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
// 在开始处理响应前检查取消状态
if (task.isCanceled) {
removeTask(task);
response.close();
AtotoLogger.d(TAG, "downloadFile canceled (before processing): " + task.url);
mHandler.post(() -> {
if (callback != null) {
callback.onCancel(task.url);
}
});
return;
}
if (!response.isSuccessful()) {
removeTask(task);
AtotoLogger.e(TAG, "downloadFile error: " + response.message());
mHandler.post(() -> {
if (callback != null) {
callback.onError(task.url, "Failed to download file: " + response.message());
}
});
return;
}
// 已存在文件,直接删除
if (destination.exists()) {
destination.delete();
}
InputStream inputStream = null;
FileOutputStream outputStream = null;
boolean downloadSuccess = false;
boolean shouldUnzip = false;
try {
assert response.body() != null;
inputStream = response.body().byteStream();
outputStream = new FileOutputStream(destination);
byte[] buffer = new byte[2048];
long totalBytesRead = 0;
long fileSize = response.body().contentLength();
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1 && !task.isCanceled) {
totalBytesRead += bytesRead;
outputStream.write(buffer, 0, bytesRead);
long finalTotalBytesRead = totalBytesRead;
mHandler.post(() -> {
if (callback != null) {
callback.onProgress(task.url, finalTotalBytesRead, fileSize);
}
});
}
if (task.isCanceled) {
AtotoLogger.d(TAG, "downloadFile canceled: " + task.url);
mHandler.post(() -> {
if (callback != null) {
callback.onCancel(task.url);
}
});
}
else {
downloadSuccess = true;
shouldUnzip = true;
mHandler.post(() -> {
if (callback != null) {
callback.onSuccess(task.url, destination.getAbsolutePath());
}
});
}
} catch (Exception e) {
downloadSuccess = false;
AtotoLogger.e(TAG, "downloadFile error: " + e);
if (task.isCanceled) {
mHandler.post(() -> {
if (callback != null) {
callback.onCancel(task.url);
}
});
} else {
mHandler.post(() -> {
if (callback != null) {
callback.onError(task.url, e.getMessage());
}
});
}
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
// 关闭流失败
AtotoLogger.e(TAG, e);
}
// 清理文件:如果取消或失败,删除文件
if ((task.isCanceled || !downloadSuccess) && destination.exists()) {
boolean result = destination.delete();
AtotoLogger.d(TAG, "delete file: " + destination.getAbsolutePath() + " result: " + result);
}
// 只有下载成功且未被取消时才解压
if (shouldUnzip && !task.isCanceled) {
// 解压zip文件
startUnZip(destination, version, callback);
}
removeTask(task);
}
}
});
return task;
}
/**
* 移除任务
*/
private void removeTask(DownloadTask task) {
currentTasks.values().removeIf(t -> t.url.equals(task.url));
}
private void buildRequestHeader(okhttp3.Request.Builder builder, Map<String, String> heads) {
if (null == heads || heads.isEmpty()) {
return;
}
for (Map.Entry<String, String> entry : heads.entrySet()) {
builder.addHeader(entry.getKey(), entry.getValue());
}
}
/**
* 解压并删除zip文件
* @param destination
*/
private void startUnZip(File destination, String version, DownloadCallback callback) {
if (destination.exists() && destination.getAbsolutePath().endsWith(".zip")) {
Thread thread = new Thread(()-> {
try {
FileUnzip.unzipFile(destination, destination.getParentFile(), getFileNameWithoutExtension(destination.getName()));
} catch (Exception e) {
AtotoLogger.e(TAG, e);
}
// 写文件到version.txt
File modelDir = new File(destination.getParentFile(), getFileNameWithoutExtension(destination.getName()));
if (modelDir.isDirectory()) {
File versionFile = new File(modelDir, "version.txt");
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(versionFile);
outputStream.write(version.getBytes());
outputStream.flush();
unzipModelLiveData.postValue(destination.getName());
mHandler.post(()-> {
callback.onUnzipFinish(destination.getName());
});
} catch (IOException e) {
AtotoLogger.e(TAG, e);
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
AtotoLogger.e(TAG, e);
}
}
}
} else {
AtotoLogger.d(TAG, "modelDir: " + modelDir.getAbsolutePath() + " is not a directory");
}
// 删除zip文件
if (destination.exists()) {
boolean result = destination.delete();
AtotoLogger.d(TAG, "delete file: " + destination.getAbsolutePath() + " result: " + result);
}
});
thread.start();
}
}
private String getFileNameWithoutExtension(String fileName) {
// 查找最后一个点的位置
int lastDotIndex = fileName.lastIndexOf(".");
// 如果存在点,并且点不是第一个字符,去掉后缀
if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
fileName = fileName.substring(0, lastDotIndex);
}
return fileName;
}
/**
* 生成任务唯一标识
*/
private String generateTaskKey(String url, String key) {
return url + "_" + key;
}
public static class DownloadTask {
private volatile boolean isCanceled = false;
private String url;
private File destination;
private Call call;
public DownloadTask(String url, File destination) {
this.url = url;
this.destination = destination;
}
public synchronized void cancel() {
if (this.call != null) {
this.call.cancel();
}
isCanceled = true;
}
public synchronized boolean isCanceled() {
return isCanceled;
}
public void setCall(Call call) {
this.call = call;
}
}
public interface DownloadCallback {
void onProgress(String url,long currentSize, long totalSize);
void onSuccess(String url,String filePath);
void onUnzipFinish(String unzipFilePath);
void onError(String url,String error);
void onCancel(String url);
}
}
依赖的解压工具
import com.google.common.io.Files;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* 描述:
* 创建者: IT乐手
* 日期: 2025/7/23
*/
public class FileUnzip {
private static final String TAG = "FileUnzip";
/**
* 解压文件
* @param zipFile 压缩包
* @param targetDirectory 解压的目录
* @param destDirName 解压后的文件夹名称
* @throws IOException
*/
public static void unzipFile(File zipFile, File targetDirectory, String destDirName) throws IOException {
long start = System.currentTimeMillis();
int level = 0;
File oldDir = null;
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry zipEntry;
while ((zipEntry = zis.getNextEntry()) != null) {
File newFile = new File(targetDirectory, zipEntry.getName());
if (zipEntry.isDirectory()) {
if (level == 0) {
oldDir = newFile;
}
newFile.mkdirs();
++level;
} else {
newFile.getParentFile().mkdirs();
AtotoLogger.d("unzip file " + newFile.getAbsolutePath());
try (FileOutputStream fos = new FileOutputStream(newFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
zis.closeEntry();
}
}
}
if (oldDir != null && oldDir.isDirectory() && oldDir.exists()) {
File newDir = new File(oldDir.getParentFile(), destDirName);
AtotoLogger.d("Change oldDir " + oldDir.getAbsolutePath() + " to " + newDir.getAbsolutePath());
try {
Files.move(oldDir, newDir);
AtotoLogger.d("Change success !");
} catch (Exception e) {
AtotoLogger.printException(TAG, e);
}
}
AtotoLogger.d(TAG, "unzipFile: " + zipFile.getAbsolutePath() + " to " + targetDirectory.getAbsolutePath() + " cost: " + (System.currentTimeMillis() - start) + "ms");
}
}
知识扩展
以下是小编为大家整理的一些Android 常用工具类,有需要的可以参考下
1.app相关辅助类
package utils;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
/**
* app相关辅助类
*/
public class AppUtil {
private AppUtil() {
/* cannot be instantiated*/
throw new UnsupportedOperationException("cannot be instantiated");
}
/**
* 获取应用程序名称
*
* @param context
* @return
*/
public static String getAppName(Context context) {
PackageManager packageManager = context.getPackageManager();
try {
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
int labelRes = packageInfo.applicationInfo.labelRes;
return context.getResources().getString(labelRes);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取应用程序版本名称信息
*
* @param context
* @return 当前应用的版本名称
*/
public static String getVersionName(Context context) {
try {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(
context.getPackageName(), 0);
return packageInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取应用程序的版本Code信息
* @param context
* @return 版本code
*/
public static int getVersionCode(Context context) {
try {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 0;
}
}
2.文件操作工具类
package utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
/**
* 文件操作工具类
*/
public class FileUtil {
/**
* 在指定的位置创建指定的文件
*
* @param filePath 完整的文件路径
* @param mkdir 是否创建相关的文件夹
* @throws Exception
*/
public static void mkFile(String filePath, boolean mkdir) throws Exception {
File file = new File(filePath);
file.getParentFile().mkdirs();
file.createNewFile();
file = null;
}
/**
* 在指定的位置创建文件夹
*
* @param dirPath 文件夹路径
* @return 若创建成功,则返回True;反之,则返回False
*/
public static boolean mkDir(String dirPath) {
return new File(dirPath).mkdirs();
}
/**
* 删除指定的文件
*
* @param filePath 文件路径
*
* @return 若删除成功,则返回True;反之,则返回False
*
*/
public static boolean delFile(String filePath) {
return new File(filePath).delete();
}
/**
* 删除指定的文件夹
*
* @param dirPath 文件夹路径
* @param delFile 文件夹中是否包含文件
* @return 若删除成功,则返回True;反之,则返回False
*
*/
public static boolean delDir(String dirPath, boolean delFile) {
if (delFile) {
File file = new File(dirPath);
if (file.isFile()) {
return file.delete();
} else if (file.isDirectory()) {
if (file.listFiles().length == 0) {
return file.delete();
} else {
int zfiles = file.listFiles().length;
File[] delfile = file.listFiles();
for (int i = 0; i < zfiles; i++) {
if (delfile[i].isDirectory()) {
delDir(delfile[i].getAbsolutePath(), true);
}
delfile[i].delete();
}
return file.delete();
}
} else {
return false;
}
} else {
return new File(dirPath).delete();
}
}
/**
* 复制文件/文件夹 若要进行文件夹复制,请勿将目标文件夹置于源文件夹中
* @param source 源文件(夹)
* @param target 目标文件(夹)
* @param isFolder 若进行文件夹复制,则为True;反之为False
* @throws Exception
*/
public static void copy(String source, String target, boolean isFolder)
throws Exception {
if (isFolder) {
(new File(target)).mkdirs();
File a = new File(source);
String[] file = a.list();
File temp = null;
for (int i = 0; i < file.length; i++) {
if (source.endsWith(File.separator)) {
temp = new File(source + file[i]);
} else {
temp = new File(source + File.separator + file[i]);
}
if (temp.isFile()) {
FileInputStream input = new FileInputStream(temp);
FileOutputStream output = new FileOutputStream(target + "/" + (temp.getName()).toString());
byte[] b = new byte[1024];
int len;
while ((len = input.read(b)) != -1) {
output.write(b, 0, len);
}
output.flush();
output.close();
input.close();
}
if (temp.isDirectory()) {
copy(source + "/" + file[i], target + "/" + file[i], true);
}
}
} else {
int byteread = 0;
File oldfile = new File(source);
if (oldfile.exists()) {
InputStream inStream = new FileInputStream(source);
File file = new File(target);
file.getParentFile().mkdirs();
file.createNewFile();
FileOutputStream fs = new FileOutputStream(file);
byte[] buffer = new byte[1024];
while ((byteread = inStream.read(buffer)) != -1) {
fs.write(buffer, 0, byteread);
}
inStream.close();
fs.close();
}
}
}
/**
* 移动指定的文件(夹)到目标文件(夹)
* @param source 源文件(夹)
* @param target 目标文件(夹)
* @param isFolder 若为文件夹,则为True;反之为False
* @return
* @throws Exception
*/
public static boolean move(String source, String target, boolean isFolder)
throws Exception {
copy(source, target, isFolder);
if (isFolder) {
return delDir(source, true);
} else {
return delFile(source);
}
}
}
以上就是基于android编写一个下载管理工具类的详细内容,更多关于android下载管理工具类的资料请关注脚本之家其它相关文章!
