上文中说了单任务的断点续传,这篇文章就说说多任务下载,不啰嗦了,直接进入正题。
q:486789970
email:[email protected]
下图是一个多任务下载的动态图:
在上篇博客基础上,新增了一个ListView用来显示多条下载内容,这个很简单,这里就不多说啦
.上面的效果图就是多任务下载(使用线程池管理),支持断点续传、实时进度更新、下载暂停、下载继续,下载完成自动安装等功能;同时包括网络下载请求和本地文件的存储。
实现原理
例如10M大小,使用3个线程来下载
Service
package com.cc.downloaddemo.services;
import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;
import com.cc.downloaddemo.info.FileInfo;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 下载service
*/
public class DownloadService extends Service {
//通过URL地址下载apk文件并保存在本地存储路径
public static final String DOWNLOAD_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";
//定义handler的flag
public static final int MSG_INIT = 0;
//异步下载任务集合(设置为map集合,查找方便一些)
private Map taskMap = new LinkedHashMap<>();
/**
* 执行下载、暂停、继续下载
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//获取从Activity传过去的参数,判断intent的值。
if(ACTION_START.equals(intent.getAction())){
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
//启动初始化子线程,联网下载文件内容。
InitThread initThread = new InitThread(fileInfo);
//使用线程池来管理
DownloadTask.executorService.execute(initThread);
} else if (ACTION_STOP.equals(intent.getAction())) {
//暂停下载时,更改下载任务类的flag便会自动暂停。
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
//从集合中取出下载任务
DownloadTask task = taskMap.get(fileInfo.getId());
//非空处理
if(task != null){
//停止下载任务
task.ispause = true;
}
}
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 网络内容下载完成后
* 通过handler启动异步任务类开始正式下载文件。
*/
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
//是否为初始化消息
if(msg.what == MSG_INIT){
//获取发回来的信息
FileInfo fileInfo = (FileInfo) msg.obj;
//启动一个下载任务,将文件内容传递过去。
//一个任务让三个线程去下载
DownloadTask downloadTask = new DownloadTask(DownloadService.this, fileInfo, 3);
//启动下载方法
downloadTask.download();
//把下载任务添加到集合中
taskMap.put(fileInfo.getId(), downloadTask);
}
}
};
/**
* 定义子线程用来下载
*/
class InitThread extends Thread{
//定义一个文件用来接收下载信息
private FileInfo mFileInfo = null;
//启动线程时传入的对象
public InitThread(FileInfo mFileInfo) {
this.mFileInfo = mFileInfo;
}
@Override
public void run() {
//定义conn
HttpURLConnection conn = null;
//定义文件内容访问类
RandomAccessFile raf = null;
try {
//连接网络文件
URL url = new URL(mFileInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(3000);
conn.setRequestMethod("GET");
//自定义一个长度,注意尽量用long(处理下载消息进度百分比时好计算)
long length = -1;
//下载完成
if(conn.getResponseCode() == HttpURLConnection.HTTP_OK ){
//获取文件总长度赋值给length
length = conn.getContentLength();
}
//长度不能小于0
if(length <= 0){
return;
}
//判断该路径存在与否
File dir = new File(DOWNLOAD_PATH);
//文件不存在时创建
if(!dir.exists()){
dir.mkdir();
}
//本地创建一个文件
File file = new File(dir, mFileInfo.getFilename());
//输出流
raf = new RandomAccessFile(file, "rwd");
//设置本地的文件长度(等于下载文件的总长度。杜绝浪费资源)
raf.setLength(length);
//赋值给定义的对象长度
mFileInfo.setLength(length);
//启动flag,将文件发送给handler处理
handler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
//关闭资源
raf.close();
conn.disconnect();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
注:使用了线程池统一管理
DownloadTask:
package com.cc.downloaddemo.services;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import com.cc.downloaddemo.db.dao.ThreadDao;
import com.cc.downloaddemo.db.impl.ThreadDaoImpl;
import com.cc.downloaddemo.info.FileInfo;
import com.cc.downloaddemo.info.ThreadInfo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 任务下载类
* @author
*/
public class DownloadTask {
//上下文
private Context context;
//定义数据库线程对象文件
private FileInfo fileInfo;
//实例化接口
public static ThreadDao threadDao = null;
//定义finished(当前已下载的长度)
private int finished = 0;
//默认直接下载
public boolean ispause = false;
//默认线程数量
private int threadCount = 1;
//定义线程集合
private List threadList;
//定义线程池
public static ExecutorService executorService = Executors.newCachedThreadPool();
/**
* 构造方法
* @param context
* @param fileInfo
*/
public DownloadTask(Context context, FileInfo fileInfo, int threadCount) {
this.context = context;
//拿到handler传过来的文件
this.fileInfo = fileInfo;
//拿到线程数量
this.threadCount = threadCount;
//实例化接口实现类
threadDao = new ThreadDaoImpl(context);
}
/**
* 下载方法
*/
public void download(){
//下载任务一开始,先查询数据库,看是否有文件在等待下载。
//读取数据库的所有线程信息
List threads = threadDao.getThreads(fileInfo.getUrl());
//当list的size为0时,说明没有等待下载的线程,为1时则有。
if(threads.size() == 0){
//获得每一个文件
int length = (int) (fileInfo.getLength() / threadCount);
for (int i = 0; i < threadCount; i++){
//创建线程信息
ThreadInfo threadInfo = new ThreadInfo(i, fileInfo.getUrl()
, length * i, (i + 1) * length - 1, 0);
//当i是最后一个线程时,设置一个索引
if(i == threadCount - 1){
threadInfo.setEnd((int) fileInfo.getLength());
}
//添加到线程信息集合中
threads.add(threadInfo);
threadDao.insertThread(threadInfo);
}
}
threadList = new ArrayList<>();
//启动多个线程进行下载
for (ThreadInfo info : threads){
DownloadThread downloadThread = new DownloadThread(info);
// downloadThread.start();
DownloadTask.executorService.execute(downloadThread);
//添加线程到集合中
threadList.add(downloadThread);
}
}
/**
* 判断是否所有线程都执行完毕
* 保证同一时间段只有一个线程访问此方法
*/
private synchronized void checkAddThreadFinished(){
//假设全部完成啦
boolean allFinished = true;
//遍历集合
for (DownloadThread thread : threadList){
if(!thread.isFinished){
allFinished = false;
break;
}
}
if(allFinished){
//下载完成,删除数据库所保存的线程信息
threadDao.deleteThread(fileInfo.getUrl());
//发送广播通知,消灾任务结束
Intent intent = new Intent(DownloadService.ACTION_FINISH);
intent.putExtra("fileInfo", fileInfo);
context.sendBroadcast(intent);
}
}
/**
* 下载线程
*/
class DownloadThread extends Thread{
//继续定义一个临时线程对象
private ThreadInfo threadInfo;
//标记线程是否结束
public boolean isFinished = false;
//构造
public DownloadThread(ThreadInfo threadInfo) {
this.threadInfo = threadInfo;
}
@Override
public void run() {
//定义conn、输入流和文件内容访问类
HttpURLConnection conn = null;
InputStream inputStream = null;
RandomAccessFile raf = null;
try{
//开始联网下载
URL url = new URL(threadInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(3000);
conn.setRequestMethod("GET");
//设置下载位置(通过当前开始值和现在值判断下载位置)
int start = threadInfo.getStart() + threadInfo.getFinished();
conn.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());
//设置文件写入位置
File file = new File(DownloadService.DOWNLOAD_PATH, fileInfo.getFilename());
raf = new RandomAccessFile(file, "rwd");
raf.seek(start);
//定义一个广播,用来更新下载进度
Intent intent = new Intent(DownloadService.ACTION_UPDATE);
//
finished += threadInfo.getFinished();
//开始下载
if(conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){
//读取数据
inputStream = conn.getInputStream();
byte[] buffer = new byte[1024 * 4];
int len = -1;
long time = System.currentTimeMillis();
while ((len = inputStream.read(buffer)) != -1){
//写入文件
raf.write(buffer, 0, len);
//累加整个文件的下载完成进度
finished += len;
//累加每个线程完成的进度
threadInfo.setFinished(threadInfo.getFinished() + len);
//间隔500毫秒更新一下进度
if(System.currentTimeMillis() - time > 1500){
time = System.currentTimeMillis();
intent.putExtra("finished", (int)(finished / (float)fileInfo.getLength() * 100));
intent.putExtra("id", fileInfo.getId());
context.sendBroadcast(intent);
}
//在下载暂停时,保存下载进度
if(ispause){
threadDao.updateThread(threadInfo.getUrl(), threadInfo.getId(), threadInfo.getFinished());
return;
}
}
intent.putExtra("finished", 100);
context.sendBroadcast(intent);
//标识线程执行完毕
isFinished = true;
//执行完之后检查
checkAddThreadFinished();
openFile(file);
}
}catch (Exception e){
e.printStackTrace();;
}finally {
conn.disconnect();
try {
inputStream.close();
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 自动安装
* @param file
*/
private void openFile(File file) {
// TODO Auto-generated method stub
Log.e("OpenFile", file.getName());
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
context.startActivity(intent);
}
}
q:486789970
email:[email protected]
---财财亲笔