Android(五)数据存储之五网络多线程断点下载-世事如棋,乾坤莫测,笑 ...  -  棒槌网@Android开发论坛 - Powered by



                    

                    

                    们编写的是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多线程断点下载——网络编

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值