Android的HTTP多线程下载示例代码
本示例介绍在android平台下通过http协议实现断点续传下载。
多线程断点需要的功能
1.多线程下载,
2.支持断点。
使用多线程的好处:使用多线程下载会提升文件下载的速度。
多线程下载文件的过程是:
(1)首先获得下载文件的长度,然后设置本地文件的长度。
httpurlconnection.getcontentlength();//获取下载文件的长度 randomaccessfile file = new randomaccessfile("qqwubisetup.exe","rwd"); file.setlength(filesize);//设置本地文件的长度
(2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
如:文件的长度为6m,线程数为3,那么,每条线程下载的数据长度为2m,每条线程开始下载的位置如下图所示。
例如10m大小,使用3个线程来下载,
线程下载的数据长度 (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4m,第三个线程下载长度为2m
下载开始位置:线程id*每条线程下载的数据长度 = ?
下载结束位置:(线程id+1)*每条线程下载的数据长度-1=?
(3)使用http的range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,
如:指定从文件的2m位置开始下载,下载到位置(4m-1byte)为止
httpurlconnection.setrequestproperty("range", "bytes=2097152-4194303");
(4)保存文件,使用randomaccessfile类指定每条线程从本地文件的什么位置开始写入数据。
randomaccessfile threadfile = new randomaccessfile("qqwubisetup.exe ","rwd"); threadfile.seek(2097152);//从文件的什么位置开始写入数据
mainactivity中代码:
package com.android.downloader; import java.io.file; import com.android.network.downloadprogresslistener; import com.android.network.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.widget.button; import android.widget.edittext; import android.widget.progressbar; import android.widget.textview; import android.widget.toast; public class mainactivity extends activity { private edittext downloadpathtext; private textview resultview; private progressbar progressbar; /** * 当handler被创建会关联到创建它的当前线程的消息队列,该类用于往消息队列发送消息 * 消息队列中的消息由当前线程内部进行处理 */ private handler handler = new handler(){ @override public void handlemessage(message msg) { switch (msg.what) { case 1: progressbar.setprogress(msg.getdata().getint("size")); float num = (float)progressbar.getprogress()/(float)progressbar.getmax(); int result = (int)(num*100); resultview.settext(result+ "%"); if(progressbar.getprogress()==progressbar.getmax()){ toast.maketext(mainactivity.this, r.string.success, 1).show(); } break; case -1: toast.maketext(mainactivity.this, r.string.error, 1).show(); break; } } }; /** called when the activity is first created. */ @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); downloadpathtext = (edittext) this.findviewbyid(r.id.path); progressbar = (progressbar) this.findviewbyid(r.id.downloadbar); resultview = (textview) this.findviewbyid(r.id.resultview); button button = (button) this.findviewbyid(r.id.button); button.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { // todo auto-generated method stub string path = downloadpathtext.gettext().tostring(); system.out.println(environment.getexternalstoragestate()+"------"+environment.media_mounted); if(environment.getexternalstoragestate().equals(environment.media_mounted)){ download(path, environment.getexternalstoragedirectory()); }else{ toast.maketext(mainactivity.this, r.string.sdcarderror, 1).show(); } } }); } /** * 主线程(ui线程) * 对于显示控件的界面更新只是由ui线程负责,如果是在非ui线程更新控件的属性值,更新后的显示界面不会反映到屏幕上 * @param path * @param savedir */ private void download(final string path, final file savedir) { new thread(new runnable() { @override public void run() { filedownloader loader = new filedownloader(mainactivity.this, path, savedir, 3); progressbar.setmax(loader.getfilesize());//设置进度条的最大刻度为文件的长度 try { loader.download(new downloadprogresslistener() { @override public void ondownloadsize(int size) {//实时获知文件已经下载的数据长度 message msg = new message(); msg.what = 1; msg.getdata().putint("size", size); handler.sendmessage(msg);//发送消息 } }); } catch (exception e) { handler.obtainmessage(-1).sendtotarget(); } } }).start(); } }
dbopenhelper中代码:
package com.android.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 = "down.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中代码:
package com.android.service; import java.util.hashmap; import java.util.map; import android.content.context; import android.database.cursor; import android.database.sqlite.sqlitedatabase; 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(); } }
downloadprogresslistener中代码:
package com.android.network; public interface downloadprogresslistener { public void ondownloadsize(int size); }
filedownloader中代码:
package com.android.network; 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 com.android.service.fileservice; import android.content.context; import android.util.log; 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]; 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);//获取下载记录 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"); } } /** * 获取文件名 * @param conn * @return */ 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 中代码:
package com.android.network; 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; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 揭秘:历史上杨玉环的死到底应该怪谁?
下一篇: Android登录的简单处理
推荐阅读