Android入门:多线程断点下载详细介绍
本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度。也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文件,下载中断后,重新进入应用点击下载,程序检查有没有本地文件的存在,若存在,获取本地文件中的下载进度,继续进行下载。当下载完成后,自动删除本地文件。
一、多线程断点下载介绍
所谓的多线程断点下载就是利用多线程下载,并且可被中断,如果突然没电了,重启手机后可以继续下载,而不需要重新下载;
利用的技术有: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(); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Android编程闹钟设置方法详解