基于android編寫一個下載管理工具類
相信大家在項目開發(fā)的過程中會用到下載相關(guān)的操作,下面是我在工作中用到的下載邏輯管理類,支持下載和取消下載,進度監(jiān)聽功能,能滿足大多數(shù)場景需求,希望對大家有幫助
以下是完整代碼:
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;
/**
* 描述: 下載管理器
* 創(chuàng)建者: 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<>();
// 添加當(dāng)前任務(wù)跟蹤
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 {
// 檢查是否已有相同任務(wù)正在執(zhí)行
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) {
// 在開始處理響應(yīng)前檢查取消狀態(tài)
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) {
// 關(guān)閉流失敗
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;
}
/**
* 移除任務(wù)
*/
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;
}
/**
* 生成任務(wù)唯一標(biāo)識
*/
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;
/**
* 描述:
* 創(chuàng)建者: 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相關(guān)輔助類
package utils;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
/**
* app相關(guān)輔助類
*/
public class AppUtil {
private AppUtil() {
/* cannot be instantiated*/
throw new UnsupportedOperationException("cannot be instantiated");
}
/**
* 獲取應(yīng)用程序名稱
*
* @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;
}
/**
* 獲取應(yīng)用程序版本名稱信息
*
* @param context
* @return 當(dāng)前應(yīng)用的版本名稱
*/
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;
}
/**
* 獲取應(yīng)用程序的版本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 {
/**
* 在指定的位置創(chuàng)建指定的文件
*
* @param filePath 完整的文件路徑
* @param mkdir 是否創(chuàng)建相關(guān)的文件夾
* @throws Exception
*/
public static void mkFile(String filePath, boolean mkdir) throws Exception {
File file = new File(filePath);
file.getParentFile().mkdirs();
file.createNewFile();
file = null;
}
/**
* 在指定的位置創(chuàng)建文件夾
*
* @param dirPath 文件夾路徑
* @return 若創(chuàng)建成功,則返回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();
}
}
/**
* 復(fù)制文件/文件夾 若要進行文件夾復(fù)制,請勿將目標(biāo)文件夾置于源文件夾中
* @param source 源文件(夾)
* @param target 目標(biāo)文件(夾)
* @param isFolder 若進行文件夾復(fù)制,則為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();
}
}
}
/**
* 移動指定的文件(夾)到目標(biāo)文件(夾)
* @param source 源文件(夾)
* @param target 目標(biāo)文件(夾)
* @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編寫一個下載管理工具類的詳細(xì)內(nèi)容,更多關(guān)于android下載管理工具類的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 創(chuàng)建/驗證/刪除桌面快捷方式(已測試可用)
桌面快捷方式的出現(xiàn)方便了用戶操作,在某些程度上提高了用戶體驗,接下來將介紹下Android創(chuàng)建/驗證/刪除桌面快捷方式的實現(xiàn)思路及代碼,感興趣的朋友可以了解下,或許本文可以幫助到你2013-02-02
詳解Android Studio實現(xiàn)用戶登陸界面demo(xml實現(xiàn))
這篇文章主要介紹了詳解Android Studio實現(xiàn)用戶登陸界面demo,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Android TV開發(fā):實現(xiàn)3D仿Gallery效果的實例代碼
這篇文章主要介紹了Android TV開發(fā):實現(xiàn)3D仿Gallery效果的實例代碼,效果:滾動翻頁+ 頁面點擊+頁碼指示器+焦點控制,主要為了移植到電視上做了按鍵事件和焦點控制。2018-05-05
Android 自定義 View 中使用 Spannable的實例詳解
這篇文章主要介紹了Android 自定義 View 中使用 Spannable的相關(guān)知識,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05
Android Flutter基于WebSocket實現(xiàn)即時通訊功能
WebSocket是一種在單個TCP連接上進行全雙工通信的協(xié)議。本文將利用Flutter WebSocket實現(xiàn)即時通訊功能,文中示例代碼講解詳細(xì),感興趣的可以了解一下2022-03-03

