Android实现网络下载二(多任务下载--支持断点续传)
Android实现网络下载二(多任务下载–支持断点续传)
上文中说了单任务的断点续传,这篇文章就说说多任务下载,不啰嗦了,直接进入正题。
附上demo源码,GitHub代码后续上传,这里的链接还是csdn的。
点这里下载源码,快,戳我戳我…
q:486789970
email:mr.cai_cai@foxmail.com
下图是一个多任务下载的动态图:
效果图如下(单任务下载在上篇文章)https://blog.csdn.net/qq_35840038/article/details/90239354
在上篇博客基础上,新增了一个ListView用来显示多条下载内容,这个很简单,这里就不多说啦
.上面的效果图就是多任务下载(使用线程池管理),支持断点续传、实时进度更新、下载暂停、下载继续,下载完成自动安装等功能;同时包括网络下载请求和本地文件的存储。
实现原理
- 首先通过Service里面的代码获得下载文件的长度,然后设置本地文件的长度
- 根据文件长度和线程数计算每条线程下载的数据长度和下载位置(这里用了三条线程,当然也可改成让用户输入线程数)
- 文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M
例如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); }}
附上demo源码,GitHub代码后续上传,这里的链接还是csdn的。
点这里下载源码,快,戳我戳我…
q:486789970
email:mr.cai_cai@foxmail.com
如果有什么问题,欢迎大家指导。并相互联系,希望能够通过文章互相学习。
---财财亲笔