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

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

程序员文章站 2024-03-01 17:49:40
本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度。也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文...

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

一、多线程断点下载介绍

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

利用的技术有: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); 
} 

三、具体代码

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

实现代码:

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(); 
  } 
} 

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