当前位置: 移动技术网 > IT编程>移动开发>Android > Android入门:多线程断点下载详细介绍

Android入门:多线程断点下载详细介绍

2019年07月24日  | 移动技术网IT编程  | 我要评论

文秘家园,陌月流光,家用净水器浩泽

本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度。也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文件,下载中断后,重新进入应用点击下载,程序检查有没有本地文件的存在,若存在,获取本地文件中的下载进度,继续进行下载。当下载完成后,自动删除本地文件。

一、多线程断点下载介绍

所谓的多线程断点下载就是利用多线程下载,并且可被中断,如果突然没电了,重启手机后可以继续下载,而不需要重新下载;

利用的技术有:sqlite存储各个线程的下载量,http请求获得下载数据;

二、辅助类介绍

为了完成多线程断点下载我们需要预先编写一些辅助类:

(1)dbopenhelper

(2)fileservice:
-map<integer,integer> getdata(string path); 根据url获得各个线程的下载量
-save(string path, map<integer, integer> map);存储url对应的各个线程下载量,此函数为刚刚开始时调用
-update(string path, map<integer, integer> map);更新数据库中url对应的各个线程的下载量;
-delete(string path);删除url对应的数据;

(3)filedownloader:
-getfilesize();获得下载文件的大小
-download(downloadprogresslistener listener);下载文件,并设置监听器

(4)downloadthread:此类在filedownloader的download中执行;

先将辅助类列出:
dbopenhelper.java

package service; 
 
import android.content.context; 
import android.database.sqlite.sqlitedatabase; 
import android.database.sqlite.sqliteopenhelper; 
 
public class dbopenhelper extends sqliteopenhelper { 
  private static final string dbname = "download.db"; 
  private static final int version = 1; 
   
  public dbopenhelper(context context) { 
    super(context, dbname, null, version); 
  } 
   
  @override 
  public void oncreate(sqlitedatabase db) { 
    db.execsql("create table if not exists filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid integer, downlength integer)"); 
  } 
 
  @override 
  public void onupgrade(sqlitedatabase db, int oldversion, int newversion) { 
    db.execsql("drop table if exists filedownlog"); 
    oncreate(db); 
  } 
 
} 

fileservice.java

package service; 
 
import java.util.hashmap; 
import java.util.map; 
 
import android.content.context; 
import android.database.cursor; 
import android.database.sqlite.sqlitedatabase; 
/** 
 * 业务bean 
 * 
 */ 
public class fileservice { 
  private dbopenhelper openhelper; 
 
  public fileservice(context context) { 
    openhelper = new dbopenhelper(context); 
  } 
  /** 
   * 获取每条线程已经下载的文件长度 
   * @param path 
   * @return 
   */ 
  public map<integer, integer> getdata(string path){ 
    sqlitedatabase db = openhelper.getreadabledatabase(); 
    cursor cursor = db.rawquery("select threadid, downlength from filedownlog where downpath=?", new string[]{path}); 
    map<integer, integer> data = new hashmap<integer, integer>(); 
    while(cursor.movetonext()){ 
      data.put(cursor.getint(0), cursor.getint(1)); 
    } 
    cursor.close(); 
    db.close(); 
    return data; 
  } 
  /** 
   * 保存每条线程已经下载的文件长度 
   * @param path 
   * @param map 
   */ 
  public void save(string path, map<integer, integer> map){//int threadid, int position 
    sqlitedatabase db = openhelper.getwritabledatabase(); 
    db.begintransaction(); 
    try{ 
      for(map.entry<integer, integer> entry : map.entryset()){ 
        db.execsql("insert into filedownlog(downpath, threadid, downlength) values(?,?,?)", 
            new object[]{path, entry.getkey(), entry.getvalue()}); 
      } 
      db.settransactionsuccessful(); 
    }finally{ 
      db.endtransaction(); 
    } 
    db.close(); 
  } 
  /** 
   * 实时更新每条线程已经下载的文件长度 
   * @param path 
   * @param map 
   */ 
  public void update(string path, map<integer, integer> map){ 
    sqlitedatabase db = openhelper.getwritabledatabase(); 
    db.begintransaction(); 
    try{ 
      for(map.entry<integer, integer> entry : map.entryset()){ 
        db.execsql("update filedownlog set downlength=? where downpath=? and threadid=?", 
            new object[]{entry.getvalue(), path, entry.getkey()}); 
      } 
      db.settransactionsuccessful(); 
    }finally{ 
      db.endtransaction(); 
    } 
    db.close(); 
  } 
  /** 
   * 当文件下载完成后,删除对应的下载记录 
   * @param path 
   */ 
  public void delete(string path){ 
    sqlitedatabase db = openhelper.getwritabledatabase(); 
    db.execsql("delete from filedownlog where downpath=?", new object[]{path}); 
    db.close(); 
  } 
   
} 

filedownloader.java

package net.download; 
 
import java.io.file; 
import java.io.randomaccessfile; 
import java.net.httpurlconnection; 
import java.net.url; 
import java.util.linkedhashmap; 
import java.util.map; 
import java.util.uuid; 
import java.util.concurrent.concurrenthashmap; 
import java.util.regex.matcher; 
import java.util.regex.pattern; 
 
import service.fileservice; 
import android.content.context; 
import android.util.log; 
/** 
 * 文件下载器 
 * filedownloader loader = new filedownloader(context, "http://browse.babasport.com/ejb3/activeport.exe", 
        new file("d:\\androidsoft\\test"), 2); 
    loader.getfilesize();//得到文件总大小 
    try { 
      loader.download(new downloadprogresslistener(){ 
        public void ondownloadsize(int size) { 
          print("已经下载:"+ size); 
        }      
      }); 
    } catch (exception e) { 
      e.printstacktrace(); 
    } 
 */ 
public class filedownloader { 
  private static final string tag = "filedownloader"; 
  private context context; 
  private fileservice fileservice;   
  /* 已下载文件长度 */ 
  private int downloadsize = 0; 
  /* 原始文件长度 */ 
  private int filesize = 0; 
  /* 线程数 */ 
  private downloadthread[] threads; 
  /* 本地保存文件 */ 
  private file savefile; 
  /* 缓存各线程下载的长度*/ 
  private map<integer, integer> data = new concurrenthashmap<integer, integer>(); 
  /* 每条线程下载的长度 */ 
  private int block; 
  /* 下载路径 */ 
  private string downloadurl; 
  /** 
   * 获取线程数 
   */ 
  public int getthreadsize() { 
    return threads.length; 
  } 
  /** 
   * 获取文件大小 
   * @return 
   */ 
  public int getfilesize() { 
    return filesize; 
  } 
  /** 
   * 累计已下载大小 
   * @param size 
   */ 
  protected synchronized void append(int size) { 
    downloadsize += size; 
  } 
  /** 
   * 更新指定线程最后下载的位置 
   * @param threadid 线程id 
   * @param pos 最后下载的位置 
   */ 
  protected synchronized void update(int threadid, int pos) { 
    this.data.put(threadid, pos); 
    this.fileservice.update(this.downloadurl, this.data); 
  } 
  /** 
   * 构建文件下载器 
   * @param downloadurl 下载路径 
   * @param filesavedir 文件保存目录 
   * @param threadnum 下载线程数 
   */ 
  public filedownloader(context context, string downloadurl, file filesavedir, int threadnum) { 
    try { 
      this.context = context; 
      this.downloadurl = downloadurl; 
      fileservice = new fileservice(this.context); 
      url url = new url(this.downloadurl); 
      if(!filesavedir.exists()) filesavedir.mkdirs(); 
      this.threads = new downloadthread[threadnum]; 
      //1.获得文件大小 
      httpurlconnection conn = (httpurlconnection) url.openconnection(); 
      conn.setconnecttimeout(5*1000); 
      conn.setrequestmethod("get"); 
      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", downloadurl);  
      conn.setrequestproperty("charset", "utf-8"); 
      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(); 
      printresponseheader(conn); 
      if (conn.getresponsecode()==200) { 
        this.filesize = conn.getcontentlength();//根据响应获取文件大小 
        if (this.filesize <= 0) throw new runtimeexception("unkown file size "); 
             
        string filename = getfilename(conn);//获取文件名称 
        this.savefile = new file(filesavedir, filename);//构建保存文件 
        map<integer, integer> logdata = fileservice.getdata(downloadurl);//获取下载记录 
         
        //2.如果以前已经下载过,则从数据库中导入记录,并继续下载 
         
        if(logdata.size()>0){//如果存在下载记录 
          for(map.entry<integer, integer> entry : logdata.entryset()) 
            data.put(entry.getkey(), entry.getvalue());//把各条线程已经下载的数据长度放入data中 
        } 
        if(this.data.size()==this.threads.length){//下面计算所有线程已经下载的数据长度 
          for (int i = 0; i < this.threads.length; i++) { 
            this.downloadsize += this.data.get(i+1); 
          } 
          print("已经下载的长度"+ this.downloadsize); 
        } 
        //计算每条线程下载的数据长度 
        this.block = (this.filesize % this.threads.length)==0? this.filesize / this.threads.length : this.filesize / this.threads.length + 1; 
      }else{ 
        throw new runtimeexception("server no response "); 
      } 
    } catch (exception e) { 
      print(e.tostring()); 
      throw new runtimeexception("don't connection this url"); 
    } 
  } 
  /** 
   * 获取文件名 
   */ 
  private string getfilename(httpurlconnection conn) { 
    string filename = this.downloadurl.substring(this.downloadurl.lastindexof('/') + 1); 
    if(filename==null || "".equals(filename.trim())){//如果获取不到文件名称 
      for (int i = 0;; i++) { 
        string mine = conn.getheaderfield(i); 
        if (mine == null) break; 
        if("content-disposition".equals(conn.getheaderfieldkey(i).tolowercase())){ 
          matcher m = pattern.compile(".*filename=(.*)").matcher(mine.tolowercase()); 
          if(m.find()) return m.group(1); 
        } 
      } 
      filename = uuid.randomuuid()+ ".tmp";//默认取一个文件名 
    } 
    return filename; 
  } 
   
  /** 
   * 开始下载文件 
   * @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null 
   * @return 已下载文件大小 
   * @throws exception 
   */ 
  public int download(downloadprogresslistener listener) throws exception{ 
    try { 
      randomaccessfile randout = new randomaccessfile(this.savefile, "rw"); 
      if(this.filesize>0) randout.setlength(this.filesize); 
      randout.close(); 
      url url = new url(this.downloadurl); 
      //如果线程数与以前不一样,则重新开始下 
      if(this.data.size() != this.threads.length){ 
        this.data.clear(); 
        for (int i = 0; i < this.threads.length; i++) { 
          this.data.put(i+1, 0);//初始化每条线程已经下载的数据长度为0 
        } 
      } 
      for (int i = 0; i < this.threads.length; i++) {//开启线程进行下载 
        int downlength = this.data.get(i+1); 
        if(downlength < this.block && this.downloadsize<this.filesize){//判断线程是否已经完成下载,否则继续下载  
          this.threads[i] = new downloadthread(this, url, this.savefile, this.block, this.data.get(i+1), i+1); 
          this.threads[i].setpriority(7); 
          this.threads[i].start(); 
        }else{ 
          this.threads[i] = null; 
        } 
      } 
      this.fileservice.save(this.downloadurl, this.data); 
      boolean notfinish = true;//下载未完成 
      while (notfinish) {// 循环判断所有线程是否完成下载 
        thread.sleep(900); 
        notfinish = false;//假定全部线程下载完成 
        for (int i = 0; i < this.threads.length; i++){ 
          if (this.threads[i] != null && !this.threads[i].isfinish()) {//如果发现线程未完成下载 
            notfinish = true;//设置标志为下载没有完成 
            if(this.threads[i].getdownlength() == -1){//如果下载失败,再重新下载 
              this.threads[i] = new downloadthread(this, url, this.savefile, this.block, this.data.get(i+1), i+1); 
              this.threads[i].setpriority(7); 
              this.threads[i].start(); 
            } 
          } 
        }         
        if(listener!=null) listener.ondownloadsize(this.downloadsize);//通知目前已经下载完成的数据长度 
      } 
      fileservice.delete(this.downloadurl); 
    } catch (exception e) { 
      print(e.tostring()); 
      throw new exception("file download fail"); 
    } 
    return this.downloadsize; 
  } 
  /** 
   * 获取http响应头字段 
   * @param http 
   * @return 
   */ 
  public static map<string, string> gethttpresponseheader(httpurlconnection http) { 
    map<string, string> header = new linkedhashmap<string, string>(); 
    for (int i = 0;; i++) { 
      string mine = http.getheaderfield(i); 
      if (mine == null) break; 
      header.put(http.getheaderfieldkey(i), mine); 
    } 
    return header; 
  } 
  /** 
   * 打印http头字段 
   * @param http 
   */ 
  public static void printresponseheader(httpurlconnection http){ 
    map<string, string> header = gethttpresponseheader(http); 
    for(map.entry<string, string> entry : header.entryset()){ 
      string key = entry.getkey()!=null ? entry.getkey()+ ":" : ""; 
      print(key+ entry.getvalue()); 
    } 
  } 
 
  private static void print(string msg){ 
    log.i(tag, msg); 
  } 
} 

downloadthread.java

package net.download; 
 
import java.io.file; 
import java.io.inputstream; 
import java.io.randomaccessfile; 
import java.net.httpurlconnection; 
import java.net.url; 
 
import android.util.log; 
 
public class downloadthread extends thread { 
  private static final string tag = "downloadthread"; 
  private file savefile; 
  private url downurl; 
  private int block; 
  /* 下载开始位置 */ 
  private int threadid = -1;  
  private int downlength; 
  private boolean finish = false; 
  private filedownloader downloader; 
 
  public downloadthread(filedownloader downloader, url downurl, file savefile, int block, int downlength, int threadid) { 
    this.downurl = downurl; 
    this.savefile = savefile; 
    this.block = block; 
    this.downloader = downloader; 
    this.threadid = threadid; 
    this.downlength = downlength; 
  } 
   
  @override 
  public void run() { 
    if(downlength < block){//未下载完成 
      try { 
        httpurlconnection http = (httpurlconnection) downurl.openconnection(); 
        http.setconnecttimeout(5 * 1000); 
        http.setrequestmethod("get"); 
        http.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, */*"); 
        http.setrequestproperty("accept-language", "zh-cn"); 
        http.setrequestproperty("referer", downurl.tostring());  
        http.setrequestproperty("charset", "utf-8"); 
        int startpos = block * (threadid - 1) + downlength;//开始位置 
        int endpos = block * threadid -1;//结束位置 
        http.setrequestproperty("range", "bytes=" + startpos + "-"+ endpos);//设置获取实体数据的范围 
        http.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)"); 
        http.setrequestproperty("connection", "keep-alive"); 
         
        inputstream instream = http.getinputstream(); 
        byte[] buffer = new byte[1024]; 
        int offset = 0; 
        print("thread " + this.threadid + " start download from position "+ startpos); 
        randomaccessfile threadfile = new randomaccessfile(this.savefile, "rwd"); 
        threadfile.seek(startpos); 
        while ((offset = instream.read(buffer, 0, 1024)) != -1) { 
          threadfile.write(buffer, 0, offset); 
          downlength += offset; 
          downloader.update(this.threadid, downlength); 
          downloader.append(offset); 
        } 
        threadfile.close(); 
        instream.close(); 
        print("thread " + this.threadid + " download finish"); 
        this.finish = true; 
      } catch (exception e) { 
        this.downlength = -1; 
        print("thread "+ this.threadid+ ":"+ e); 
      } 
    } 
  } 
  private static void print(string msg){ 
    log.i(tag, msg); 
  } 
  /** 
   * 下载是否完成 
   * @return 
   */ 
  public boolean isfinish() { 
    return finish; 
  } 
  /** 
   * 已经下载的内容大小 
   * @return 如果返回值为-1,代表下载失败 
   */ 
  public long getdownlength() { 
    return downlength; 
  } 
} 
downloadprogresslistener.java
package net.download; 
 
//下载监听器 
public interface downloadprogresslistener { 
  public void ondownloadsize(int size); 
} 

三、具体代码

效果如下:

实现代码:

package org.xiazdong.download; 
 
import java.io.file; 
 
import net.download.downloadprogresslistener; 
import net.download.filedownloader; 
import android.app.activity; 
import android.os.bundle; 
import android.os.environment; 
import android.os.handler; 
import android.os.message; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.widget.button; 
import android.widget.edittext; 
import android.widget.progressbar; 
import android.widget.textview; 
import android.widget.toast; 
 
public class mainactivity extends activity { 
  private button downloadbutton; 
  private edittext urlpathedittext; 
  private textview percenttextview; 
  private progressbar progressbar; 
  private handler handler; 
  //主线程 
  private class uihandler extends handler{ 
    @override 
    public void handlemessage(message msg) { 
      int downloadsize = msg.getdata().getint("downloadsize"); 
      int percent = msg.getdata().getint("percent"); 
      progressbar.setprogress(downloadsize); 
      percenttextview.settext(percent+"%"); 
    } 
  } 
  private onclicklistener listener = new onclicklistener() { 
    downloadthread thread; 
    class downloadthread extends thread{ 
      private string url ; 
      private file savedir; 
      private filedownloader download; 
      public downloadthread(string url, file savedir) { 
        this.url = url; 
        this.savedir = savedir; 
      } 
      //子线程 
      @override 
      public void run() { 
        download = new filedownloader(mainactivity.this,url, savedir, 3); 
        progressbar.setmax(download.getfilesize()); //设置最大刻度 
        try { 
          download.download(downlistener); 
        } catch (exception e) { 
          e.printstacktrace(); 
        } 
      } 
    } 
    //由子线程调用 
    private downloadprogresslistener downlistener = new downloadprogresslistener() { 
      @override 
      public void ondownloadsize(int size) { 
        //实时跟踪下载的情况 
        int percent = (int)(((double)size)/progressbar.getmax()*100); 
        message msg = new message(); 
        msg.what = 1;  //设置id 
        system.out.println(percent+"%"); 
        system.out.println(size+"k"); 
        msg.getdata().putint("percent", percent); 
        msg.getdata().putint("downloadsize",size); 
        handler.sendmessage(msg); 
         
      } 
    }; 
     
    @override 
    public void onclick(view v) { 
      if(v==downloadbutton){ 
        if(environment.getexternalstoragestate().equals(environment.media_mounted)){ 
          string url = urlpathedittext.gettext().tostring(); 
          file savedir = environment.getexternalstoragedirectory(); 
          download(url,savedir); 
        } 
        else{ 
          toast.maketext(mainactivity.this, "sdcard不存在", toast.length_short).show(); 
        } 
      } 
    } 
 
    private void download(string url, file savedir) { 
      thread = new downloadthread(url,savedir); 
      thread.start(); 
       
    } 
  }; 
  @override 
  public void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.main); 
    downloadbutton = (button)findviewbyid(r.id.download); 
    urlpathedittext = (edittext)findviewbyid(r.id.path); 
    percenttextview = (textview)findviewbyid(r.id.textview); 
    progressbar = (progressbar)findviewbyid(r.id.progressbar);  
    downloadbutton.setonclicklistener(listener); 
    handler = new uihandler(); 
  } 
} 

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网