们编写的是andorid的http多线程断点下载应用程序。因为之间我们学习的学习积累,直接使用单线程下载http文件对我们来说是一件非常简单的事。那么,多线程断点下载的难点在哪里?1.多线程下载,2.支持断点。
多线程下载:
如何才能从文件的指定位置处开始下载文件?(比如从50mb开始)这一点我们可以通过http请求信息头来设置,还记得http请求信息头的“range”属性吗?
断点:
首要问题(多线程下载)已经被我们解决了,支持断点下载想必大家也已经想到了。就是将下载的进度保存到文件中,但在android中却不能这么做。通过老黎的试验,在android平台中,我们需要向文件中写出下载的文件数据,还需要向另一个文件中写出下载进度,这样会出错。这样会导致有一个文件的内容没有被写出。所以我们就不能以文件的方式来保存下载进度,但可以通过数据库的方式保存下载进度。
这两大问题我们已经有了解决思路,那么就开始动手编写吧!
1. 创建android工程
project name:multhreaddownloader
buildtarget:android2.1
application name:多线程断点下载
package name:com.changcheng.download
create activity:multhreaddownloader
min sdk version:7
2.androidmanifest.xml
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
package ="com.changcheng.download"
android:versioncode="1"
android:versionname="1.0">
"@drawable /icon" android:label="@string/app_name">
".multhreaddownloader"
android:label="@string/app_name">
"android.intent.action.main" />
"android.intent.category.launcher" />
"7" />
"android.permission.mount_unmount_filesystems"/>
"android.permission.write_external_storage"/>
internet权限 -->
"android.permission.internet"/>
3.strings.xml
"1.0" encoding="utf-8"?>
"hello">hello world, downloadactivity!
"app_name">多线程断点下载
"path">下载路径
"downloadbutton ">下载
"sdcarderror">sdcard不存在或者写保护
4.main.xml
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/path"
/>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="http://www.winrar.com.cn/download/wrar380sc.exe"
android:id="@+id/path"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/downloadbutton "
android:id="@+id/button"
/>
android:layout_width="fill_parent"
android:layout_height="20dip"
style="?android:attr/progressbarstylehorizontal"
android:id="@+id/downloadbar"/>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/resultview"
/>
5.multhreaddownloader
package com.changcheng.download;
import java.io.file;
import com.changcheng.net.download.downloadprogresslistener;
import com.changcheng.net.download.filedownloader;
import com.changcheng.download.r;
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 multhreaddownloader extends activity {
private edittext pathtext;
private progressbar progressbar;
private textview resultview;
private handler handler = new handler(){
@override
public void handlemessage(message msg) {
if (!thread.currentthread().isinterrupted()){
switch (msg.what) {
case 1:
// 获取当前文件下载的进度
int size = msg.getdata().getint("size");
progressbar.setprogress(size);
int result = (int )(((float )size/(float )progressbar.getmax()) * 100);
resultview.settext(result+ "%");
if (progressbar.getmax() == size){
toast.maketext(multhreaddownloader.this , "文件下载完成", 1).show();
}
break ;
case -1:
string error = msg.getdata().getstring("error");
toast.maketext(multhreaddownloader.this , error, 1).show();
break ;
}
}
super .handlemessage(msg);
}
};
@override
public void oncreate(bundle savedinstancestate) {
super .oncreate(savedinstancestate);
setcontentview(r.layout.main);
pathtext = (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) {
string path = pathtext.gettext().tostring();
if (environment.getexternalstoragestate().equals(environment.media_mounted)){
//下载文件需要很长的时间,主线程是不能够长时间被阻塞,如果主线程被长时间阻塞, 那么android被回收应用
download(path, environment.getexternalstoragedirectory());
}else {
toast.maketext(multhreaddownloader.this , r.string.sdcarderror, 1).show();
}
}
});
}
/**
* 下载文件
* @param path 下载路径
* @param savedir 文件保存目录
*/
//对于android的ui控件,只能由主线程负责显示界面的更新,其他线程不能直接更新ui控件的显示
public void download(final string path, final file savedir){
new thread(new runnable() {
@override
public void run() {
filedownloader downer = new filedownloader(multhreaddownloader.this , path, savedir, 3);
progressbar.setmax(downer.getfilesize());//设置进度条的最大刻度
try {
downer.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) {
message msg = new message();
msg.what = -1;
msg.getdata().putstring("error", "下载失败");
handler.sendmessage(msg);
}
}
}).start();
}
}
6.filedownload
package com.changcheng.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 com.changcheng.download.service.fileservice;
import android.content.context;
import android.util.log;
/**
* 文件下载器
* @author lihuoming@sohu.com
*
*/
public class filedownloader {
private context context;
private fileservice fileservice;
private static final string tag = "filedownloader";
/* 已下载文件大小 */
private int downloadsize = 0;
/* 原始文件大小 */
private int filesize = 0;
/* 线程数 */
private downloadthread[] threads;
/* 下载路径 */
private url url;
/* 本地保存文件 */
private file savefile;
/* 下载记录文件 */
private file logfile;
/* 缓存各线程最后下载的位置*/
private map data = new concurrenthashmap();
/* 每条线程下载的大小 */
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 void update(int threadid, int pos) {
this .data.put(threadid, pos);
}
/**
* 保存记录文件
*/
protected synchronized void savelogfile() {
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(context);
this .url = new url(downloadurl);
if (!filesavedir.exists()) filesavedir.mkdirs();
this .threads = new downloadthread[threadnum];
httpurlconnection conn = (httpurlconnection) url.openconnection();
conn.setconnecttimeout(6*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 string filename = getfilename(conn);
this .savefile = new file(filesavedir, filename);/* 保存文件 */
map logdata = fileservice.getdata(downloadurl);
if (logdata.size()>0){
data.putall(logdata);
}
this .block = this .filesize / this .threads.length + 1;
if (this .data.size()==this .threads.length){
for (int i = 0; i this .downloadsize += this .data.get(i+1)-(this .block * i);
}
print("已经下载的长度"+ this .downloadsize);
}
}else {
throw new runtimeexception("服务器响应错误 ");
}
} catch (exception e) {
print(e.tostring());
throw new runtimeexception("连接不到下载路径 ");
}
}
/**
* 获取文件名
*/
private string getfilename(httpurlconnection conn) {
string filename = this .url.tostring().substring(this .url.tostring().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 {
if (this .data.size() != this .threads.length){
this .data.clear();
for (int i = 0; i this .data.put(i+1, this .block * i);
}
}
for (int i = 0; i int downlength = this .data.get(i+1) - (this .block * i);
if (downlength randomaccessfile randout = new randomaccessfile(this .savefile, "rw");
if (this .filesize>0) randout.setlength(this .filesize);
randout.seek(this .data.get(i+1));
this .threads = new downloadthread(this , this .url, randout, this .block, this .data.get(i+1), i+1);
this .threads.setpriority(7);
this .threads.start();
}else {
this .threads = null ;
}
}
this .fileservice.save(this .downloadurl, this .data);
boolean notfinish = true ;//下载未完成
while (notfinish) {// 循环判断是否下载完毕
thread.sleep(900);
notfinish = false ;//假定下载完成
for (int i = 0; i if (this .threads != null && !this .threads.isfinish()) {
notfinish = true ;//下载没有完成
if (this .threads.getdownlength() == -1){//如果下载失败,再重新下载
randomaccessfile randout = new randomaccessfile(this .savefile, "rw");
randout.seek(this .data.get(i+1));
this .threads = new downloadthread(this , this .url, randout, this .block, this .data.get(i+1), i+1);
this .threads.setpriority(7);
this .threads.start();
}
}
}
if (listener!=null ) listener.ondownloadsize(this .downloadsize);
}
fileservice.delete(this .downloadurl);
} catch (exception e) {
print(e.tostring());
throw new exception("下载失败");
}
return this .downloadsize;
}
/**
* 获取http响应头字段
* @param http
* @return
*/
public static map gethttpresponseheader(httpurlconnection http) {
map header = new linkedhashmap();
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 header = gethttpresponseheader(http);
for (map.entry entry : header.entryset()){
string key = entry.getkey()!=null ? entry.getkey()+ ":" : "";
print(key+ entry.getvalue());
}
}
private static void print(string msg){
log.i(tag, msg);
}
}
7.downloadprogresslistener
package com.changcheng.net.download;
public interface downloadprogresslistener {
public void ondownloadsize(int size);
}
8.fileservice
package com.changcheng.download.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 getdata(string path){
sqlitedatabase db = openhelper.getreadabledatabase();
cursor cursor = db.rawquery("select threadid, position from filedown where downpath=?", new string[]{path});
map data = new hashmap();
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 map){//int threadid, int position
sqlitedatabase db = openhelper.getwritabledatabase();
db.begintransaction();
try {
for (map.entry entry : map.entryset()){
db.execsql("insert into filedown(downpath, threadid, position) 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 map){
sqlitedatabase db = openhelper.getwritabledatabase();
db.begintransaction();
try {
for (map.entry entry : map.entryset()){
db.execsql("update filedown set position=? 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 filedown where downpath=?", new object[]{path});
db.close();
}
}
9.downloadthread
package com.changcheng.net.download;
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 randomaccessfile savefile;
private url downurl;
private int block;
/* 下载开始位置 */
private int threadid = -1;
private int startpos;
private int downlength;
private boolean finish = false ;
private filedownloader downloader;
public downloadthread(filedownloader downloader, url downurl, randomaccessfile savefile, int block, int startpos, int threadid) {
this .downurl = downurl;
this .savefile = savefile;
this .block = block;
this .startpos = startpos;
this .downloader = downloader;
this .threadid = threadid;
this .downlength = startpos - (block * (threadid - 1));
}
@override
public void run() {
if (downlength try {
httpurlconnection http = (httpurlconnection) downurl.openconnection();
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");
http.setrequestproperty("range", "bytes=" + this .startpos + "-");
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();
int max = block>1024 ? 1024 : (block>10 ? 10 : 1);
byte [] buffer = new byte [max];
int offset = 0;
print("线程 " + this .threadid + "从位置"+ this .startpos+ "开始下载 ");
while (downlength savefile.write(buffer, 0, offset);
downlength += offset;
downloader.update(this .threadid, block * (threadid - 1) + downlength);
downloader.savelogfile();
downloader.append(offset);
int spare = block-downlength;//求剩下的字节数
if (spare }
savefile.close();
instream.close();
print("线程 " + this .threadid + "完成下载 ");
this .finish = true ;
this .interrupt();
} catch (exception e) {
this .downlength = -1;
print("线程"+ 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;
}
}
11.dbopenhelper
package com.changcheng.download.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 = 2;
public dbopenhelper(context context) {
super (context, dbname, null , version);
}
@override
public void oncreate(sqlitedatabase db) {
db.execsql("create table if not exists filedown (id integer primary key autoincrement, downpath varchar(100), threadid integer, position integer)");
}
@override
public void onupgrade(sqlitedatabase db, int oldversion, int newversion) {
db.execsql("drop table if exists filedown");
oncreate(db);
}
}
结束!
本文转载至: http://blog.csdn.net/shimiso/archive/2010/04/16/5493421.aspx
推荐文章:
android中的intent详细讲解
android中string资源文件的format方法
盛大资深软件工程师谈android开发经验
android 应用程序构成
android api 中文(13) —— togglebutton
如何创建自己的contentprovider
小菜之成长记[java篇]--网络编程合集
android 的系统属性(systemproperties)设置分析
游戏源码-- hexagon (有图)
android 开发环境配置
在自定义按钮上面写字的问题
android 技术专题系列之五 -- 本地化
android apidemos 系列解析【view-imageview/imagebutton】
求助:急急急!在调用系统铃声时出错
一个数据库例子源代码 database_emo
android开发
android编程
android教程
android资料下载
android sdk
android源代码
android教学
android入门
android开发视频
android系统
android开发论坛
棒槌网@Android相关文章推荐:
Android(五)数据存储之五网络多线程断点下载- 世事如棋,乾坤莫测,笑 ...
Android(五)数据存储之五网络数据交互- 世事如棋,乾坤莫测,笑尽英雄 ...
Android(四)数据存储之四网络- 世事如棋,乾坤莫测,笑尽英雄-
Android(三)数据存储之三SQLite嵌入式数据库- 世事如棋,乾坤莫测,笑 ...
Android(四)数据存储之四ContentProvider - 世事如棋,乾坤莫测,笑尽 ...
android多线程断点下载——网络编