Android文件下载——多文件多线程断点下载

文章目录

1. 前言

在之前的博客中我大概花了几天时间来做那么一个多线程下载的案例,并企图将它封装成一个自己的*库。比如:

最终的项目地址为:https://github.com/baiyazi/AndroidDownloadUtils

但是,由于网络原因Github在我这边确实已经约等于不可访问了。所以这几天开始转向Gitee这个国内的仓库。

无意间发现Gitee中开源项目还挺多的,比如这个页面

所以我决定在Gitee上找几个Android文件下载相关的项目来看看。理解理解别人的思路,然后在继续对之前的项目进行改写。

然后找到了这个项目:zhangdcyf / MulDownload

大概会看下这个项目,然后做一个简单的记录。

2. 学习 MulDownload

首先需要先了解下在AndroidStudio中如何通过类得到这些UML类图。关于类图的介绍可以参考:PlantUML 简介

在之前的博客:UML图,及其使用案例一文中简单介绍了一些基本的类图中的关系,之后会根据这个网站的内容,以及查阅一些资料,继续将这个博客中关于UML图的关系进行简单介绍。

关于如何在AndroidStudio中得到这些UML类图,可以参考文章AndroidStudio UML图生成。这里不再重复介绍。

那么可以得到这个MulDownload的结构如下:
Android文件下载——多文件多线程断点下载
Android文件下载——多文件多线程断点下载
不妨看下这个项目的代码,并对上图进行一个简单的分析:
Android文件下载——多文件多线程断点下载
Android文件下载——多文件多线程断点下载
其实也就是说,最终的核心方法其实在DownloadManagerController的内部类DownloadRunnable中。这里拷贝一下这个类的实现:

/**
 * 开启下载(存在列表下载时)
 *
 * @param mData 数据源
 * @param downloadPath 下载路径
 * @param filePath     文件存放路径
 * @param fileName     文件名称
 * @param position     第几个在下载
 */
public void download(Object mData, String downloadPath, String filePath, String fileName, int position, OnProgressListener onProgressListener) {
    if (TextUtils.isEmpty(filePath)) {
        Log.d(TAG, "please inti filepath");
        return;
    }

    DownloadBean mDownloadBean = DownloadBeanManager.getInstance().get(fileName);
    if (null != mDownloadBean) {
        // 同一个文件是否可以重复下载
        if (DownloadProxy.obtain().getConfigBean().isReset()) {
            com.mul.download.manager.DownloadManager.getInstance().getManager().remove(mDownloadBean.getDownloadId());
            DownloadBeanManager.getInstance().remove(fileName);
        } else {
            if (!TextUtils.isEmpty(DownloadProxy.obtain().getConfigBean().getSubmit())) {
                Toast.makeText(DownloadProxy.obtain().getConfigBean().getContext()
                        , DownloadProxy.obtain().getConfigBean().getSubmit(), Toast.LENGTH_SHORT).show();
            }
            return;
        }
    }

    DownloadStatusManager.getInstance().createDownloadStatusService();
    mExecutorService.execute(new DownloadRunnable(mData, downloadPath, filePath, fileName, position, onProgressListener));
}

private class DownloadRunnable implements Runnable {
    private Object mData;
    private String downloadPath;
    private String filePath;
    private String fileName;
    private int position;
    private OnProgressListener onProgressListener;

    public DownloadRunnable(Object mData, String downloadPath, String filePath, String fileName, int position, OnProgressListener onProgressListener) {
        this.mData = mData;
        this.downloadPath = downloadPath;
        this.filePath = filePath;
        this.fileName = fileName;
        this.position = position;
        this.onProgressListener = onProgressListener;
    }

    @Override
    public void run() {
        Log.i(TAG, downloadPath);
        //创建下载请求
        DownloadManager.Request down = new DownloadManager.Request(Uri.parse(downloadPath));
        //设置允许使用的网络类型,这里是移动网络和wifi都可以
        down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
        //后台下载
        down.setNotificationVisibility(DownloadProxy.obtain().getConfigBean().isNotificationVisibility()
                ? DownloadManager.Request.VISIBILITY_VISIBLE : DownloadManager.Request.VISIBILITY_HIDDEN);
        //显示下载界面
        down.setVisibleInDownloadsUi(DownloadProxy.obtain().getConfigBean().isVisibleInDownloadsUi());
        //设置下载后文件存放的位置
        down.setDestinationInExternalPublicDir(filePath, fileName);
        //将下载请求放入队列
        DownloadBeanManager.getInstance().save(new DownloadBean(onProgressListener, fileName, com.mul.download.manager.DownloadManager.getInstance().getManager().enqueue(down), position, mData));
    }
}

从上买代码可以看见,在download方法中进行了简单的判断是否重复下载后,就将这个任务直接添加到了一个线程池mExecutorService中。

所以具体执行的是DownloadRunnable类中的run方法。

这里不难发现,其实也就是使用AndroidDownloadManager对文件进行简单下载。

整体代码流程下来,其实没有看到对文件断点续传、多线程下载等相关的操作。只是将整个下载包抽离为了一个比较合理的结构。因为是将文件加入到下载线程池中,所以在这里可以处理多文件下载问题。

整体亮点在于这个下载分离解耦,这块确实值得学习。

总体评价来说,没达到自己想要的结果。不过还是按照这个项目的思想对项目进行重构。思想确实挺好的。

3. 仿造

按照MulDownload的分包思想对之前的项目进行重构。这里就不搬单线程的断点下载了,这里直接改造多线程断点下载版本。并且需要修改下存储的逻辑,使得能够支持多文件多线程断点下载。测试效果如下,达到了预期:

Android文件下载——多文件多线程断点下载
为了实现多文件断点下载,所以需要用Map<url, record>类似的结构来记录每个文件的每个线程的下载的数据范围。

  • 使用DownloadProgressHelper来按照下载url存储每个文件的下载记录,存储为Map<String, long[]>格式,每个long类型的数组中存储的是该文件每个线程的当前下载数据位置。
  • 对于单个文件的下载进度,统计上面的long类型数组的所有数据大小即可。
  • 为了达到下次可继续接着上个位置下载,所以这里将下载记录存储到SharedPreferences文件中,具体在SharedPreferencesHelper类中。这里规定在SP文件中存储为Set<String>类型,而由于Set集合的无序性,所以是无序的,故而不能通过下标来标识。所以在存储String的时候使用index#record来标识。存取保持一致即可,同时需要注意的是多线程需要加锁控制。
  • 请求下载过程,首先使用HttpURLConnection来请求一次以获取文件大小,首先判断一次是否本地下载目录的文件大小是否和服务器请求的一致,一致就不需要下载。否则就根据定义的线程数目大小,计算每个线程需要请求的数据范围,然后每个线程去请求各自的数据范围的数据。

至于其余的细节,在之前的文章中其实也介绍过,这里不再重复介绍。包结构为:

Android文件下载——多文件多线程断点下载

对应地址:android-file-down-up-loader-learn/ mylibrary2

4. 后记

这里只是简单重构,后续会继续找找别人的项目,继续封装。

上一篇:Python 发送带附件的邮件


下一篇:RawConfigParser 与 ConfigParser ——Python的配置文件读取模块