先上图看卡结果:
GITHUB:Android多线程下载断点续传
如图所示点击下载就开始下载,点击停止就会停止再次点击下载就会接着下载了。
设计思路是这样的:
首先通过广播将下载信息传递给DownService,DownService根据文件URL获取文件大小,再通过DownTask将下载任务分配,并且通过广播当点击停止下载时将下载进度保存在数据库中,当点击开始下载时再从数据库中获取到保存的进度,继续下载。
代码结构:
核心类是 DownLoadService,java 和DownTask.java将这两个类贴出来:
package com.example.downloaddemo.services;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.http.HttpStatus;
import com.example.downloaddemo.enties.FileInfo;
import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
public class DownLoadService extends Service {
// 设置存储路劲
public static final String DOWN_PATH = Environment
.getExternalStorageDirectory().getAbsolutePath() + "/downloads";
public static final String ACTION_START = "ACTION_START";
public static final String ACTION_STOP = "ACTION_STOP";
public static final String ACTION_FINISH = "ACTION_FINISH";
public static final String ACTION_UPDATE = "ACTION_UPDATE";
public static final int MSG_INIT = 0;
// private DownLoadTask mDownLoadTask=null;
private Map<Integer, DownLoadTask> mTasks = new LinkedHashMap<Integer, DownLoadTask>();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 获取Activity传来的数据
if (ACTION_START.equals(intent.getAction())) {
FileInfo fileinfo = (FileInfo) intent
.getSerializableExtra("fileinfo");
InitThread minitThread=new InitThread(fileinfo);
DownLoadTask.sExecutorService.execute(minitThread);
Log.i("test", "start:" + fileinfo.toString());
} else if (ACTION_STOP.equals(intent.getAction())) {
FileInfo fileinfo = (FileInfo) intent
.getSerializableExtra("fileinfo");
// 从集合中获取下载任务
DownLoadTask task = mTasks.get(fileinfo.getId());
if (task != null) {
// 停止下载任务
task.isPause = true;
}
Log.i("test", "stop:" + fileinfo.toString());
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_INIT:
FileInfo info = (FileInfo) msg.obj;
Log.i("test", "init:" + info.toString());
// 开启下载任务,默认为三个线程下载
DownLoadTask task = new DownLoadTask(DownLoadService.this,info, 3);
task.downLoad();
// 把下载任务添加到集合中
mTasks.put(info.getId(), task);
break;
default:
break;
}
}
};
/**
*
* 初始化子线程
*/
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(3000);
conn.setRequestMethod("GET");
int length = -1;
// 获取文件长度
if (conn.getResponseCode() == HttpStatus.SC_OK) {
length = conn.getContentLength();
}
if (length <= 0) {
return;
}
File dir = new File(DOWN_PATH);
if (!dir.exists()) {
dir.mkdir();
}
// 在本地文件并设置长度
File file = new File(dir,mfileInfo.getFileName());
// 特殊的输出流能够 任任意位置写入
raf = new RandomAccessFile(file,"rwd");
// 设置本地文件的长度
raf.setLength(length);
mfileInfo.setLength(length);
mHandler.obtainMessage(MSG_INIT, mfileInfo).sendToTarget();
} catch (Exception e) {
// TODO: handle exception
} finally {
try {
// 关闭流操作和网络连接操作
raf.close();
conn.disconnect();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
super.run();
}
}
}
下面是DownLoadTask.java的代码:
package com.example.downloaddemo.services;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.http.HttpStatus;
import com.example.downloaddemo.db.ThreadDAO;
import com.example.downloaddemo.db.ThreadDAOImpl;
import com.example.downloaddemo.enties.FileInfo;
import com.example.downloaddemo.enties.ThreadInfo;
import android.content.Context;
import android.content.Intent;
/**
*
* 下载任务
*
*/
public class DownLoadTask {
private Context mContext = null;
private FileInfo mFileInfo = null;
private ThreadDAO mDao = null;
private int mfinished = 0;
public boolean isPause = false;
private int threadCount = 1;// 线程数量
private List<DownLoad> mThreadList = null;// 线程集合方便管理分段下载线程
//使用带缓存型池子,先查看池中有没有以前建立的线程,如果有,
//就reuse.如果没有,就建一个新的线程加入池中
//缓存型池子通常用于执行一些生存期很短的异步型任务,
//能reuse的线程,必须是timeout IDLE内的池中线程,
//缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
public static ExecutorService sExecutorService=Executors.newCachedThreadPool();
public DownLoadTask(Context mContext, FileInfo mFileInfo, int threadCount) {
super();
this.mContext = mContext;
this.mFileInfo = mFileInfo;
mDao = new ThreadDAOImpl(mContext);
}
public void downLoad() {
// 读取数据库的线程信息
List<ThreadInfo> mThreadInfos = mDao.getThreads(mFileInfo.getUrl());
if (mThreadInfos.size() == 0) {
// 获取每个线程下载的长度
int length = mFileInfo.getLength() / threadCount;
// 创建线程下载信息
for (int i = 0; i < threadCount; i++) {
ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), i* length, (i + 1) * length, 0);
if (i == threadCount - 1) {
threadInfo.setEnd(mFileInfo.getLength());
}
// 添加到线程信息集合中
mThreadInfos.add(threadInfo);
// 向数据库中插入线程信息
mDao.insertThread(threadInfo);
}
}
mThreadList = new ArrayList<DownLoadTask.DownLoad>();
// 启动多个线程来下载
for (ThreadInfo info : mThreadInfos) {
DownLoad download = new DownLoad(info);
DownLoadTask.sExecutorService.execute(download);
mThreadList.add(download);
}
}
/**
* 判断下载线程是否都下载完毕
*/
private synchronized void checkAllThreadsFinished() {
boolean allFinished = true;
for (DownLoad download : mThreadList) {
if (!download.isfinished) {
allFinished = false;
break;
}
}
if (allFinished) {
// 下载完毕删除线程信息
mDao.deleteThread(mFileInfo.getUrl());
// 发送广播到Activity
Intent intent = new Intent(DownLoadService.ACTION_FINISH);
intent.putExtra("fileInfo", mFileInfo);
mContext.sendBroadcast(intent);
}
}
class DownLoad extends Thread {
private ThreadInfo mThreadInfo = null;
public boolean isfinished = false;// 表示线程是否下载完毕
public DownLoad(ThreadInfo mThreadInfo) {
super();
this.mThreadInfo = mThreadInfo;
}
@Override
public void run() {
// 打开连接
HttpURLConnection conn = null;
RandomAccessFile raf = null;
InputStream ins = null;
try {
URL url = new URL(mThreadInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setRequestMethod("GET");
int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
// 设置下载位置
conn.setRequestProperty("Range",
"bytes=" + "-" + mThreadInfo.getEnd());
// 设置文件写入位置
File file = new File(DownLoadService.DOWN_PATH,
mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
//移动到指定位置
raf.seek(start);
Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
mfinished += mThreadInfo.getFinished();
// 开始下载
if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
// 读取数据
ins = conn.getInputStream();
byte[] buffer = new byte[1024 * 4];
int length = -1;
long time = System.currentTimeMillis();
while ((length = ins.read(buffer)) != -1) {
// 写入文件
raf.write(buffer, 0, length);
// 把下载进度发送广播更新UI
// 累加整文件完成进度
mfinished += length;
// 累加每个线程完成的进度mThreadInfo.setFinished(mThreadInfo.getFinished()+ length);
if (System.currentTimeMillis() - time > 1000) {
time = System.currentTimeMillis();
intent.putExtra("finished", mfinished * 100
/ mFileInfo.getLength());
intent.putExtra("id", mFileInfo.getId());
mContext.sendBroadcast(intent);
}
// 下载暂停保存进度
if (isPause) {
mDao.updateThread(mThreadInfo.getUrl(),
mThreadInfo.getId(),
mThreadInfo.getFinished());
return;
}
}
isfinished = true;
// 检查下载任务是否完成
checkAllThreadsFinished();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
conn.disconnect();
ins.close();
raf.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
super.run();
}
}
}
没写之前我在想多线程下载之后如何下载下来的刷数据拼接在一起,后来查看JavaAPI之后使用了用Java的RandomAccessFile操作就可以写入到指定的位,DownLoadService获取长度后,就在存储的位置创建该文件。
HttpURLConnection conn = null;
RandomAccessFile raf = null;
try {
// 连接网络文件
URL url = new URL(mfileInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setRequestMethod("GET");
int length = -1;
// 获取文件长度
if (conn.getResponseCode() == HttpStatus.SC_OK) {
length = conn.getContentLength();
}
if (length <= 0) {
return;
}
File dir = new File(DOWN_PATH);
if (!dir.exists()) {
dir.mkdir();
}
// 在本地文件并设置长度
File file = new File(dir, mfileInfo.getFileName());
// 特殊的输出流能够 任任意位置写入
raf = new RandomAccessFile(file, "rwd");
// 设置本地文件的长度
raf.setLength(length);
mfileInfo.setLength(length);
mHandler.obtainMessage(MSG_INIT, mfileInfo).sendToTarget();
在写入文件时:不同的线程获取对应的数据写在对应的文件位置就可以了不存在拼接问题。举个栗子:
T1下载0–5;T2下载6—-10;T3下载11—-15,就可以啦下载完写到对应得位置即可。
在写入的时候设置么每写入 byte[1024 * 4],通知UI更新进度条,并进行总的进度累加,下载完后在判断多个线程下载一个文件是不是都下载完成了,如果下载完成了就通知弹出下载完成提示。
核心代码如下:
// 打开连接
HttpURLConnection conn = null;
RandomAccessFile raf = null;
InputStream ins = null;
try {
URL url = new URL(mThreadInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setRequestMethod("GET");
int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
// 设置下载位置
conn.setRequestProperty("Range",
"bytes=" + "-" + mThreadInfo.getEnd());
// 设置文件写入位置
File file = new File(DownLoadService.DOWN_PATH,
mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
//移动到指定位置
raf.seek(start);
Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
mfinished += mThreadInfo.getFinished();
// 开始下载
if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
// 读取数据
ins = conn.getInputStream();
byte[] buffer = new byte[1024 * 4];
int length = -1;
long time = System.currentTimeMillis();
while ((length = ins.read(buffer)) != -1) {
// 写入文件
raf.write(buffer, 0, length);
// 把下载进度发送广播更新UI
// 累加整文件完成进度
mfinished += length;
// 累加每个线程完成的进度
mThreadInfo.setFinished(mThreadInfo.getFinished()
+ length);
if (System.currentTimeMillis() - time > 1000) {
time = System.currentTimeMillis();
intent.putExtra("finished", mfinished * 100
/ mFileInfo.getLength());
intent.putExtra("id", mFileInfo.getId());
mContext.sendBroadcast(intent);
}
// 下载暂停保存进度
if (isPause) {
mDao.updateThread(mThreadInfo.getUrl(),
mThreadInfo.getId(),
mThreadInfo.getFinished());
return;
}
}
isfinished = true;
// 检查下载任务是否完成
checkAllThreadsFinished();
线程通过使用线程池来管理
ExecutorService sExecutorService=Executors.newCachedThreadPool();
//使用带缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse.如果没有,就建一个新的线程加入池中
//缓存型池子通常用于执行一些生存期很短的异步型任务,能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
GITHUB: