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

Android多线程+单线程+断点续传+进度条显示下载功能

程序员文章站 2024-02-09 19:33:22
效果图 白话分析: 多线程:肯定是多个线程咯 断点:线程停止下载的位置 续传:线程从停止下载的位置上继续下载,直到完成任务为止。 核心分析: 断点: 当前...

效果图

Android多线程+单线程+断点续传+进度条显示下载功能

白话分析:

多线程:肯定是多个线程咯

断点:线程停止下载的位置

续传:线程从停止下载的位置上继续下载,直到完成任务为止。

核心分析:

断点:

当前线程已经下载的数据长度

续传:

向服务器请求上次线程停止下载位置的数据

con.setrequestproperty("range", "bytes=" + start + "-" + end);

分配线程:

 int currentpartsize = filesize / mthreadnum;

定义位置

定义线程开始下载的位置和结束的位置

for (int i = 0; i < mthreadnum; i++) {
int start = i * currentpartsize;//计算每条线程下载的开始位置
 int end = start + currentpartsize-1;//线程结束的位置
 if(i==mthreadnum-1){
   end=filesize;
  }}

创建数据库:

由于每一个文件要分成多个部分,要被不同的线程同时进行下载。当然要创建线程表,保存当前线程下载开始的位置和结束的位置,还有完成进度等。创建file表,保存当前下载的文件信息,比如:文件名,url,下载进度等信息
线程表:

public static final string create_table_sql="create table "+table_name+"(_id integer primary "
      +"key autoincrement, threadid, start , end, completed, url)";

file表:

public static final string create_table_sql="create table "+table_name+"(_id integer primary" +
    " key autoincrement ,filename, url, length, finished)";

创建线程类

无非就2个类,一个是线程管理类downloadmanager.java,核心方法:start(),stop(),restart(),addtask().clear()。另一个是线程任务类

downloadtask.java,就是一个线程类,用于下载线程分配好的任务。后面会贴出具体代码。

创建数据库方法类

无非就是单例模式,封装一些增删改查等基础数据库方法,后面会贴出具体代码。

创建实体类

也就是创建threadinfo和fileinfo这2个实体类,把下载文件信息和线程信息暂时存储起来。

引入的第三方开源库

numberprogressbar是一个关于进度条的开源库,挺不错的。直达链接

代码具体分析

1.首先是创建实体类,文件的实体类fileinfo,肯定有filename,url,length,finised,isstop,isdownloading这些属性。线程的实体类threadinfo肯定有threadid,start,end,completed,url这些属性。这些都很简单

 //thredinfo.java
  public class fileinfo {
  private string filename; //文件名
  private string url; //下载地址
  private int length; //文件大小
  private int finished; //下载已完成进度
  private boolean isstop=false; //是否暂停下载
  private boolean isdownloading=false; //是否正在下载
  public fileinfo(){
  }
  public fileinfo(string filename,string url){
    this.filename=filename;
    this.url=url;
  }
  public string getfilename() {
    return filename;
  }
  public void setfilename(string filename) {
    this.filename = filename;
  }
  public string geturl() {
    return url;
  }
  public void seturl(string url) {
    this.url = url;
  }
  public int getlength() {
    return length;
  }
  public void setlength(int length) {
    this.length = length;
  }
  public int getfinished() {
    return finished;
  }
  public void setfinished(int finished) {
    this.finished = finished;
  }
  public boolean isstop() {
    return isstop;
  }
  public void setstop(boolean stop) {
    isstop = stop;
  }
  public boolean isdownloading() {
    return isdownloading;
  }
  public void setdownloading(boolean downloading) {
    isdownloading = downloading;
  }
  @override
  public string tostring() {
    return "fileinfo{" +
        "filename='" + filename + '\'' +
        ", url='" + url + '\'' +
        ", length=" + length +
        ", finished=" + finished +
        ", isstop=" + isstop +
        ", isdownloading=" + isdownloading +
        '}';
  }}
 //fileinfo.java
  public class fileinfo {
  private string filename; //文件名
  private string url; //下载地址
  private int length; //文件大小
  private int finished; //下载已完成进度
  private boolean isstop=false; //是否暂停下载
  private boolean isdownloading=false; //是否正在下载
  public fileinfo(){
  }
  public fileinfo(string filename,string url){
    this.filename=filename;
    this.url=url;
  }
  public string getfilename() {
    return filename;
  }
  public void setfilename(string filename) {
    this.filename = filename;
  }
  public string geturl() {
    return url;
  }
  public void seturl(string url) {
    this.url = url;
  }
  public int getlength() {
    return length;
  }
  public void setlength(int length) {
    this.length = length;
  }
  public int getfinished() {
    return finished;
  }
  public void setfinished(int finished) {
    this.finished = finished;
  }
  public boolean isstop() {
    return isstop;
  }
  public void setstop(boolean stop) {
    isstop = stop;
  }
  public boolean isdownloading() {
    return isdownloading;
  }
  public void setdownloading(boolean downloading) {
    isdownloading = downloading;
  }
  @override
  public string tostring() {
    return "fileinfo{" +
        "filename='" + filename + '\'' +
        ", url='" + url + '\'' +
        ", length=" + length +
        ", finished=" + finished +
        ", isstop=" + isstop +
        ", isdownloading=" + isdownloading +
        '}';
  }}

2.实体类写完了,那么接下来写创建一个类,继承sqliteopenhelper类,来管理数据库连接,主要作用:管理数据库的初始化,并允许应用程序通过该类获取sqlitedatabase对象。

 public class threadhelper extends sqliteopenhelper{
  public static final string table_name="downthread";
  public static final string create_table_sql="create table "+table_name+"(_id integer primary "
      +"key autoincrement, threadid, start , end, completed, url)";
  public threadhelper(context context, string name, int version) {
    super(context, name, null, version);
  }
  @override
  public void oncreate(sqlitedatabase db) {
    db.execsql(create_table_sql);
  }
  @override
  public void onupgrade(sqlitedatabase db, int oldversion, int newversion) {
  }}

3.接下来封装一些数据库的增删改查操作,用的单例模式,用双重检验锁实现单例。好处:既能很大程度上确保线程安全,又能实现延迟加载。 缺点:使用volatile关键字会使jvm对该代码的优化丧失,影响性能。并且在一些高并发的情况,仍然可能会创建多个实例,这称为双重检验锁定失效。单例模式

 public class thread {
  private sqlitedatabase db;
  public static final string db_name="downthread.db3";
  public static final int version=1;
  private context mcontext;
  private volatile static thread t=null;
  private thread(){
    mcontext= baseapplication.getcontext();
    db=new threadhelper(mcontext,db_name,version).getreadabledatabase();
  }
  public static thread getinstance(){
    if(t==null){
      synchronized (thread.class){
        if(t==null){
          t=new thread();
        }
      }
    }
    return t;
  }
  public sqlitedatabase getdb(){
    return db;
  }
  //保存当前线程下载进度
  public synchronized void insert(threadinfo threadinfo){
    contentvalues values=new contentvalues();
    values.put("threadid",threadinfo.getthreadid());
    values.put("start",threadinfo.getstart());
    values.put("end",threadinfo.getend());
    values.put("completed",threadinfo.getcompeleted());
    values.put("url",threadinfo.geturl());
    long rowid=db.insert(threadhelper.table_name,null,values);
    if(rowid!=-1){
      utilslog.i("插入线程记录成功");
    }else{
      utilslog.i("插入线程记录失败");
    }
  }
  //查询当前线程 下载的进度
  public synchronized threadinfo query(string threadid,string queryurl){
    cursor cursor=db.query(threadhelper.table_name,null,"threadid= ? and url= ?",new string[]{threadid,queryurl},null,null,null);
    threadinfo info=new threadinfo();
    if(cursor!=null){
      while (cursor.movetonext()){
        int start=cursor.getint(2);
        int end=cursor.getint(3);
        int completed=cursor.getint(4);
        string url=cursor.getstring(5);
        info.setthreadid(threadid);
        info.setstart(start);
        info.setend(end);
        info.setcompeleted(completed);
        info.seturl(url);
      }
      cursor.close();
    }
    return info;
  }
  //更新当前线程下载进度
  public synchronized void update(threadinfo info){
    contentvalues values=new contentvalues();
    values.put("start",info.getstart());
    values.put("completed",info.getcompeleted());
    db.update(threadhelper.table_name,values,"threadid= ? and url= ?",new string[]{info.getthreadid(),info.geturl()});
  }
  //关闭db
  public void close(){
    db.close();
  }
  //判断多线程任务下载 是否第一次创建线程
  public boolean isexist(string url){
    cursor cursor=db.query(threadhelper.table_name,null,"url= ?",new string[]{url},null,null,null);
    boolean isexist=cursor.movetonext();
    cursor.close();
    return isexist;
  }
  public synchronized void delete(threadinfo info){
    long rowid=db.delete(threadhelper.table_name,"url =? and threadid= ?",new string[]{info.geturl(),info.getthreadid()});
    if(rowid!=-1){
      utilslog.i("删除下载线程记录成功");
    }else{
      utilslog.i("删除下载线程记录失败");
    }
  }
  public synchronized void delete(string url){
    long rowid=db.delete(threadhelper.table_name,"url =? ",new string[]{url});
    if(rowid!=-1){
      utilslog.i("删除下载线程记录成功");
    }else{
      utilslog.i("删除下载线程记录失败");
    }
  }}

4.基本的准备操作我们已经完成了,那么开始写关于下载的类吧。首先写的肯定是downloadmanager类,就是管理任务下载的类。不多说,直接看代码。

public class downloadmanager {
  private map<string, fileinfo> map = new hashmap<>();
  private static int mthreadnum;
  private int filesize;
  private boolean flag = false; //true第一次下载 false不是第一次下载
  private list<downloadtask> threads;
  private static fileinfo minfo;
  private static resultlistener mlistener;
  public static executorservice executorservice = executors.newcachedthreadpool();
  public static file file;
  private int totalcomleted;
  private downloadmanager() {
    threads = new arraylist<>();
  }
  public static downloadmanager getinstance(fileinfo info, int threadnum,resultlistener listener) {
    mlistener = listener;
    mthreadnum = threadnum;
    minfo = info;
    return downloadmanagerholder.dlm;
  }
  private static class downloadmanagerholder {
    private static final downloadmanager dlm = new downloadmanager();
  }
  public void start() {
    totalcomleted=0;
    clear();
    final fileinfo newinfo = download.getinstance().querydata(minfo.geturl());
    newinfo.setdownloading(true);
    map.put(minfo.geturl(),newinfo);
    prepare(newinfo);
  }
  //停止下载任务
  public void stop() {
    map.get(minfo.geturl()).setdownloading(false);
    map.get(minfo.geturl()).setstop(true);
  }
  public void clear(){
    if(threads.size()>0){
      threads.clear();
    }
  }
  //重新下载任务
  public void restart() {
    stop();
    try {
      file file = new file(com.cmazxiaoma.downloader.download.downloadmanager.file_path, map.get(minfo.geturl()).getfilename());
      if (file.exists()) {
        file.delete();
      }
      java.lang.thread.sleep(100);
    } catch (interruptedexception e) {
      e.printstacktrace();
    }
    download.getinstance().resetdata(minfo.geturl());
    start();
  }
  //获取当前任务状态, 是否在下载
  public boolean getcurrentstate() {
    return map.get(minfo.geturl()).isdownloading();
  }
  //添加下载任务
  public void addtask(fileinfo info) {
    //判断数据库是否已经存在此下载信息
    if (!download.getinstance().isexist(info)) {
      download.getinstance().insertdata(info);
      map.put(info.geturl(), info);
    } else {
      download.getinstance().delete(info);
      download.getinstance().insertdata(info);
      utilslog.i("map已经更新");
      map.remove(info.geturl());
      map.put(info.geturl(), info);
    }
  }
  private void prepare(final fileinfo newinfo) {
    new java.lang.thread(){
      @override
      public void run() {
        httpurlconnection con = null;
        randomaccessfile raf=null;
        try {
          //连接资源
          url url = new url(newinfo.geturl());
          utilslog.i("url=" + url);
          con = (httpurlconnection) url.openconnection();
          con.setconnecttimeout(2 * 1000);
          con.setrequestmethod("get");
          int length = -1;
          utilslog.i("responsecode=" + con.getresponsecode());
          if (con.getresponsecode() == 200) {
            length = con.getcontentlength();
            utilslog.i("文件大小=" + length);
          }
          if (length <= 0) {
            return;
          }
          //创建文件保存路径
          file dir = new file(com.cmazxiaoma.downloader.download.downloadmanager.file_path);
          if (!dir.exists()) {
            dir.mkdirs();//建立多级文件夹
          }
          newinfo.setlength(length);
          filesize = length;
          utilslog.i("当前线程id=" + java.lang.thread.currentthread().getid() + ",name=" + java.lang.thread.currentthread().getname());
          int currentpartsize = filesize / mthreadnum;
          file = new file(com.cmazxiaoma.downloader.download.downloadmanager.file_path, newinfo.getfilename());
          raf = new randomaccessfile(file, "rwd");
          raf.setlength(filesize);
          if (thread.getinstance().isexist(newinfo.geturl())) {
            flag = false;
          } else {
            flag = true;
          }
          for (int i = 0; i < mthreadnum; i++) {
            if (flag) {
              utilslog.i("第一次多线程下载");
              int start = i * currentpartsize;//计算每条线程下载的开始位置
              int end = start + currentpartsize-1;//线程结束的位置
              if(i==mthreadnum-1){
                end=filesize;
              }
              string threadid = "xiaoma" + i;
              threadinfo threadinfo = new threadinfo(threadid, start, end, 0,newinfo.geturl());
              thread.getinstance().insert(threadinfo);
              downloadtask thread = new downloadtask(threadinfo,newinfo, threadid, start, end, 0);
              downloadmanager.executorservice.execute(thread);
              threads.add(thread);
            } else {
              utilslog.i("不是第一次多线程下载");
              threadinfo threadinfo = thread.getinstance().query("xiaoma" + i, newinfo.geturl());
              downloadtask thread = new downloadtask(threadinfo,newinfo,threadinfo.getthreadid(),threadinfo.getstart(),threadinfo.getend(),threadinfo.getcompeleted());//这里出现过问题
              downloadmanager.executorservice.execute(thread);
              threads.add(thread);
            }
          }
          boolean iscompleted=false;
          while(!iscompleted){
            iscompleted=true;
            for(downloadtask thread:threads){
              totalcomleted+=thread.completed;
              if(!thread.iscompleted){
                iscompleted=false;
              }
            }
            if(newinfo.isstop()){
              totalcomleted=0;
              return;
            }
            message message=new message();
            message.what=0x555;
            message.arg1=filesize;
            message.arg2=totalcomleted;
            handler.sendmessage(message);
            if(iscompleted){
              totalcomleted=0;
              //任务线程全部完成,清空集合
              clear();
              handler.sendemptymessage(0x666);
              return;
            }
            totalcomleted=0;
            java.lang.thread.sleep(1000);
          }
        }catch (exception e) {
          e.printstacktrace();
        }finally {
          try {
            if (con != null) {
              con.disconnect();
            }
            if(raf!=null){
              raf.close();
            }
          } catch (ioexception e) {
            e.printstacktrace();
          }
        }
      }
    }.start();
  }
  private handler handler=new handler(){
    @override
    public void handlemessage(message msg) {
      super.handlemessage(msg);
      switch (msg.what){
        case 0x555:
          if(mlistener!=null){
            mlistener.progress(msg.arg1,msg.arg2);
          }
          break;
        case 0x666:
          if(mlistener!=null){
            mlistener.comleted();
          }
          break;
      }
    }
  };}

5.接下来呢,就是downloadtask类了,就是一个线程下载类。

 public class downloadtask extends java.lang.thread{
  private int start;//当前线程的开始下载位置
  private int end;//当前线程结束下载的位置
  private randomaccessfile raf;//当前线程负责下载的文件大小
  public int completed=0;//当前线程已下载的字节数
  private string threadid;//自己定义的线程id
  private fileinfo info;
  private threadinfo threadinfo;
  public boolean iscompleted=false; //true为当前线程完成任务,false为当前线程未完成任务
  //保存新的start
  public int finshed=0;
  public int newstart=0;
  public downloadtask(threadinfo threadinfo,fileinfo info,string threadid, int start, int end,int completed){
    this.threadinfo=threadinfo;
    this.info=info;
    this.threadid=threadid;
    this.start=start;
    this.end=end;
    this.completed=completed;
  }
  @override
  public void run() {
      httpurlconnection con = null;
      try {
        utilslog.i("start="+start+",end="+end+",completed="+completed+",threadid="+getthreadid());
        url url = new url(info.geturl());
        con = (httpurlconnection) url.openconnection();
        con.setconnecttimeout(2 * 1000);
        con.setrequestmethod("get");
        con.setrequestproperty("range", "bytes=" + start + "-"+end);//重点
        raf=new randomaccessfile(downloadmanager.file,"rwd");
        //从文件的某一位置写入
        raf.seek(start);
        if (con.getresponsecode() == 206) { //文件部分下载 返回码是206
          inputstream is = con.getinputstream();
          byte[] buffer = new byte[4096];
          int hasread = 0;
          while ((hasread = is.read(buffer)) != -1) {
            //写入文件
            raf.write(buffer, 0, hasread);
            //单个文件的完成程度
            completed += hasread;
            threadinfo.setcompeleted(completed);
            //保存新的start
            finshed=finshed+hasread;//这里出现过问题,嘻嘻
            newstart=start+finshed;
            threadinfo.setstart(newstart);
            //utilslog.i("thread:"+getthreadid()+",completed="   + completed);
            //停止下载
            if (info.isstop()) {
              utilslog.i("isstop="+info.isstop());
              //保存下载进度
              utilslog.i("现在thread:"+getthreadid()+",completed=" + completed);
              thread.getinstance().update(threadinfo);
              return;
            }
          }
          //删除该线程下载记录
          thread.getinstance().delete(threadinfo);
          iscompleted=true;
          thread.getinstance().update(threadinfo);
          utilslog.i("thread:"+getthreadid()+"已经完成任务!--"+"completed="+completed);
        }
      } catch (exception e) {
        if (con != null) {
          con.disconnect();
        }
        try {
          if (raf != null) {
            raf.close();
          }
        } catch (ioexception e1) {
          e1.printstacktrace();
        }
      }
    }
  public string getthreadid() {
    return threadid;
  }}

6.接口,就是一个监听下载进度的接口,也是很简单。

 public interface resultlistener{
  void progress(int max, int progress);
  void comleted();}

结束

大致操作就是这样,其实多线程也挺简单的。

以上所述是小编给大家介绍的android多线程+单线程+断点续传+进度条显示下载功能,希望对大家有所帮助