慢慢造轮子,在造轮子的过程中学习android相关的原理和设计模式。先开始一步步实现多线程文件断点下载器。
这次的多线程文件断点下载器,要实现以下几点:
1. 断点续传,不只是单纯的点个暂停开始,而是在退出重进之后仍然有任务的进度,这个就需要用到数据持久化了。
2. 多任务并行下载,一定数量的任务并行下载,超过额定值的任务暂停等待。
3. 单任务多线程下载,这个需要服务端的支持
这个从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卡。接下来开始尝试加入断点下载功能。
先规划一下断点下载最基本的功能
第一点,可以利用一组变量表示当前一个任务的下载状态,通过改变这个变量来间接控制任务,通过读取变量获得任务当前的状态。
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的情况下读取。
其实这两种情况并没有什么区别,都是需要重新开启一个新的线程进行下载,所以在开启线程的时候读取数据库,看看有没有数据就可以了。
具体的代码就不写了。
断点续传到这里差不多就完成了,下一篇博文开始多任务并行下载。
具体代码可以查看我的github项目。