多线程下载

楼主三年磨剑(当然不是磨着一把剑),倾血奉献Android多线程下载Demo。有的人就问了“怎么写来写去还是Demo?”,因为老哥我实在太忙了, 每天写一点,写到现在也才写了个下载器,也就差下载管理类就是个完整的模块了。对于新手学习这已经足够了,不对,是完全足够了。

这不仅仅只是一个简单的Demo,这绝对是你前所未见的商业级别的范例,集支持多线程下载,断点续传,只使用wifi网络下载,显示下载速度,人性化提示 及超强的容错机制多功能于一体,绝对的实用,绝对的专业。

当然我写这个是为了下载apk的,大家稍微改一改就可以写成更通用的下载器。唯一有点不足的地方就是在Android上使用RandomAccessFile在创建大文件的时候 速度有些慢,导致前几秒的进度都为0。不知道有没有人可以帮我解决这个问题。

下面给出关键代码。

 package com.h3c.DownloadEngine;  
  
 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 android.content.Context;  
 import android.os.Environment;  
 import android.util.Log;  
  
 import com.h3c.DownloadEngine.common.DownloaderErrorException;  
 import com.h3c.DownloadEngine.common.EngineConstants;  
 import com.h3c.DownloadEngine.common.EngineUtil;  
 import com.h3c.DownloadEngine.common.EngineVariable;  
 import com.h3c.DownloadEngine.db.EngineDBOperator;  
 import com.h3c.DownloadEngine.entity.DownloadBean;  
  
 public class Downloader {  
  private final static String TAG = "Downloader";  
  private final static byte[] lock_getFileSize = new byte[1];  
  private final static byte[] lock_refresh_progress = new byte[1];  
  
  private int mThreadCount = 4;// 默认子线程数为4个 
  private int bufferSize = 1024 * 16; // 16K 一个块 
  
  private DownloadBean mBean;// 注意,这里是dwonloader的bean不是其subBean 
  private Context mContext;  
  private DownloadEngineCallback mCallback;  
  private EngineDBOperator mDBOper;  
  private int mDoneThreadCount = 0;// 完成的线程数 
  private int mState = EngineConstants.DOWNLOAD_STATE_INIT;// 下载器状态 
  private ArrayList<DownloadBean> mBeans = new ArrayList<DownloadBean>(  
             mThreadCount);  
  
  public Downloader(DownloadBean bean, Context context,  
             DownloadEngineCallback callback) throws DownloaderErrorException {  
  this.mBean = bean;  
  this.mContext = context;  
  this.mCallback = callback;  
  this.mDBOper = EngineDBOperator.getInstance(context);  
  
  if (this.mDBOper != null) {  
  if (this.mDBOper.isHasDownloadTaskByUrl(bean.url)) {// 如果此任务已经存放进数据库 
                 getDownloaderInfoFromDB(bean);  
             } else {// 插入信息至数据库 
                 addDownloaderInfoToDB(bean);  
             }  
         } else {  
             callBackError("Downloader错误,可能是EngineDBOperator为Null.");  
  throw new DownloaderErrorException(  
  "Downloader错误,可能是EngineDBOperator为Null.");  
         }  
     }  
  
  public DownloadBean getDownloaderInfo() {  
  return mBean;  
     }  
  
  public int getDownloaderState() {  
  return mState;  
     }  
  
  /** 
      * 请求初始化 
      *  
      * @param state 
      */ 
  protected void setDownloaderState(int state) {  
         mState = state;  
  if (state == EngineConstants.DOWNLOAD_STATE_INIT) {  
             mBean.currentPosition = 0;  
         }  
     }  
  
  /** 
      * 加入下载信息进入数据库,此方法用于刚刚初始化Downloader,且数据库中没有该任务的时候 
      *  
      * @param bean 
      * @throws DownloaderErrorException 
      */ 
  private void addDownloaderInfoToDB(DownloadBean bean)  
  throws DownloaderErrorException {  
  if (mState != EngineConstants.DOWNLOAD_STATE_INIT  
                 && mState != EngineConstants.DOWNLOAD_STATE_STOP  
                 && mState != EngineConstants.DOWNLOAD_STATE_ERROR) {  
             callBackError("这个任务已经加入到数据库中了");  
  throw new DownloaderErrorException("这个任务已经加入到数据库中了");  
         }  
  
  if (mDBOper != null) {  
  long fileSize = bean.fileSize;  
  if (mBeans.size() > 0) {  
                 mBeans.clear();  
             }  
  
  try {  
  if (fileSize > 0) {// 判断传入的fileSize大小,如果大于0,就不用从网络中获取,直接初始化N个子下载器 
  if (!hasSpaceInSDCard()) {  
  return;  
                     }  
  long range = fileSize / mThreadCount;// 文件分段值 
  for (int i = 0; i < mThreadCount - 1; i++) {  
                         DownloadBean subBean = (DownloadBean) bean.clone();  
                         subBean.threadId = i;  
                         subBean.startPosition = i * range;  
                         subBean.endPosition = (i + 1) * range - 1;  
                         mBeans.add(subBean);  
                     }  
  
                     DownloadBean subBean = (DownloadBean) bean.clone();  
                     subBean.threadId = mThreadCount - 1;  
                     subBean.startPosition = (mThreadCount - 1) * range;  
                     subBean.endPosition = fileSize - 1;  
                     mBeans.add(subBean);  
                 } else {// 如果等于0,就直接初始化N个0大小的子下载器 
  for (int n = 0; n < mThreadCount - 1; n++) {  
                         DownloadBean subBean = (DownloadBean) bean.clone();  
                         subBean.threadId = n;  
                         mBeans.add(subBean);  
                     }  
  
                     DownloadBean subBean = (DownloadBean) bean.clone();  
                     subBean.threadId = mThreadCount - 1;  
                     mBeans.add(subBean);  
                 }  
  
                 mDBOper.addDownloadTask(mBeans);  
  if (bean.fileSize > 0) {// 如果文件大小已经获取就进入等待状态 
                     mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态 
                 } else {// 文件大小未获取就开启线程去获取文件大小并更新子下载器中的内容 
  new Thread(new Runnable() {  
  @Override 
  public void run() {  
  boolean flag = false;  
  synchronized (lock_getFileSize) {  
                                 flag = getFileSizeByNetwork(mBean);  
                             }  
  if (flag) {  
                                 mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态 
                             } else {  
                                 Log.e(TAG, "从网络中获取文件大小失败 1");  
                             }  
                         }  
                     }).start();  
  
                 }  
             } catch (CloneNotSupportedException e) {  
                 e.printStackTrace();  
             }  
         } else {  
             callBackError("addDownloaderInfoToDB错误,可能是EngineDBOperator为Null.");  
  throw new DownloaderErrorException(  
  "addDownloaderInfoToDB错误,可能是EngineDBOperator为Null.");  
         }  
     }  
  
  /** 
      * 从数据库中读取下载器信息 
      *  
      * @param bean 
      * @throws DownloaderErrorException 
      */ 
  private void getDownloaderInfoFromDB(DownloadBean bean)  
  throws DownloaderErrorException {  
  if (mDBOper != null) {  
             mBeans.clear();  
             mBeans = mDBOper.getDownloadTaskByUrl(bean.url);  
  
             mBean.currentPosition = 0;  
             mBean.fileSize = 0;  
             mThreadCount = mBeans.size();  
  for (DownloadBean subBean : mBeans) {  
                 mBean.currentPosition += subBean.currentPosition;  
  if (subBean.fileSize > mBean.fileSize) {  
                     mBean.fileSize = subBean.fileSize;  
                 }  
             }  
  
  if (mBean.fileSize < 1) {  
  new Thread(new Runnable() {  
  @Override 
  public void run() {  
  boolean flag = false;  
  synchronized (lock_getFileSize) {  
                             flag = getFileSizeByNetwork(mBean);  
                         }  
  if (flag) {  
                             mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态 
                         } else {  
                             Log.e(TAG, "从网络中获取文件大小失败 2");  
                         }  
                     }  
                 }).start();  
             } else {  
                 mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态 
             }  
         } else {  
             callBackError("getDownloaderInfoFromDB Error,May be EngineDBOperator is Null.");  
  throw new DownloaderErrorException(  
  "getDownloaderInfoFromDB Error,May be EngineDBOperator is Null.");  
         }  
     }  
  
  /** 
      * 从网络中获取文件大小,并更新listBeans 
      */ 
  private boolean getFileSizeByNetwork(DownloadBean bean) {  
         HttpURLConnection connection = null;  
  long fileSize = bean.fileSize;  
  try {  
  if (fileSize <= 0) {// 如果没有传入文件大小就从网络中获取 
                 URL url = new URL(bean.url);  
                 connection = (HttpURLConnection) url.openConnection();  
                 connection.setConnectTimeout(5000);  
                 connection.setReadTimeout(8000);  
  
  if (android.os.Build.VERSION.SDK_INT > 10) {// 规避2.x上因为加入setRM导致连接超时的bug 
                     connection.setRequestMethod("HEAD");// head 
                 }  
  
  int resopnseCode = connection.getResponseCode();  
  if (resopnseCode != 200 && resopnseCode != 206) {  
                     callBackError("http返回码不正确:" + resopnseCode);  
  return false;  
                 }  
  // 获得文件大小 
                 fileSize = connection.getContentLength();  
                 mBean.fileSize = fileSize;  
  
  if (fileSize <= 0) {  
                     callBackError("无法从服务器上获得文件大小" + fileSize);  
  return false;  
                 }  
  
  // if (connection.getHeaderField("Content-Range") == null) { 
  // Log.e(TAG, "服务器不支持断点续传"); 
  // mThreadCount = 1; 
  // } 
  
  // 如果没有存储空间了 
  if (!hasSpaceInSDCard()) {  
  return false;  
                 }  
  
  long range = fileSize / mThreadCount;// 文件分段值 
  // 更新listBean 
  for (int i = 0; i < mThreadCount - 1; i++) {  
                     DownloadBean subBean = mBeans.get(i);  
                     subBean.fileSize = fileSize;  
                     subBean.startPosition = i * range;  
                     subBean.endPosition = (i + 1) * range - 1;  
                 }  
  
                 DownloadBean subBean = mBeans.get(mThreadCount - 1);  
                 subBean.fileSize = fileSize;  
                 subBean.startPosition = (mThreadCount - 1) * range;  
                 subBean.endPosition = fileSize - 1;  
  
  // 更新数据库 
  if (mDBOper != null) {  
                     mDBOper.updateTaskCompleteSize(mBeans, mBean.url);  
                 } else {  
                     callBackError("getFileSizeByNetwork错误,可能是EngineDBOperator is Null.");  
  throw new DownloaderErrorException(  
  "getFileSizeByNetwork错误,可能是EngineDBOperator is Null.");  
                 }  
  return true;  
             } else {// 文件有大小就直接退出 
  return true;  
             }  
         } catch (Exception e) {  
             callBackError("从服务器获取文件大小超时");  
             e.printStackTrace();  
         } finally {  
  if (connection != null) {  
                 connection.disconnect();  
             }  
         }  
  return false;  
     }  
  
  /** 
      * 开始下载,可能多次调用 
      */ 
  public void startDownloader() {  
  if (mState == EngineConstants.DOWNLOAD_STATE_DOWNLOADING) {// 如果正在下载就return 
  return;  
         }  
  
  if (mBean == null) {  
             callBackError("下载器没有初始化");  
  return;  
         }  
  
         File file = new File(mBean.savePath);  
         File parentDirectory = file.getParentFile();  
  if (!parentDirectory.exists()) {  
             parentDirectory.mkdirs();  
         }  
  
  if (!file.exists()) {  
  try {  
                 file.createNewFile();  
             } catch (IOException e) {  
                 e.printStackTrace();  
             }  
         }  
  
  if (mBeans.size() < 1) {// 防止由于发生错误导致清空了mBeans列表,但是又重新开始了任务,所有要再次初始化mBeans 
  try {  
                 addDownloaderInfoToDB(mBean);  
             } catch (DownloaderErrorException e) {  
                 e.printStackTrace();  
  return;  
             }  
         }  
  
  /** 
          * 只有获得文件大小后才会开始下载 
          */ 
  synchronized (lock_getFileSize) {  
  if (mState == EngineConstants.DOWNLOAD_STATE_INIT) {// 获取文件大小失败,重新获取 
  boolean flag = getFileSizeByNetwork(mBean);  
  if (!flag) {  
                     callBackError("获取文件大小失败");  
  return;  
                 }  
             }  
         }  
  
         mState = EngineConstants.DOWNLOAD_STATE_DOWNLOADING;  
         mDBOper.removePauseFileByUrl(mBean.url);// 从暂停列表中移除 
         mDoneThreadCount = 0;// 初始化完成线程数 
  
  for (DownloadBean bean : mBeans) {  
  if (bean.currentPosition < (bean.endPosition - bean.startPosition)) {// 如果该线程属于没有下载完成的 
                 HamalThread hamalThread = new HamalThread(bean);  
                 hamalThread.start();  
             } else {// 已经完成的线程不需要重新创建 
                 mDoneThreadCount++;  
             }  
         }  
  
  if (mDoneThreadCount == mThreadCount) {// 下载完成 
             downloaderDone();  
         }  
     }  
  
  private class HamalThread extends Thread {  
  private int threadId;  
  private long startPos;  
  private long endPos;  
  private long compeleteSize;  
  private String urlstr;  
  
  public HamalThread(DownloadBean bean) {  
  this.threadId = bean.threadId;  
  this.startPos = bean.startPosition;  
  this.endPos = bean.endPosition;  
  this.compeleteSize = bean.currentPosition;  
  this.urlstr = bean.url;  
         }  
  
  @Override 
  public void run() {  
             HttpURLConnection connection = null;  
             RandomAccessFile randomAccessFile = null;  
             InputStream is = null;  
  try {  
                 URL url = new URL(urlstr);  
                 connection = (HttpURLConnection) url.openConnection();  
                 connection.setConnectTimeout(5000);  
                 connection.setReadTimeout(8000);  
                 connection.setRequestMethod("GET");  
  if (mThreadCount > 1) {// 多线程下载 
  // 设置范围,格式为Range:bytes x-y; 
                     connection.setRequestProperty("Range", "bytes=" 
                             + (startPos + compeleteSize) + "-" + endPos);  
                 }  
  
                 randomAccessFile = new RandomAccessFile(mBean.savePath, "rwd");  
                 randomAccessFile.seek(startPos + compeleteSize);  
  // 将要下载的文件写到保存在保存路径下的文件中 
                 is = connection.getInputStream();  
  byte[] buffer = new byte[bufferSize];  
  int length = -1;  
                 EngineUtil eUtil = EngineUtil.getInstance();  
  
  if (EngineVariable.SUPPORT_NETWORK_TYPE == EngineConstants.DOWNLOAD_NETWORK_ONLYWIFI) {// 如果只能是3G下载 
  if (eUtil.getNetworkType() != EngineConstants.NETWORK_STATE_WIFI) {// 且当前网络不是Wifi 
                         interruptDownloader();  
  return;  
                     }  
                 }  
  
  while ((length = is.read(buffer)) != -1) {  
  // 网络判断 
  if (EngineVariable.SUPPORT_NETWORK_TYPE == EngineConstants.DOWNLOAD_NETWORK_ONLYWIFI) {// 如果只能是3G下载 
  if (eUtil.getNetworkType() != EngineConstants.NETWORK_STATE_WIFI) {// 且当前网络不是Wifi 
                             interruptDownloader();  
  return;  
                         }  
                     }  
  
                     randomAccessFile.write(buffer, 0, length);  
                     compeleteSize += length;  
  synchronized (lock_refresh_progress) {  
                         mBean.currentPosition += length;  
                     }  
  // 更新数据库中的下载信息 
                     mDBOper.updateTaskCompleteSize(threadId, compeleteSize,  
                             urlstr);  
  if (mState == EngineConstants.DOWNLOAD_STATE_PAUSE  
                             || mState == EngineConstants.DOWNLOAD_STATE_INTERRUPT  
                             || mState == EngineConstants.DOWNLOAD_STATE_STOP  
                             || mState == EngineConstants.DOWNLOAD_STATE_ERROR) {// 暂停 
  return;  
                     }  
                 }  
  
  // 该子线程下载完成 
                 mDoneThreadCount++;  
             } catch (Exception e) {  
                 Log.e(TAG, "下载途中断掉了连接...");  
                 interruptDownloader();  
                 e.printStackTrace();  
             } finally {  
  try {  
                     is.close();  
                     randomAccessFile.close();  
                     connection.disconnect();  
                 } catch (Exception e) {  
                     e.printStackTrace();  
                 }  
             }  
  
  if (mDoneThreadCount == mThreadCount) {  
                 downloaderDone();  
             }  
         }  
     }  
  
  /** 
      * 获取下载进度 
      *  
      * @return 
      */ 
  public int getProgress() {  
  if (mBean.fileSize < 1) {  
  return 0;  
         }  
  return (int) (mBean.currentPosition * 100 / mBean.fileSize);  
     }  
  
  /** 
      * 暂停下载 
      */ 
  public void pauseDownloader() {  
         mState = EngineConstants.DOWNLOAD_STATE_PAUSE;  
         mDBOper.addPauseFile(mBean.url, mBean.packageName, mBean.fileId);  
     }  
  
  /** 
      * 中断下载(非人为的暂停) 
      */ 
  private void interruptDownloader() {  
         mState = EngineConstants.DOWNLOAD_STATE_INTERRUPT;  
     }  
  
  /** 
      * 结束下载 
      */ 
  public void stopDownloader() {  
         mState = EngineConstants.DOWNLOAD_STATE_STOP;  
         mBean.currentPosition = 0;  
         removeDownloaderInfo(mBean.url);  
     }  
  
  /** 
      * 清除下载的信息 
      *  
      * @param urlstr 
      */ 
  private void removeDownloaderInfo(String urlstr) {  
         mDBOper.deleteDownloadTaskByUrl(urlstr);  
         mDBOper.removePauseFileByUrl(urlstr);  
         mBeans.clear();  
     }  
  
  /** 
      * 下载完成 
      */ 
  private void downloaderDone() {  
         mState = EngineConstants.DOWNLOAD_STATE_DONE;  
         mBean.doneTime = System.currentTimeMillis();  
         mCallback.callbackWhenDownloadTaskListener(mState, mBean,  
                 mBean.fileName + "下载完成");  
  
         removeDownloaderInfo(mBean.url);  
         mDBOper.addCompleteTask(mBean);// 将完成信息保存至数据库 
     }  
  
  /** 
      * 出现错误时候回调 
      *  
      * @param info 
      */ 
  private void callBackError(String info) {  
         mState = EngineConstants.DOWNLOAD_STATE_ERROR;  
         mCallback.callbackWhenDownloadTaskListener(mState, mBean, info);  
         removeDownloaderInfo(mBean.url);  
     }  
  
  /** 
      * 判断SD卡上是否留有足够的空间 
      */ 
  private boolean hasSpaceInSDCard() {  
  if (mBean.fileSize > EngineUtil.getInstance().getFreeSpaceAtDirectory(  
                 Environment.getExternalStorageDirectory().getAbsolutePath())) {  
             callBackError("存储卡空间不够");  
  return false;  
         }  
  return true;  
     }  
 } 

源码:http://download.csdn.net/detail/h3c4lenovo/5987789

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CodeSheep的技术分享

利用K8S技术栈打造个人私有云(连载之:K8S集群搭建)

最近被业务折腾的死去活来,实在没时间发帖,花了好多个晚上才写好这篇帖子,后续会加油的!

44312
来自专栏移动端周边技术扩展

iOS打开系统功能对应的URL

1843
来自专栏程序员同行者

07-部署Flanneld网络

1394
来自专栏androidBlog

Android 8.0(Android O) AccountManager 行为变更

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

1610
来自专栏iOSDevLog

Android 原生 BLE 开发

6202
来自专栏FreeBuf

Scrounger:一款功能强大的移动端应用程序安全测试套件

今天给大家介绍的是一款名叫Scrounger 的工具,广大研究人员可以使用这款工具来对移动端应用程序的安全性进行测试。首先,这款工具参考和借鉴了很多目前安全社区...

1221
来自专栏技术小黑屋

聊一聊Android 6.0的运行时权限

Android 6.0,代号棉花糖,自发布伊始,其主要的特征运行时权限就很受关注。因为这一特征不仅改善了用户对于应用的使用体验,还使得应用开发者在实践开发中需要...

1024
来自专栏小怪聊职场

爬虫课堂(二十六)|使用scrapy-redis框架实现分布式爬虫(1)

3755
来自专栏编程思想之路

Android6.0蓝牙开发中获取附近低功耗蓝牙设备结果权限问题分析

问题描述: fang_fang_story 近期做一个扫描附近低功耗蓝牙设备获取到rssi并进行一系列的相对的定位的功能。在开发前期一直使用低版本(Andr...

26510
来自专栏Android源码框架分析

Android权限检查API checkSelfPermission失效问题为什么targetSdkVersion < 23 Context 的 checkSelfPermission失效target

6193

扫码关注云+社区

领取腾讯云代金券