有和无 發表於 2025-11-8 11:04:16

基于android编写一个下载管理工具类

<p>相信大家在项目开发的过程中会用到下载相关的操作,下面是我在工作中用到的下载逻辑管理类,支持下载和取消下载,进度监听功能,能满足大多数场景需求,希望对大家有帮助</p>
<p>以下是完整代码:</p>
<div class="jb51code"><pre class="brush:java;">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&lt;String&gt; unzipModelLiveData = new MutableLiveData&lt;&gt;();

    // 添加当前任务跟踪
    private final Map&lt;String, DownloadTask&gt; currentTasks = new ConcurrentHashMap&lt;&gt;();

    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 &amp;&amp; !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&lt;String, String&gt; buildHeaders() {
      Map&lt;String, String&gt; headers = new HashMap&lt;&gt;();
      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(() -&gt; {
                  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(() -&gt; {
                        if (callback != null) {
                            callback.onCancel(task.url);
                        }
                  });
                  return;
                }

                if (!response.isSuccessful()) {
                  removeTask(task);
                  AtotoLogger.e(TAG, "downloadFile error: " + response.message());
                  mHandler.post(() -&gt; {
                        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;
                  long totalBytesRead = 0;
                  long fileSize = response.body().contentLength();
                  int bytesRead;
                  while ((bytesRead = inputStream.read(buffer)) != -1 &amp;&amp; !task.isCanceled) {
                        totalBytesRead += bytesRead;
                        outputStream.write(buffer, 0, bytesRead);
                        long finalTotalBytesRead = totalBytesRead;
                        mHandler.post(() -&gt; {
                            if (callback != null) {
                              callback.onProgress(task.url, finalTotalBytesRead, fileSize);
                            }
                        });
                  }
                  if (task.isCanceled) {
                        AtotoLogger.d(TAG, "downloadFile canceled: " + task.url);
                        mHandler.post(() -&gt; {
                            if (callback != null) {
                              callback.onCancel(task.url);
                            }
                        });
                  }
                  else{
                        downloadSuccess = true;
                        shouldUnzip = true;
                        mHandler.post(() -&gt; {
                            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(() -&gt; {
                            if (callback != null) {
                              callback.onCancel(task.url);
                            }
                        });
                  } else {
                        mHandler.post(() -&gt; {
                            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) &amp;&amp; destination.exists()) {
                        boolean result = destination.delete();
                        AtotoLogger.d(TAG, "delete file: " + destination.getAbsolutePath() + " result: " + result);
                  }

                  // 只有下载成功且未被取消时才解压
                  if (shouldUnzip &amp;&amp; !task.isCanceled) {
                        // 解压zip文件
                        startUnZip(destination, version, callback);
                  }

                  removeTask(task);
                }
         }
       });
       return task;
    }

    /**
   * 移除任务
   */
    private void removeTask(DownloadTask task) {
      currentTasks.values().removeIf(t -&gt; t.url.equals(task.url));
    }

    private void buildRequestHeader(okhttp3.Request.Builder builder, Map&lt;String, String&gt; heads) {
      if (null == heads || heads.isEmpty()) {
            return;
      }
      for (Map.Entry&lt;String, String&gt; entry : heads.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
      }
    }

    /**
   * 解压并删除zip文件
   * @param destination
   */
    private void startUnZip(File destination, String version, DownloadCallback callback) {
      if (destination.exists() &amp;&amp; destination.getAbsolutePath().endsWith(".zip")) {
            Thread thread = new Thread(()-&gt; {
                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(()-&gt; {
                            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 &gt; 0 &amp;&amp; lastDotIndex &lt; 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);
    }


}
</pre></div>
<p>依赖的解压工具</p>
<div class="jb51code"><pre class="brush:java;">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;
                        int len;
                        while ((len = zis.read(buffer)) &gt; 0) {
                            fos.write(buffer, 0, len);
                        }
                  }
                  zis.closeEntry();
                }
            }
      }
      if (oldDir != null &amp;&amp; oldDir.isDirectory() &amp;&amp; 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");
    }
}
</pre></div>
<p><strong>知识扩展</strong></p>
<p>以下是小编为大家整理的一些Android 常用工具类,有需要的可以参考下</p>
<p>1.app相关辅助类</p>
<div class="jb51code"><pre class="brush:java;">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;
    }
}
</pre></div>
<p>2.文件操作工具类</p>
<div class="jb51code"><pre class="brush:java;">
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 &lt; zfiles; i++) {
                        if (delfile.isDirectory()) {
                            delDir(delfile.getAbsolutePath(), true);
                        }
                        delfile.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 &lt; file.length; i++) {
                if (source.endsWith(File.separator)) {
                  temp = new File(source + file);
                } else {
                  temp = new File(source + File.separator + file);
                }
                if (temp.isFile()) {
                  FileInputStream input = new FileInputStream(temp);
                  FileOutputStream output = new FileOutputStream(target + "/" + (temp.getName()).toString());
                  byte[] b = new byte;
                  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, target + "/" + file, 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;
                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);
      }
    }
}
</pre></div>
<p>以上就是基于android编写一个下载管理工具类的详细内容,更多关于android下载管理工具类的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Android实现获取定位信息的工具类</li><li>Android如何使用Flow封装一个FlowBus工具类</li><li>Android生成二维码工具类封装及使用</li><li>Android实现文件压缩与解压工具类</li><li>Android封装常用工具类的示例详解</li><li>Android&nbsp;DateUtil时间工具类使用方法示例解析</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: 基于android编写一个下载管理工具类