最近学习在Android环境中一些网络请求方面的知识,其中有一部分是关于网络下载方面的知识。在这里解析一下自己写的demo,总结一下自己所学的知识。下图为demo的效果图,仿照一些应用下载商城在ListView中列出加载项,然后可以可以下载和停止。

1.概述

这里有几个比较重要的类DownloadManager、DownloadService、DownloadTask、ThreadDAOImpl。主要的下载流程如下。
(1) DownloadManager 负责下载任务的调配,以及下载服务DownloadService的启动
(2) DownloadService 主获取下载文件的的一些信息,包括文件的名字、文件的长度等,并创建下载任务DownloadTask
(3) DownloadTask 是正式下载文件的类,首先查看数据库里有没保存过相应的断点,并从相应的断点开始下载,如果没有则将文件分段,并启动下载
(4) ThreadDAOImpl 数据库操作类,主要是保存线程下载的断点信息

2.多线程断点续传

当然这里最核心的部分就是多线程断点续传,原来不是很难,就是将要下载的文件分割成多个部分,每个部分使用的不同的线程同时下载。

2.1获取下载文件长度,设置本地文件

在DownloadService 设置下载文件的信息,如下一段代码:

 class InitThread extends Thread {
// FileInfo fileInfo;
TaskInfo taskInfo;
public InitThread (TaskInfo taskInfo) {
this.taskInfo = taskInfo;
}
@Override
public void run() {
super.run();
Log.i(tag,"InitThread");
try {
URL url = new URL(taskInfo.getUrl());
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setConnectTimeout();
if(con.getResponseCode() == HttpURLConnection.HTTP_OK) {
int len = con.getContentLength(); <span style="color:#ff0000;">//文件的总长度</span>
taskInfo.setLenght(len);
if(len <= ) {
return;
}
…………此处省略部分
//start 设置下载文件
<span style="color:#ff6666;"> RandomAccessFile accessFile = new RandomAccessFile(new File(taskInfo.getFilePath(),taskInfo.getFileName()),"rwd");
accessFile.setLength(len); //设置文件长度</span>
accessFile.close();
//end 设置下载文件
…………此处省略部分 }
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

2.2 文件分段

接下的工作是分段下载
举个列子一个10M的文件,分成三份求整10/3 = 3,前面的一二份分别是3M,最后一份是4M。所以第一份从0~2.9,第二份是从3~5.9,第三份是从6~10,这里只是粗来的说明。接下来看代码,在DownloadTask中有如下有如下代码:
/**
* 启动下载
*/
public void downlaod() { …………此处省略部分
//start 数据库没有对应的线程信息,则创建相应的线程信息
if(threadInfoList.size() <=) {
<span style="color:#ff6666;">int block = mTaskInfo.getLenght()/mThreadCount; //将下载文件分段,每段的长度</span>
if(block > ) {
//start 根据线程数量分别建立线程信息
for(int i = ;i < mThreadCount;i++) {
ThreadInfo info = new ThreadInfo(i,mTaskInfo.getUrl(),i*block,(i+)*block-,);
if(i == mThreadCount -) {
<span style="color:#ff0000;"> info.setEnd(mTaskInfo.getLenght()); //分段最后一个,结束位置到文件总长度末尾</span>
}
threadInfoList.add(info); //加入列表
mThreadDao.insertThread(info); //向数据库插入线程信息
}
//end 根据线程数量分别建立线程信息
}else {
ThreadInfo info = new ThreadInfo(,mTaskInfo.getUrl(),,mTaskInfo.getLenght(),);
threadInfoList.add(info);
mThreadDao.insertThread(info);
}
}
//end 数据库中没有对应的线程信息,则创建相应的线程信息 …………此处省略部分
}

2.3下载线程

下面是主要的下载文件的线程

主要的是设置开始读取和结束的地方:

con.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd()); //设置读取文件的位置,和结束位置

写入本地文件的地方:

accessFile.seek(start);    //设置开始写入的位置
/**
* 下载线程
*/
class DownloadThread extends Thread {
…………此处省略部分
@Override
public void run() {
…………此处省略部分
int start = threadInfo.getStart()+threadInfo.getFinished(); //读取文件的位置
//start 初始化下载链接
…………此处省略部分
con.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd()); //设置读取文件的位置,和结束位置
//end 初始化下载链接
//start 初始化下载到本地的文件
<span style="color:#ff0000;">accessFile = new RandomAccessFile(new File(mTaskInfo.getFilePath(), mTaskInfo.getFileName()),"rwd");
accessFile.seek(start); //设置开始写入的位置</span>
//end 初始化下载到本地的文件 …………此处省略部分
while((readLen = inputStream.read(buffer))!=-) {
<span style="background-color: rgb(255, 255, 255);"><span style="color:#ff0000;"> accessFile.write(buffer, , readLen);</span></span>
// Log.i(tag, "readLen = " + readLen);
finished += readLen;
threadInfo.setFinished(finished); //设置已经下载进度
if(System.currentTimeMillis() - time >) {
// Log.i(tag, "readLen = " + readLen);
notifyProgress(threadInfo.getId(), finished); //每隔2秒通知下载进度
time = System.currentTimeMillis();
}
//start 停止下载,保存进度
if(isPause) {
Log.i(tag,"pause name = "+mTaskInfo.getFileName());
notifyProgress(threadInfo.getId(), finished); //通知下载进度
mThreadDao.updateThread(threadInfo.getUrl(),threadInfo.getId(),finished); //更新数据库对应的线程信息
return;
}
//end 停止下载,保存进度
}
//end 读取输入流写入文件 …………此处省略部分 }
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally { …………此处省略部分
}
super.run();
}
}

2.4保存断点

在上面的DownloadThread下载线程中保存断点信息,是使用数据库形式保存的。

mThreadDao.updateThread(threadInfo.getUrl(),threadInfo.getId(),finished);  //更新数据库对应的线程信息

3其他辅助类

(1) 数据库操作类ThreadDAOImpl,断点信息的增、改、查、删。
(2) 回调接口OnDownload,下载进度以及下载完成
(3) 下载任务信息TaskInfo
(4) 线程信息ThreadInfo

4线程池

由于使用到多线程同时下载,这里在使用了线程池管理。在DownloadService类中创建并初始化线程池,按照CPU核心数量乘以2再加1设置线程池中线程的数量
mThreadPool = Executors.newFixedThreadPool(getNumberOfCPUCores()*2+1);  //初始化线程
在下载时使用三个线程同时下载
DownloadTask task = new DownloadTask(DownloadService.this,info,mThreadPool,3); //建立下载任务,3个线程同时下载

由于线程池内的线程数量是有限的,当启动下载之后有空闲的线程会马上执行,如果没有就只能等待下载任务完成再下载。

最新文章

  1. weak和nonull
  2. [Python] python vs cplusplus
  3. eclipse导入第三方jar包进入web项目的方法
  4. ROW_NUMBER() OVER的用法
  5. xampp笔记
  6. css案例学习之table tr th td ul li实现日历
  7. [转]Converting a C library to gyp
  8. 汇编语言实现led灯的跑马灯
  9. ASP.NET MVC @Html.Label的问题
  10. Java图形界面编程生成exe文件
  11. OnePlus5刷机后一直检查更新
  12. vim学习纪要
  13. Android向系统日历中添加日程事件
  14. BootstrapTable(附源码)
  15. css动画和js动画的差异
  16. [UE4]复制引起的重复对象
  17. canvas-圆弧形可拖动进度条
  18. Cookie介绍
  19. CentOS系统yum源配置修改、yum安装软件包源码包出错解决办法apt.sw.be couldn&#39;t connect to host
  20. HDU 2844 Coin 多重背包

热门文章

  1. 对GPDB查询计划的Motion结点的理解
  2. traits的介绍
  3. 关于Android手机MTP模式连接的一些设置(win7和ubuntu下,以红米1s为例)
  4. ZOJ 2588 Burning Bridges(无向连通图求割边)
  5. mysql简单优化思路
  6. 学习 shell —— 创建序列数组
  7. POJ 3276 枚举+差分?
  8. Fedora27 源配置
  9. react-native flatlist setState修改数据视图不刷新解决方案
  10. BZOJ1189: [HNOI2007]紧急疏散evacuate(二分答案,最大流)