之前有看过一个著名的断点下载的框架MultiThreadDownload,用的是线程池开启下载任务,点击暂停的时候将断点的信息保存在数据库里面,下次拿出来继续下载,本文的思路也和这个差不多。
这篇文章的代码将会在上次写的《即拿即用-HttpURLConnection分别实现图片,文本,文件的请求》的GitHub项目里面继续更新。完整下载地址在文章最后
该例子在下载中多次点击开始和暂停对进度进行控制,下面放上一张效果图:
主要步骤如下:
点击开始的时候,我们要先开一个网络连接去获取文件的长度:
/**
* 点击开始
*/
public void onStartClick(View view) {
// 开启
fileName.setText(getfileName(urlstr));
// 获得Activity传来的参数
Log.i("test", "START" + fileInfo.toString());
//开启一个下载任务
new InitThread(fileInfo).start();
}
/**
* 开启一个网络连接用来获得下载文件的信息
*/
class InitThread extends Thread {
private FileInfo mFileInfo = null;
public InitThread(FileInfo mFileInfo) {
super();
this.mFileInfo = mFileInfo;
}
@Override
public void run() {
HttpURLConnection conn = null;
RandomAccessFile raf = null;
try {
URL url = new URL(mFileInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
int length = -1;
if (code == HttpURLConnection.HTTP_OK) {
length = conn.getContentLength();
}
//如果文件长度为小于0,表示获取文件失败,直接返回
if (length <= 0) {
return;
}
// 判断文件路径是否存在,不存在这创建
File dir = new File(DownloadPath);
if (!dir.exists()) {
dir.mkdir();
}
// 创建本地文件
File file = new File(dir, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
raf.setLength(length);
// 设置文件长度
mFileInfo.setLength(length);
// 将ileInfo对象传送给Handler
Message msg = Message.obtain();
msg.obj = mFileInfo;
msg.what = MSG_INIT;
mHandler.sendMessage(msg);
// msg.setTarget(mHandler);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
try {
if (raf != null) {
raf.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
super.run();
}
}
获取文件的长度之后,发送一个handler消息通知应用去开启另外一个网络连接DownloadTask,进行下载:
// 从InitThread线程中获取FileInfo信息,然后开始下载任务
Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_INIT:
FileInfo fileInfo = (FileInfo) msg.obj;
Log.i("test", "INIT:" + fileInfo.toString());
// 获取FileInfo對象,开始下载任务
mTask = new DownloadTask(SingleThreadDownloadActivity.this, fileInfo);
mTask.download();
break;
}
}
};
在开启下载任务之前,根据下载的地址作为条件,看一下数据库这个地址有没有上一次下载的信息,也就是上一次文件下载的长度,结束的位置等信息,有的话获取并传给下载任务,没有的话会在下载任务里面插入一条到数据库
/**
* 查询数据库上一次下载的信息,有则获取,没有则新建
*/
public void download() {
// 从数据库中获取到下载的信息
List<ThreadInfo> list = mDao.queryThreads(mFileInfo.getUrl());
ThreadInfo info = null;
if (list.size() == 0) {
info = new ThreadInfo(0, mFileInfo.getUrl(), 0, mFileInfo.getLength(), 0);
} else {
info = list.get(0);
}
//开启下载任务
new DownloadThread(info).start();
}
到了最关键的时候了,开启一个下载任务。一边下载一边记录下载的长度,并更新到数据库。如果点击了暂停,则跳出循环结束下载。
class DownloadThread extends Thread {
private ThreadInfo threadInfo = null;
public DownloadThread(ThreadInfo threadInfo) {
this.threadInfo = threadInfo;
}
@Override
public void run() {
Log.i("test", "开启一个下载任务");
// 如果数据库不存在下载信息,添加下载信息
if (!mDao.isExists(threadInfo.getUrl(), threadInfo.getId())) {
mDao.insertThread(threadInfo);
}
HttpURLConnection conn = null;
RandomAccessFile raf = null;
InputStream is = null;
try {
URL url = new URL(mFileInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
//开始位置为getFinished()开始上次结束的位置
Log.i("test", "文件的长度"+mFileInfo.getLength()+" 上次结束的位置:" + threadInfo.getFinished());
int start = threadInfo.getStart() + threadInfo.getFinished();
// 设置下载文件开始到结束的位置(结束的位置也就是文件的长度)
conn.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());
File file = new File(SingleThreadDownloadActivity.DownloadPath, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
raf.seek(start);
mFinished += threadInfo.getFinished();
int code = conn.getResponseCode();
if (code == HttpURLConnection.HTTP_PARTIAL) {
is = conn.getInputStream();
byte[] bt = new byte[1024];
int len = -1;
// 定义UI刷新时间
long time = System.currentTimeMillis();
while ((len = is.read(bt)) != -1) {
raf.write(bt, 0, len);
//记录结束的位置
mFinished += len;
// 设置为500毫米更新一次
if (System.currentTimeMillis() - time > 500) {
time = System.currentTimeMillis();
//发送一个广播提示下载的进度
Intent intent = new Intent(SingleThreadDownloadActivity.ACTION_UPDATE);
//结束的位置/文件长度*100=下载进度百分比
intent.putExtra("finished", mFinished * 100 / mFileInfo.getLength());
Log.i("test", mFinished * 100 / mFileInfo.getLength() + "");
// 发送广播给Activity
mComtext.sendBroadcast(intent);
}
if (mIsPause) {
//如果状态为暂停,则跳出循环,并记录这次结束的位置的长度
mDao.updateThread(threadInfo.getUrl(), threadInfo.getId(), mFinished);
return;
}
}
}
// 下载完成后,刪除数据库信息
mDao.deleteThread(threadInfo.getUrl(), threadInfo.getId());
Intent intent = new Intent(SingleThreadDownloadActivity.ACTION_UPDATE);
//结束的位置/文件长度*100=下载进度百分比
intent.putExtra("finished", 100);
// 发送广播给Activity
mComtext.sendBroadcast(intent);
Log.i("DownloadTask", "下载完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
try {
if (is != null) {
is.close();
}
if (raf != null) {
raf.close();
}
} catch (IOException e) {
e.printStackTrace();
}
Log.i("test", "关闭一个下载任务");
}
super.run();
}
}
在这篇文章中的重点是使用了RandomAccessFile,它支持任意访问的方式,程序可以直接跳到任意地方来读写数据。
完整代码地址
https://github.com/mocn26169/HttpRequest-Demo
参考