Android原生实现多线程断点下载实例代码

各位父老乡亲,我单汉三又回来了,今天为大家带来一个用原生的安卓写的多线程断点下载Demo。

通过本文你可以学习到:

    SQLite的基本使用,数据库的增删改查。 Handler的消息处理与更新UI。 Service(主要用于下载)的进阶与使用。 原生的json文件解析(多层嵌套)。 RandomAccessFile的基本使用,可以将文件分段。 基于HttpURLConnection的大文件下载。 上面内容结合,实现多线程,断点下载。

Demo是在TV上运行的,图片显示的问题不要纠结了。

文件下载的Demo已完成,没时间上传与讲解,今天为您展示并讲解一下,纯原生的东西来下载文件,希望可以帮你理解更多安卓比较基础的问题。

我们的思路:建立一个数据库,两个表,一个用来保存网络数据,一个保存本地下载的进度等等。在点击下载按钮的时候启动DownloadService,进行比对之后下载

先看一下Demo的目录结构:

所有的步骤在代码里有非常详细的讲解,一定要看代码(下面是抽取的几个重要的类讲解)!

数据库的建立与DAO

/** * Created by Administrator on 2017/3/6 0006. */public class DownLoadDBHelper extends SQLiteOpenHelper {  /**   * DownLoadDBHelper用于创建数据库,如果不会使用原生的建库的话   *   * 跟随小司机我的脚步来一起练一练   * 建两个表:   * download_info表存储下载信息   * localdownload_info表存储本地下载信息   * 之后对比两个表进行继续下载等等   */  public static String DATABASE_NAME = "downloadFILES.db";  public static String TABLE_DOWNLOAD_INFO = "download_info";  public static String TABLE_LOCALDOWNLOAD_INFO = "localdownload_info";  private static int version = 1;  public DownLoadDBHelper(Context context) {    super(context, DATABASE_NAME, null, version);  }  @Override  public void onCreate(SQLiteDatabase db) {    /*在此进行创建数据库和表格,来一起动手写一遍,就是两个sqlite语句*/    db.execSQL("create table " + TABLE_DOWNLOAD_INFO + "(" + "id integer PRIMARY KEY AUTOINCREMENT," +        "thread_id integer," + "start_position integer," + "end_position integer," + " completed_size integer," + "url varchar(100))");    db.execSQL("create table " + TABLE_LOCALDOWNLOAD_INFO + "(" + "id integer PRIMARY KEY AUTOINCREMENT," + "name varchar(50)," +        "url varchar(100)," + "completedSize integer," + "fileSize integer," + "status integer)");  }  @Override  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {    /*数据库更新升级,检测到版本的变化,发现版本号不一样,就会自动调用onUpgrade函数    * 新版本号和老版本号都会作为onUpgrade函数的参数传进来,便于开发者知道数据库应该从哪个版本升级到哪个版本。    * */    String sql = "drop table if exists " + TABLE_DOWNLOAD_INFO + "";    String sqlOne = "drop table if exists " + TABLE_LOCALDOWNLOAD_INFO + "";    db.execSQL(sql);    db.execSQL(sqlOne);    onCreate(db);//删除数据库,重新创建。这里只是简单的,并没有添加或者减少数据库中的其他字段  }}

DAO对数据库进行增删改查

/** * Created by ShanCanCan on 2017/3/6 0006. */public class Dao {  /*  * DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此。  * DAO层所定义的接口里的方法都大同小异,这是由我们在DAO层对数据库访问的操作来决定的,  * 对数据库的操作,我们基本要用到的就是新增,更新,删除,查询等方法。  * 因而DAO层里面基本上都应该要涵盖这些方法对应的操作。  * */  private static Dao dao;  private static DownLoadDBHelper dbHelper;  public static final byte[] Lock = new byte[0]; //新建两个字节作为对象锁  public static final byte[] file_Lock = new byte[0];  public Dao() {//空构造方法,  }  public static synchronized Dao getInstance(Context context) {//本demo用单例模式中的懒汉模式+线程不安全 线程安全的代价是效率变低    if (dao == null) {      dao = new Dao();      dbHelper = new DownLoadDBHelper(context);    }    return dao;  }  /* public static synchronized Dao getInstance(Context context) {//本demo用单例模式中的懒汉模式+线程安全 线程安全的代价是效率变低,99%情况下不需要同步    if (dao == null) { //你可以在这两个方法中随便选择一个      dao = new Dao();      dbHelper = new DownLoadDBHelper(context);    }    return dao;  }*/  /***************************************  下方Dao层中对数据库的增、删、改、查  *********************************************************/  /**   * 检查本地下载记录,是否下载过   *   * @param url   * @return   */  public boolean isExist(String url) {    SQLiteDatabase database = dbHelper.getReadableDatabase(); //获取本app所创建的数据库    String sql = "select count(*) from " + TABLE_LOCALDOWNLOAD_INFO + " where url=?"; //查询语句,查询总共有多少条的语句    Cursor cursor = database.rawQuery(sql, new String[]{url});    /**     *     * @Cursor     * Cursor 是每行的集合。     * 使用 moveToFirst() 定位第一行。     * 你必须知道每一列的名称。     * 你必须知道每一列的数据类型。     * Cursor 是一个随机的数据源。     * 所有的数据都是通过下标取得。     * Cursor按照我的理解就是一个箭头,指到哪一行就是那一行的集合     * 比较重要的方法有:close(),moveToFirst(),moveToNext(),moveToLast(),moveToPrevious(),getColumnCount()等。     *     * @rawQuery     * rawQuery是直接使用SQL语句进行查询的,也就是第一个参数字符串,     * 在字符串内的“?”会被后面的String[]数组逐一对换掉     * cursor用完之后要关闭,cursor用完之后要关闭,cursor用完之后要关闭。重要的事情说三遍!!!     *     * */    cursor.moveToFirst();    int count = cursor.getInt(0);    cursor.close();    return count > 0;  }  /**   * 是否为首次下载   *   * @param url   * @return   */  public boolean isFirstDownload(String url) {    SQLiteDatabase database = dbHelper.getReadableDatabase();    String sql = "select count(*) from " + TABLE_DOWNLOAD_INFO + " where url=?";    Cursor cursor = database.rawQuery(sql, new String[]{url});    cursor.moveToFirst();    int count = cursor.getInt(0);    cursor.close();    return count == 0;  }  /**   * 保存下载的具体信息 保存所下载的list集合中的数据   *   * @param infos   * @param context   */  public void saveInfos(List<DownLoadInfo> infos, Context context) {    /**     * 事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。     * 这些操作要么都做,要么都不做,是一个不可分割的工作单位。     * 通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,     * 以便保持数据的完整性。     *     * 事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、     * 隔离性( Isolation )和持续性( Durability )。这四个特性简称为 ACID 特性。     *     * */    synchronized (Lock) {      SQLiteDatabase database = dbHelper.getWritableDatabase();      database.beginTransaction();//开启事务      try {//如果有异常,在这里捕获        for (DownLoadInfo info : infos) {//for循环将数据存入数据库          String sql = "insert into " + TABLE_DOWNLOAD_INFO + "(thread_id,start_position, end_position, completed_size, url) values (?,?,?,?,?)";          Object[] bindArgs = {info.getThreadId(), info.getStartPosition(), info.getEndPosition(), info.getCompletedSize(), info.getUrl()};          database.execSQL(sql, bindArgs);        }        database.setTransactionSuccessful();//结束事务      } catch (SQLException e) {        e.printStackTrace();      } finally {        database.endTransaction();//关闭事务      }    }  }  /**   * 得到下载具体信息   *   * @param urlstr   * @return List<DownloadInfo> 一个下载器信息集合器,里面存放了每条线程的下载信息   */  public List<DownLoadInfo> getInfos(String urlstr) {    List<DownLoadInfo> list = new ArrayList<DownLoadInfo>();    SQLiteDatabase database = dbHelper.getReadableDatabase();    String sql = "select thread_id, start_position, end_position, completed_size, url from " + TABLE_DOWNLOAD_INFO + " where url=?";    Cursor cursor = database.rawQuery(sql, new String[]{urlstr});    while (cursor.moveToNext()) {//通过cursor取到下载器信息,循环遍历,得到下载器集合      DownLoadInfo info = new DownLoadInfo(cursor.getInt(0), cursor.getInt(1), cursor.getInt(2), cursor.getInt(3), cursor.getString(4));      list.add(info);    }    cursor.close();    return list;  }  /**   * 本地下载列表添加记录,添加本地数据库信息,完成度等等   *   * @param fileStatus   **/  public void insertFileStatus(FileStatus fileStatus) {    synchronized (file_Lock) {//异步加开启事务,保证数据的完整性      SQLiteDatabase database = dbHelper.getWritableDatabase();      database.beginTransaction();      try {        String sql = "insert into " + TABLE_LOCALDOWNLOAD_INFO + " (name,url,completedSize,fileSize,status) values(?,?,?,?,?)";        Object[] bindArgs = {fileStatus.getName(), fileStatus.getUrl(), fileStatus.getCompletedSize(), fileStatus.getFileSize(), fileStatus.getStatus()};        database.execSQL(sql, bindArgs);        database.setTransactionSuccessful();      } catch (SQLException e) {        e.printStackTrace();      } finally {        database.endTransaction();      }    }  }  /**   * @param context   * @param compeletedSize   * @param threadId   * @param urlstr     这里是更新数据库,建议在保存一个表格的时候就对另一个表格数据库进行更新   */  public void updataInfos(int threadId, int compeletedSize, String urlstr, Context context) {    synchronized (Lock) {      String sql = "update " + TABLE_DOWNLOAD_INFO + "set completed_size = ? where thread_id =? and url=?";      String localSql = "update " + TABLE_LOCALDOWNLOAD_INFO + "set completedSize = (select sum(completed_size) from " +          TABLE_DOWNLOAD_INFO + "where url=? group by url ) where url=?";      Object[] bindArgs = {compeletedSize, threadId, urlstr};      Object[] localArgs = {urlstr, urlstr};      SQLiteDatabase database = dbHelper.getWritableDatabase();      database.beginTransaction();      try {        database.execSQL(sql, bindArgs);        database.execSQL(localSql, localArgs);        database.setTransactionSuccessful();      } catch (SQLException e) {        e.printStackTrace();      } finally {        database.endTransaction();      }    }  }  /**   * @param url 更新文件的状态,0为正在下载,1为已经下载完成,2为下载出错   **/  public void updateFileStatus(String url) {    synchronized (file_Lock) {      String sql = "update " + TABLE_LOCALDOWNLOAD_INFO + " set status = ? where url = ?";      Object[] bindArgs = {1, url};      SQLiteDatabase database = dbHelper.getWritableDatabase();      database.beginTransaction();      try {        database.execSQL(sql, bindArgs);        database.setTransactionSuccessful();      } catch (SQLException e) {        e.printStackTrace();      } finally {        database.endTransaction();      }    }  }  /**   * @return List<FileStatus>   * 取出本地下载列表数据,如在重新进入应用时,要重新把进度之类的设置好   **/  public List<FileStatus> getFileStatus() {    List<FileStatus> list = new ArrayList<FileStatus>();    SQLiteDatabase database = dbHelper.getReadableDatabase();    //String sql = "slect * from " + TABLE_LOCALDOWNLOAD_INFO + ""; //不能用,需要哪些条件就在语句中写出哪些条件    String sql = "select name, url, status, completedSize, fileSize from " + TABLE_LOCALDOWNLOAD_INFO + "";    Cursor cursor = database.rawQuery(sql, null);    while (cursor.moveToNext()) {      FileStatus fileState = new FileStatus(cursor.getString(0), cursor.getString(1), cursor.getInt(2), cursor.getInt(3), cursor.getInt(4));      list.add(fileState);    }    cursor.close();    return list;  }  /**   * @param url   * @param completeSize   * @param status    更新文件的下载状态   **/  public void updateFileDownStatus(int completeSize, int status, String url) {    synchronized (file_Lock) {      String sql = "update " + TABLE_LOCALDOWNLOAD_INFO + " set completedSize = ?,status = ? where url = ?";      SQLiteDatabase database = dbHelper.getWritableDatabase();      database.beginTransaction();      try {        Object[] bindArgs = {completeSize, status, url};        database.execSQL(sql, bindArgs);        database.delete(TABLE_DOWNLOAD_INFO, "url = ?", new String[]{url});        database.setTransactionSuccessful();      } catch (SQLException e) {        e.printStackTrace();      } finally {        database.endTransaction();      }    }  }  /**   * @param url 获取文件名称   **/  public String getFileName(String url) {    String result = "";    String sql = "select name from " + TABLE_LOCALDOWNLOAD_INFO + " where url = ?";    SQLiteDatabase database = dbHelper.getReadableDatabase();    Cursor cursor = database.rawQuery(sql, new String[]{url});    if (cursor.moveToNext()) {      result = cursor.getString(0);    }    cursor.close();    return result;  }  /**   * 删除文件之后,要删除下载的数据,一个是用户可以重新下载   * 另一个是表再次添加一条数据的时候不出现错误   *   * @param url   */  public void deleteFile(String url) {    SQLiteDatabase database = dbHelper.getWritableDatabase();    database.beginTransaction();    try {      database.delete(TABLE_DOWNLOAD_INFO, " url = ?", new String[]{url});      database.delete(TABLE_LOCALDOWNLOAD_INFO, " url = ?", new String[]{url});      database.setTransactionSuccessful();    } catch (Exception e) {      e.printStackTrace();    } finally {      database.endTransaction();    }  }  /**   * 关闭数据库   *   * @close   */  public void closeDB() {    dbHelper.close();  }}

DownloadService 主要代码

@SuppressLint("HandlerLeak")public class DownloadService extends Service{ public IBinder binder = new MyBinder(); public class MyBinder extends Binder { public DownloadService getService() {  return DownloadService.this; } } @Override public IBinder onBind(Intent intent) { return binder; } public static int number = 0; // 文件保存地址 public final String savePath = "/mnt/sdcard/MultiFileDownload/"; // 存放下载列表的引用 public static List<FileStatus> list = new ArrayList<FileStatus>(); public static Map<String, String> localDownList = new HashMap<String, String>(); // 保存每个文件下载的下载器 public static Map<String, Downloader> downloaders = new HashMap<String, Downloader>(); // 每个下载文件完成的长度 private Map<String, Integer> completeSizes = new HashMap<String, Integer>(); // 每个下载文件的总长度 private Map<String, Integer> fileSizes = new HashMap<String, Integer>(); private Downloader downloader; private int threadCount = 5; private Dao dao; private DownLoadCallback loadCallback;  private FileStatus mFileStatus = null; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) {  super.handleMessage(msg);  if (msg.what == 1)  {  String url = (String) msg.obj;  int length = msg.arg1;  int completeSize = completeSizes.get(url);  int fileSize = fileSizes.get(url);  completeSize += length;  completeSizes.put(url, completeSize);  synchronized (list)  {   for (int i = 0; i < list.size(); i++)   {   FileStatus fileStatus = list.get(i);   if (fileStatus.getUrl().equals(url))   {    if (completeSize == fileStatus.getFileSize())    {    System.out.println("-----------下载完成:"+fileStatus.getName()+":"+completeSize+"-----"+fileStatus.getFileSize());    list.set(i, new FileStatus(fileStatus.getName(), fileStatus.getUrl(), 1, completeSize, fileStatus.getFileSize()));    dao.updateFileDownStatus(completeSize, 1, url);    }    else    {    list.set(i, new FileStatus(fileStatus.getName(), fileStatus.getUrl(), 0, completeSize, fileStatus.getFileSize()));    }    mFileStatus = list.get(i);   }   }      this.postDelayed(new Runnable()   {   @Override   public void run()   {    if (loadCallback != null && mFileStatus != null)    {    loadCallback.refreshUI(mFileStatus);    }   }   }, 1000);  }    } } }; @Override public void onCreate() { super.onCreate(); dao = Dao.getInstance(this); list = dao.getFileStatus(); for (FileStatus fileStatus : list) {  localDownList.put(fileStatus.getUrl(), fileStatus.getUrl()); }  Timer timer = new Timer(); timer.schedule(new TimerTask() {  @Override  public void run()  {  number++;  } }, 0, 1000); } public void download(final Button button, final String url, final String name, final Handler mHandler) { if (dao.isExist(url)) {  Toast.makeText(this, "别点了,已经在下载了", Toast.LENGTH_SHORT).show();  return; } final String fileName = name + url.substring(url.lastIndexOf(".")); new Thread(new Runnable() {  @Override  public void run()  {  downloader = downloaders.get(url);  if (downloader == null)  {   downloader = new Downloader(url, savePath, fileName, threadCount, DownloadService.this, handler);   downloaders.put(url, downloader);  }  if (downloader.isDownloading())   return;    LoadInfo loadInfo = downloader.getDownloaderInfors();    if(loadInfo != null)  {   FileStatus fileStatus = new FileStatus(fileName, url, 0, loadInfo.getComplete(), loadInfo.getFileSize());   dao.insertFileStatus(fileStatus);   completeSizes.put(url, loadInfo.getComplete());   fileSizes.put(url, fileStatus.getFileSize());   list.add(fileStatus);   localDownList.put(url, url);   downloader.download();   Message msg = new Message();   msg.what = 1;   msg.obj = button;   mHandler.sendMessage(msg);  }  else  {   Message msg = new Message();   msg.what = 2;   msg.obj = button;   mHandler.sendMessage(msg);  }  } }).start(); }  //暂停下载 public void Pause(Downloader downloader) { downloader.pause(); }  //继续下载 public void reDownload(final Button button, final String url, final String name, final Handler mHandler) { new Thread(new Runnable() {  @Override  public void run()  {  String fileName = dao.getFileName(url);    downloader = downloaders.get(url);  if (downloader == null)  {   downloader = new Downloader(url, savePath, fileName, threadCount, DownloadService.this, handler);   downloaders.put(url, downloader);  }  if (downloader.isDownloading())   return;    LoadInfo loadInfo = downloader.getDownloaderInfors();    if(loadInfo != null && !fileName.equals(""))  {   if(!completeSizes.containsKey(url))   {   completeSizes.put(url, loadInfo.getComplete());   }   if(!fileSizes.containsKey(url))   {   fileSizes.put(url, loadInfo.getFileSize());   }   downloader.download();   Message msg = new Message();   msg.what = 1;   msg.obj = button;   mHandler.sendMessage(msg);  }  else  {   Message msg = new Message();   msg.what = 2;   msg.obj = button;   mHandler.sendMessage(msg);  }  } }).start(); }  public void delete(final String url) { Downloader down = downloaders.get(url); if(down != null) {  down.pause(); }  handler.postDelayed(new Runnable() {  @Override  public void run()  {  dao.deleteFile(url);    for (int i = 0; i < list.size(); i++)  {   FileStatus fileStatus = list.get(i);   if (fileStatus.getUrl().equals(url))   {   list.remove(i);   }  }    localDownList.remove(url);  downloaders.remove(url);  completeSizes.remove(url);  fileSizes.remove(url);    if(loadCallback != null)  {   loadCallback.deleteFile(url);  }  } }, 1000); } public interface DownLoadCallback { public void refreshUI(FileStatus fileStatus);  public void deleteFile(String url); } public void setLoadCallback(DownLoadCallback loadCallback) { this.loadCallback = loadCallback; }}

下载工具类DownLoadUtil

/** * Created by ShanCanCan on 2017/3/7 0007. */public class DownLoadUtil {  /**   * 此类的主要功能   * 1、检查是否下载   * 2、下载文件,文件的下载采用httpurlconnection   */  private String downPath;// 下载路径  private String savePath;// 保存路径  private String fileName;// 文件名称  private int threadCount;// 线程数  private Handler mHandler;  private Dao dao;  private Context context;  private int fileSize;// 文件大小  private int range;  private List<DownLoadInfo> infos;// 存放下载信息类的集合  private int state = INIT;  private static final int INIT = 1;// 定义三种下载的状态:初始化状态,正在下载状态,暂停状态  private static final int DOWNLOADING = 2;  private static final int PAUSE = 3;  /**   * 构造方法,获取dao的对象   *   * @param downPath   * @param savePath   * @param fileName   * @param threadCount   * @param context   * @param mHandler   */  public DownLoadUtil(String downPath, String savePath, String fileName, int threadCount, Handler mHandler, Context context) {    this.downPath = downPath;    this.savePath = savePath;    this.fileName = fileName;    this.threadCount = threadCount;    this.mHandler = mHandler;    this.context = context;    dao = Dao.getInstance(context);  }  /**   * 判断是否PAUSE   **/  public boolean isPause() {    return state == PAUSE;  }  /**   * 判断是否DOWNLOADING   */  public boolean isDownloading() {    return state == DOWNLOADING;  }  /**   * @param url 判断是否是第一次下载,利用dao查询数据库中是否有下载这个地址的记录   */  private boolean isFirst(String url) {    return dao.isFirstDownload(url);  }  /**   * 获取要下载的东西   */  public LoadItemInfo getDownloadInfos() {    if (isFirst(downPath)) {      if (initFirst()) {//如果是第一次下载的话,要进行初始化,1.获得下载文件的长度 2.创建文件,设置文件的大小        range = this.fileSize / this.threadCount;        infos = new ArrayList<DownLoadInfo>();        //这里就是启动多线程下载,看出来了吗?配合RandomAccessFile。每一个DownLoadInfo就是RandomAccessFile文件的一部分        for (int i = 0; i < this.threadCount - 1; i++) {          DownLoadInfo info = new DownLoadInfo(i, i * range, (i + 1) * range - 1, 0, downPath);          infos.add(info);        }        DownLoadInfo info = new DownLoadInfo(this.threadCount - 1, (this.threadCount - 1) * range, this.fileSize, 0, downPath);        infos.add(info);        dao.saveInfos(infos, this.context);        //(String urlDownload, int completePercent, int fileSize)        LoadItemInfo loadInfo = new LoadItemInfo(this.downPath, 0, this.fileSize);        return loadInfo;      } else {        return null;      }    } else {      //不是第一次下载,我们应该怎么做呢?从数据库里面取回来      infos = dao.getInfos(this.downPath);      if (infos != null && infos.size() > 0) {        int size = 0;        int completeSize = 0;        for (DownLoadInfo info : infos) {          completeSize += info.getCompletedSize();          size += info.getEndPosition() - info.getStartPosition() + this.threadCount - 1;        }        LoadItemInfo loadInfo = new LoadItemInfo(this.downPath, completeSize, size);        return loadInfo;      } else {        return null;      }    }  }  // 设置暂停  public void pause() {    state = PAUSE;  }  // 重置下载状态,将下载状态设置为init初始化状态  public void reset() {    state = INIT;  }  /**   * 基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream结合起来,再加上它自己的一些方法,   * 比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )、skipBytes()跳过多少字节数。   * 此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件。   */  private boolean initFirst() {    boolean result = true;    HttpURLConnection conn = null;    RandomAccessFile randomFile = null;    URL url = null;    try {      url = new URL(downPath);      conn = (HttpURLConnection) url.openConnection();      conn.setConnectTimeout(5 * 1000);      conn.setRequestMethod("GET");      // 如果http返回的代码是200或者206则为连接成功      if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) //状态码(206),表示服务器已经执行完部分对资源的GET请求      {        fileSize = conn.getContentLength();// 得到文件的大小        if (fileSize <= 0) {          //("网络故障,无法获取文件大小");          return false;        }        File dir = new File(savePath);        // 如果文件目录不存在,则创建        if (!dir.exists()) {          if (dir.mkdirs()) {            //("mkdirs success.");          }        }        File file = new File(this.savePath, this.fileName);        randomFile = new RandomAccessFile(file, "rwd");        randomFile.setLength(fileSize);// 设置保存文件的大小        randomFile.close();        conn.disconnect();      }    } catch (Exception e) {      e.printStackTrace();      result = false;    } finally {      if (randomFile != null) {        try {          randomFile.close();        } catch (IOException e) {          e.printStackTrace();        }      }      if (conn != null) {        conn.disconnect();      }    }    return result;  }  /**   * 下面的这个方法就是开启多线程进行下载了数据了   */  public void downLoad() {    if (infos != null) {      if (state == DOWNLOADING) {        return;      }      state = DOWNLOADING;// 把状态设置为正在下载      for (DownLoadInfo info : infos) {//为什么说我们是多线程呢?因为我们分别用新线程去下载刚才分割好的一个RandomAccessFile文件        new DownLoadThread(info.getThreadId(), info.getStartPosition(), info.getEndPosition(), info.getCompletedSize(), info.getUrl(), this.context).start();      }    }  }  /**   * 现在要创建线程用来下载了,这里采用内部类   */  public class DownLoadThread extends Thread {    private int threadId;    private int startPostion;    private int endPostion;    private int compeletedSize;    private String url;    private Context context;    public static final int PROGRESS = 1;    public DownLoadThread(int threadId, int startPostion, int endPostion, int compeletedSize, String url, Context context) {//构造方法,传入特定的参数      this.threadId = threadId;      this.startPostion = startPostion;      this.endPostion = endPostion;      this.compeletedSize = compeletedSize;      this.url = url;      this.context = context;    }    //开始下载    @Override    public void run() {      HttpURLConnection conn = null;      RandomAccessFile randomAccessFile = null;      InputStream inStream = null;      File file = new File(savePath, fileName);      URL url = null;      try {        url = new URL(this.url);        conn = (HttpURLConnection) url.openConnection();        constructConnection(conn);        if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) {          randomAccessFile = new RandomAccessFile(file, "rwd");          randomAccessFile.seek(this.startPostion + this.compeletedSize);//RandomAccessFile移动指针,到需要下载的块          inStream = conn.getInputStream();          byte buffer[] = new byte[4096];//这个4096为么子呢?我也不知道,就是看阿里的人下载apk的时候都用4096,我也用          int length = 0;          while ((length = inStream.read(buffer, 0, buffer.length)) != -1) {            randomAccessFile.write(buffer, 0, length);            compeletedSize += length;            // 更新数据库中的下载信息            dao.updataInfos(threadId, compeletedSize, this.url, this.context);            // 用消息将下载信息传给进度条,对进度条进行更新            Message message = Message.obtain();            message.what = PROGRESS;            message.obj = this.url;            message.arg1 = length;            mHandler.sendMessage(message);// 给DownloadService发送消息            if (state == PAUSE) {              //("-----pause-----");              return;            }          }          // ("------------线程:" + this.threadId + "下载完成");        }      } catch (IOException e) {        e.printStackTrace();        //("-----下载异常-----"); 这里下载异常我就不处理了,你可以发一条重新下载的消息      } finally {//用完只后流要关闭,不然容易造成资源抢占,内存泄漏        try {          if (inStream != null) {            inStream.close();          }          if (randomAccessFile != null) {            randomAccessFile.close();          }          if (conn != null) {            conn.disconnect();          }        } catch (IOException e) {          e.printStackTrace();        }      }    }    /**     * 构建请求连接时的参数 返回开始下载的位置     *     * @param conn     */    private void constructConnection(HttpURLConnection conn) throws IOException {      conn.setConnectTimeout(5 * 1000);// 设置连接超时5秒      conn.setRequestMethod("GET");// GET方式提交,如果你是用post请求必须添加 conn.setDoOutput(true); conn.setDoInput(true);      conn.setRequestProperty(          "Accept",          "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");      conn.setRequestProperty("Accept-Language", "zh-CN");      conn.setRequestProperty("Referer", this.url);      conn.setRequestProperty("Charset", "UTF-8");      int startPositionNew = this.startPostion + this.compeletedSize;      // 设置获取实体数据的范围      conn.setRequestProperty("Range", "bytes=" + startPositionNew + "-" + this.endPostion);      conn.setRequestProperty(          "User-Agent",          "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");      conn.setRequestProperty("Connection", "Keep-Alive");      conn.connect();    }  }}

Github地址:https://github.com/Shanlovana/DownLoadFiles/

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

当你感到悲哀痛苦时,最好是去学些什么东西。

Android原生实现多线程断点下载实例代码

相关文章:

你感兴趣的文章:

标签云: