欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-11-29 11:23:28
各位父老乡亲,我单汉三又回来了,今天为大家带来一个用原生的安卓写的多线程断点下载demo。 通过本文你可以学习到: sqlite的基本使用,数据库的增删改查。...

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

通过本文你可以学习到:

  1. sqlite的基本使用,数据库的增删改查。
  2. handler的消息处理与更新ui。
  3. service(主要用于下载)的进阶与使用。
  4. 原生的json文件解析(多层嵌套)。
  5. randomaccessfile的基本使用,可以将文件分段。
  6. 基于httpurlconnection的大文件下载。
  7. 上面内容结合,实现多线程,断点下载。

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

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

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

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

先看一下demo的目录结构:

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

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

数据库的建立与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/

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