Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > android下载管理工具类

基于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下载管理工具类的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文