Android 多线程文件断点下载器实现(造轮子系列)(一)

1.介绍

慢慢造轮子,在造轮子的过程中学习android相关的原理和设计模式。先开始一步步实现多线程文件断点下载器。

这次的多线程文件断点下载器,要实现以下几点:
1. 断点续传,不只是单纯的点个暂停开始,而是在退出重进之后仍然有任务的进度,这个就需要用到数据持久化了。
2. 多任务并行下载,一定数量的任务并行下载,超过额定值的任务暂停等待。
3. 单任务多线程下载,这个需要服务端的支持

2.从零开始

这个从0开始不是说从新建工程开始,而是从简单的下载一个文件开始。
这次网络请求用的库是okhttp,当然用URLConnection也可以。
先来看一下下载文件的代码,很简单,新建一个线程进行下载文件的耗时操作。

@Override
public void run()
{
    String url = ""; //文件的url地址
    OkHttpClient client = new OkHttpClient(); //okhttp client
    RandomAcessFile file;
    BufferedInputStream bis = null; //读取okhttp响应的流
    String fileSavePath = ""; //文件路径

    try {
        //构建okhttp的请求
        Request request = new Request.Builder()
                .url(url)
                .build();
        Response response = client.newCall(request).execute();

        //获得响应内容
        ResponseBody responseBody = response.body();

        //做一个判断,如果返回为空,则不进行下面的下载
        if (responseBody == null) {
            System.out.println("resource not found");
            return;
        }

        //不为空,获取流,并且创建文件流
        bis = new BufferedInputStream(response.body().byteStream());
        file = new RandomAcessFile(fileSavePath, "rwd");

        //创建一个缓冲区,用来读写文件,设置一个合适的大小,防止占用过多内存
        byte[] buff = new byte[5 * 1024];
        int len;
        while ((len = bis.read(buff)) > 0) {
            file.write(buff, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //关闭流
        try {
            if (fos != null)
                fos.close();
            if (bis != null)
                bis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这就完成了一个简单任务下载的过程,从指定的url处下载文件,并保存到SD卡。接下来开始尝试加入断点下载功能。

3.断点下载

先规划一下断点下载最基本的功能

  • 可以控制任务切换下载状态,并从正确的地方继续下载(如果服务端不支持断点,则重新下载)
  • 可以保存下载进度
  • 可以判断服务端是否支持断点下载,并采用不同的策略

第一点,可以利用一组变量表示当前一个任务的下载状态,通过改变这个变量来间接控制任务,通过读取变量获得任务当前的状态。

public class LoadState {
    public static final int PREPARE=1;
    public static final int START=2;
    public static final int DOWNLOADING=3;
    public static final int PAUSE=4;
    public static final int NET_ERROR=5;
    public static final int COMPLETED=6;
    public static final int CANCEL=7;
}

因为没有同时拥有两种状态的情况,就用简单的值表示。
修改一下上面的代码,让任务过程和状态关联起来,并加入数据储存。这里使用的是SQLite,为了方便,用了GreenDao,生成代码入下

private static void addDownloadEntity(Schema schema)
{
    Entity entity = schema.addEntity("DownloadEntity");
    entity.addStringProperty("url").primaryKey();
    entity.addLongProperty("taskSize");
    entity.addLongProperty("completedSize");
    entity.addStringProperty("saveDirPath");
    entity.addStringProperty("fileName");
}

需要保存的主要有

protected long taskSize;     //文件大小
protected long completedSize;   //已完成大小

protected String url;   //地址

protected String fileName;  //文件名
protected String saveDirPath;   //保存地址

每写入一段数据,就储存一下进度到数据库,这样即使网络突然中断数据也不会丢失。
断点续传的主要实现就是使用Http中的Range头来进行断点请求,令服务器返回Range头指定的范围。这里也可以判断服务器是否支持断点下载,支持断点下载的服务器响应里会有Content-Range头,如果不含Content-Range头,则就不需要保存进度,暂停后恢复需要重新下载。判断的主要方法如下。

String Content_Range = response.header("Content-Range");
if (Content_Range == null) {
    //if not support , get content length and use single thread process download
    taskSize = responseBody.contentLength();
} else
    //else get length from head content range or
    //directly get by content length(request use Range: byte:0-)
    taskSize = Long.parseLong(Content_Range.substring(Content_Range.lastIndexOf("/") + 1));

保存进度的方法就是记录进度,在每读取一段数据之后将记录写入数据库,将开始代码的写入部分改成这样即可。

int len;
while ((len = bis.read(buffer)) > 0 && state == LoadState.DOWNLOADING) {
    file.write(buffer, 0, length);
    completedSize += length;
    //更新进度并保存
    downloadEntity.setCompletedSize(completedSize);
    downloadDao.insertOrReplace(downloadEntity);
}

既然保存了,那么就要读取。这里读取分两种情况,在未退出Application的前提下读取,和重新进入Application的情况下读取。
其实这两种情况并没有什么区别,都是需要重新开启一个新的线程进行下载,所以在开启线程的时候读取数据库,看看有没有数据就可以了。
具体的代码就不写了。

4.结束

断点续传到这里差不多就完成了,下一篇博文开始多任务并行下载。

具体代码可以查看我的github项目。

你可能感兴趣的:(设计模式,多线程,android,文件下载,数据)