> 文档中心 > Android实现网络下载二(多任务下载--支持断点续传)

Android实现网络下载二(多任务下载--支持断点续传)


Android实现网络下载二(多任务下载–支持断点续传)

上文中说了单任务的断点续传,这篇文章就说说多任务下载,不啰嗦了,直接进入正题。

附上demo源码,GitHub代码后续上传,这里的链接还是csdn的。

点这里下载源码,快,戳我戳我…

q:486789970
email:mr.cai_cai@foxmail.com

下图是一个多任务下载的动态图:

效果图如下(单任务下载在上篇文章)https://blog.csdn.net/qq_35840038/article/details/90239354

在上篇博客基础上,新增了一个ListView用来显示多条下载内容,这个很简单,这里就不多说啦
Android实现网络下载二(多任务下载--支持断点续传)
.上面的效果图就是多任务下载(使用线程池管理),支持断点续传、实时进度更新、下载暂停、下载继续,下载完成自动安装等功能;同时包括网络下载请求和本地文件的存储。

实现原理

  • 首先通过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

如果有什么问题,欢迎大家指导。并相互联系,希望能够通过文章互相学习。

---财财亲笔