Android 断点续传进阶之多线程下载

今天继续下载的风骚走位内容---多线程多文件断点续传

Android 断点续传基础之单线程下载http://blog.csdn.net/qq_27489007/article/details/53897653

效果图:

文件关系:

所需内容

多文件下载列表的显示

启动多个线程分段下载

使用通知栏显示进度条

使用其他方式实现线程通信

与单线程不主要不同:

任务下载类(DuoDownloadTask) 使用了线程池, 数据库对线程的增、删操作使用synchronized

多线程下载原理简介

假设要分3个线程下载一个100字节的文件,每个线程可以平分线程1(0-32字节)、 线程2(33-65字节)、线程3(66-100字节)

部分主要代码:

/**

  • 多线程多文件的下载任务类,为了学习查看方便 所以提出来了

  • Created by lung on 2016-12-17.

*/

public class DuoDownloadTask {

private Context mContext = null;

private FileInfo mFileInfo = null;

private ThreadDAOImpl mThreadDAO = null;

private long mFinished = 0; //总的完成进度

public boolean isPause = false; //暂停下载的开关

private int mThreadCount = 1; //线程数量

private List downloadThreadList = null;//线程集合 方便管理

public static ExecutorService executorService = Executors.newCachedThreadPool();//线程池

public DuoDownloadTask(Context mContext, FileInfo mFileInfo, int mThreadCount) {

this.mContext = mContext;

this.mFileInfo = mFileInfo;

this.mThreadCount = mThreadCount;

mThreadDAO = new ThreadDAOImpl(mContext);

}

//下载的方法

public void download() {

//读取数据库的线程信息

List threaddInfos = mThreadDAO.getThreads(mFileInfo.getUrl());

if (threaddInfos.size() == 0) { //如果数据库无线程信息

//获得每个线程下载的长度

int length = mFileInfo.getLength() / mThreadCount;

for (int i = 0; i < mThreadCount; i++) {

// 参数三线程开始的地方 参数四 线程结束的地方 参数五任务完成的进度

TheardInfo theardInfo = new TheardInfo(i, mFileInfo.getUrl(), length * i, (i + 1) * length - 1, 0);

//最后一个线程 会出现除不尽的情况

if (i == mThreadCount - 1) {

//直接把线程的结束位置 设置成文件的最大长度位置

theardInfo.setEnd(mFileInfo.getLength());

}

//添加到线程信息集合中

threaddInfos.add(theardInfo);

//向数据库插入线程信息

mThreadDAO.insertThread(theardInfo);

}

}

downloadThreadList = new ArrayList<>();

//启动多个线程进行下载

for (TheardInfo info : threaddInfos) {

DownloadThread thread = new DownloadThread(info);

// thread.start(); //开始线程添加到集合中

DuoDownloadTask.executorService.execute(thread); //利用线程池来执行线程任务

downloadThreadList.add(thread); //把线程添加进去方便ckeckAllThreadFinshed检查线程

}

}

//这个方法 用来判断所有的线程是否都执行完了

private synchronized void checkAllThreadFinshed() {

boolean allFinished = true;

//遍历线程集合,判断线程是否都执行完毕

for (DownloadThread thread : downloadThreadList) {

if (!thread.isFinished) {

allFinished = false;

break;

}

}

if (allFinished) { //如果线程都完成了

//下载完成后 删除线程信息

//删除下载记录

mThreadDAO.deleteThread(mFileInfo.getUrl(), mFileInfo.getId());

//发送广播通知ui下载任务结束

Intent intent = new Intent(DuoDownloadService.ACTION_FINISHED);

intent.putExtra("fileInfo", mFileInfo);

mContext.sendBroadcast(intent); //发送广播

}

}

class DownloadThread extends Thread {

private TheardInfo threadInfo;

public boolean isFinished = false; //用来标识线程是否结束

public DownloadThread(TheardInfo threadInfo) {

this.threadInfo = threadInfo;

}

@Override

public void run() {

//设置下载位置

try {

URL url = new URL(threadInfo.getUrl());

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("GET");

conn.setConnectTimeout(3000);

//开始的字节数 为开始加上完成的长度

long start = threadInfo.getStart() + threadInfo.getFinished();

//下载的范围 开始的字节数 到结束的字节数

conn.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());

//设置文件写入位置 路径 文件名

File file = new File(DownloadService.DOWNLOAD_PATH, mFileInfo.getFileName());

//

RandomAccessFile raf = new RandomAccessFile(file, "rwd");

raf.seek(start); //文件的写入位置

Intent intent = new Intent(DownloadService.ACTION_UPDATE);//把进度广播发送给activity 所以需要intent

mFinished += threadInfo.getFinished();//从线程中拿到完成的进度

//开始下载

if (conn.getResponseCode() == 206) {

//读取数据

InputStream input = conn.getInputStream();

byte[] buffer = new byte[1024 * 4];

int len = -1;

long time = System.currentTimeMillis(); //拿到当前时间

while ((len = input.read(buffer)) != -1) {

//写入文件

raf.write(buffer, 0, len);

//把下载进度发送广播给activity

mFinished += len; //把现在下载的进度累加进去

//累加每个线程完成的进度

threadInfo.setFinished(threadInfo.getFinished() + len);

if (System.currentTimeMillis() - time > 1000) { //减少ni负载 大于10秒发送更新

time = System.currentTimeMillis();

//以百分比的形式发送给广播

intent.putExtra("finished", (int) (mFinished * 100 / mFileInfo.getLengt

h()));

intent.putExtra("id", mFileInfo.getId());

mContext.sendBroadcast(intent);

}

//在下载暂停时,保存下载进度

if (isPause) { //如果暂停 把线程信息进行保存 最后一个参数 他会把每个线程的 进度保存起来

mThreadDAO.updateThread(mFileInfo.getUrl(), mFileInfo.getId(), threadInfo.getFinished());

return;

}

}

intent.putExtra("finished", 100);

mContext.sendBroadcast(intent); //发送广播

// 标识线程执行完毕

isFinished = true;

//每个线程执行完毕 都执行去判断 所有的线程是否都执行完毕

checkAllThreadFinshed();

input.close();

}

raf.close();

conn.disconnect();

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} finally { //关闭各种链接

}

super.run();

}

}

}

ThreadDAOImpl.java

相关推荐
CYRUS_STUDIO7 小时前
Frida 检测与对抗实战:进程、maps、线程、符号全特征清除
android·逆向
csj509 小时前
安卓基础之《(28)—Service组件》
android
lhbian11 小时前
PHP、C++和C语言对比:哪个更适合你?
android·数据库·spring boot·mysql·kafka
catoop12 小时前
Android 最佳实践、分层架构与全流程解析(2025)
android
ZHANG13HAO12 小时前
Android 13 特权应用(Android Studio 开发)调用 AOSP 隐藏 API 完整教程
android·ide·android studio
田梓燊12 小时前
leetcode 142
android·java·leetcode
angerdream13 小时前
Android手把手编写儿童手机远程监控App之JAVA基础
android
菠萝地亚狂想曲13 小时前
Zephyr_01, environment
android·java·javascript
sTone8737513 小时前
跨端框架通信机制全解析:从 URL Schema 到 JSI 到 Platform Channel
android·前端
sTone8737513 小时前
Java 注解完全指南:从 "这是什么" 到 "自己写一个"
android·前端