最近做的项目中需要实现断点下载,即用户一次下载可以分多次进行,下载过程可以中断,在目前大多数的带离线缓存的软件都是需要实现这一功能。本文阐述了通过sqlite3简单实现了一个具有断点下载功能的demo。言归正传,开始正文。
设计
数据库表存储元数据
dbhelper.java
用于业务存储的dao
dao.java
抽象下载信息的bean
loadinfo.java
呈现下载信息view
mainactivity.java
存储下载信息bean
downloadinfo.java
封装好的下载类
downloader.java
代码结构
具体实现
下载信息类:downloadinfo.java
这里的代码很简单,就是一个用来保存下载信息的类,很简单,没啥好说的,具体看注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
package entity;
public class downloadinfo {
private int threadid; //线程id
private int startpos; //下载起始位置
private int endpos; //下载结束位置
private int completesize; //下载完成量
private string url; //资源url
public downloadinfo( int tid, int sp, int ep, int csize,string address){
threadid=tid;
startpos=sp;
endpos=ep;
completesize = csize;
url=address;
}
/**
* @return the threadid
*/
public int getthreadid() {
return threadid;
}
/**
* @param threadid the threadid to set
*/
public void setthreadid( int threadid) {
this .threadid = threadid;
}
/**
* @return the startpos
*/
public int getstartpos() {
return startpos;
}
/**
* @param startpos the startpos to set
*/
public void setstartpos( int startpos) {
this .startpos = startpos;
}
/**
* @return the endpos
*/
public int getendpos() {
return endpos;
}
/**
* @param endpos the endpos to set
*/
public void setendpos( int endpos) {
this .endpos = endpos;
}
/**
* @return the completesize
*/
public int getcompletesize() {
return completesize;
}
/**
* @param completesize the completesize to set
*/
public void setcompletesize( int completesize) {
this .completesize = completesize;
}
/**
* @return the url
*/
public string geturl() {
return url;
}
/**
* @param url the url to set
*/
public void seturl(string url) {
this .url = url;
}
@override
public string tostring() {
// todo auto-generated method stub
return "threadid:" +threadid+ ",startpos:" +startpos+ ",endpos:" +endpos+ ",completesize:" +completesize+ ",url:" +url;
}
}
|
数据库 dbhelper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package db;
import android.content.context;
import android.database.sqlite.sqlitedatabase;
import android.database.sqlite.sqliteopenhelper;
public class dbhelper extends sqliteopenhelper {
string sql = "create table download_info (id integer primary key autoincrement,"
+ "thread_id integer,"
+ "start_pos integer,"
+ "end_pos integer,"
+ "complete_size integer,"
+ "url char)" ;
public dbhelper(context context) {
// todo auto-generated constructor stub
super (context, "download.db" , null , 1 );
}
@override
public void oncreate(sqlitedatabase db) {
// todo auto-generated method stub
db.execsql(sql);
}
@override
public void onupgrade(sqlitedatabase db, int oldversion, int newversion) {
// todo auto-generated method stub
}
}
|
数据库业务管理类 dao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
package db;
import java.util.arraylist;
import java.util.list;
import android.content.context;
import android.database.cursor;
import android.database.sqlite.sqlitedatabase;
import entity.downloadinfo;
public class dao {
private dbhelper dbhelper;
public dao(context context){
dbhelper = new dbhelper(context);
}
public boolean isnewtask(string url){
sqlitedatabase db = dbhelper.getreadabledatabase();
string sql = "select count(*) from download_info where url=?" ;
cursor cursor = db.rawquery(sql, new string[]{url});
cursor.movetofirst();
int count = cursor.getint( 0 );
cursor.close();
return count == 0 ;
}
public void saveinfo(list<downloadinfo> infos){
sqlitedatabase db = dbhelper.getwritabledatabase();
string sql = "insert into download_info(thread_id,start_pos,end_pos,complete_size,url) values(?,?,?,?,?)" ;
object[] bindargs= null ;
for (downloadinfo info:infos){
bindargs= new object[]{info.getthreadid(),info.getstartpos(),info.getendpos(),info.getcompletesize(),info.geturl()};
db.execsql(sql, bindargs);
}
}
public list<downloadinfo> getinfo(string url){
sqlitedatabase db = dbhelper.getreadabledatabase();
list<downloadinfo> infos = new arraylist<downloadinfo>();
string sql = "select thread_id,start_pos,end_pos,complete_size,url from download_info where url=?" ;
cursor cursor=db.rawquery(sql, new string[]{url});
while (cursor.movetonext()){
downloadinfo info = new downloadinfo(cursor.getint( 0 ), cursor.getint( 1 ), cursor.getint( 2 ), cursor.getint( 3 ),cursor.getstring( 4 ));
infos.add(info);
}
cursor.close();
return infos;
}
public void deleteinfo(string url){
sqlitedatabase db = dbhelper.getwritabledatabase();
db.delete( "download_info" , "url=?" , new string[]{url});
db.close();
}
public void updateinfo( int completesize, int threadid,string url){
sqlitedatabase db = dbhelper.getwritabledatabase();
string sql = "update download_info set complete_size=? where thread_id=? and url=?" ;
db.execsql(sql, new object[]{completesize,threadid,url});
}
public void closedb(){
dbhelper.close();
}
}
|
当前状态保存类 loadinfo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
package entity;
public class loadinfo {
private int filesize;
private int completesize;
private string url;
public loadinfo( int fs, int csize,string address){
filesize=fs;
completesize = csize;
url=address;
}
/**
* @return the filesize
*/
public int getfilesize() {
return filesize;
}
/**
* @param filesize the filesize to set
*/
public void setfilesize( int filesize) {
this .filesize = filesize;
}
/**
* @return the completesize
*/
public int getcompletesize() {
return completesize;
}
/**
* @param completesize the completesize to set
*/
public void setcompletesize( int completesize) {
this .completesize = completesize;
}
/**
* @return the url
*/
public string geturl() {
return url;
}
/**
* @param url the url to set
*/
public void seturl(string url) {
this .url = url;
}
}
|
下载助手类:downloader.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
package com.winton.downloadmanager;
import java.io.file;
import java.io.ioexception;
import java.io.inputstream;
import java.io.randomaccessfile;
import java.net.httpurlconnection;
import java.net.malformedurlexception;
import java.net.url;
import java.util.arraylist;
import java.util.list;
import android.content.context;
import android.os.handler;
import android.os.message;
import db.dao;
import entity.downloadinfo;
import entity.loadinfo;
public class downloader {
private string url;
private string localpath;
private int threadcount;
private handler mhanler;
private dao dao;
private int filesize;
private list<downloadinfo> infos;
private final static int init = 1 ;
private final static int downloading = 2 ;
private final static int pause = 3 ;
private int state = init;
public downloader(string address,string lpath, int thcount,context context,handler handler){
url =address;
localpath = lpath;
threadcount = thcount;
mhanler = handler;
dao = new dao(context);
}
public boolean isdownloading(){
return state == downloading;
}
public loadinfo getdownloadinfos(){
if (isfirstdownload(url)){
init();
int range = filesize/threadcount;
infos = new arraylist<downloadinfo>();
for ( int i= 0 ;i<=threadcount- 1 ;i++){
downloadinfo info = new downloadinfo(i, i*range, (i+ 1 )*range- 1 , 0 , url);
infos.add(info);
}
dao.saveinfo(infos);
return new loadinfo(filesize, 0 , url);
} else {
infos = dao.getinfo(url);
int size = 0 ;
int completesize = 0 ;
for (downloadinfo info:infos){
completesize += info.getcompletesize();
size += info.getendpos()-info.getstartpos()+ 1 ;
}
return new loadinfo(size, completesize, url);
}
}
public boolean isfirstdownload(string url){
return dao.isnewtask(url);
}
public void init(){
try {
url murl = new url( this .url);
httpurlconnection connection = (httpurlconnection)murl.openconnection();
connection.setconnecttimeout( 5000 );
connection.setrequestmethod( "get" );
filesize = connection.getcontentlength();
file file = new file(localpath);
if (!file.exists()){
file.createnewfile();
}
randomaccessfile accessfile = new randomaccessfile(file, "rwd" );
accessfile.setlength(filesize);
accessfile.close();
connection.disconnect();
} catch (malformedurlexception e) {
// todo auto-generated catch block
e.printstacktrace();
} catch (ioexception e) {
// todo auto-generated catch block
e.printstacktrace();
}
}
public void download(){
if (infos != null ){
if (state ==downloading){
return ;
}
state = downloading;
for (downloadinfo info:infos){
new downloadthread(info.getthreadid(), info.getstartpos(), info.getendpos(), info.getcompletesize(), info.geturl()).start();
}
}
}
class downloadthread extends thread{
private int threadid;
private int startpos;
private int endpos;
private int completesize;
private string url;
public downloadthread( int tid, int sp, int ep, int csize,string address) {
// todo auto-generated constructor stub
threadid=tid;
startpos=sp;
endpos = ep;
completesize = csize;
url = address;
}
@override
public void run() {
// todo auto-generated method stub
httpurlconnection connection = null ;
randomaccessfile randomaccessfile = null ;
inputstream is = null ;
try {
url murl = new url(url);
connection = (httpurlconnection)murl.openconnection();
connection.setconnecttimeout( 5000 );
connection.setrequestmethod( "get" );
connection.setrequestproperty( "range" , "bytes=" +(startpos+completesize)+ "-" +endpos);
randomaccessfile = new randomaccessfile(localpath, "rwd" );
randomaccessfile.seek(startpos+completesize);
is=connection.getinputstream();
byte [] buffer = new byte [ 4096 ];
int length =- 1 ;
while ((length=is.read(buffer)) != - 1 ){
randomaccessfile.write(buffer, 0 , length);
completesize +=length;
dao.updateinfo(threadid, completesize, url);
message msg = message.obtain();
msg.what= 1 ;
msg.obj=url;
msg.arg1=length;
mhanler.sendmessage(msg);
if (state==pause){
return ;
}
}
} catch (malformedurlexception e) {
// todo auto-generated catch block
e.printstacktrace();
} catch (ioexception e) {
// todo auto-generated catch block
e.printstacktrace();
} finally {
try {
is.close();
randomaccessfile.close();
connection.disconnect();
dao.closedb();
} catch (ioexception e) {
// todo auto-generated catch block
e.printstacktrace();
}
}
}
}
public void delete(string url){
dao.deleteinfo(url);
}
public void reset(){
state=init;
}
public void pause(){
state=pause;
}
}
|
view呈现类:mainactivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
package com.winton.downloadmanager;
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.progressbar;
import android.widget.textview;
import android.widget.toast;
import entity.loadinfo;
public class mainactivity extends activity implements onclicklistener{
private textview name;
private progressbar process;
private button start,stop;
private downloader downloader;
//处理下载进度ui的
handler handler = new handler(){
public void handlemessage(android.os.message msg) {
if (msg.what== 1 ){
name.settext((string)msg.obj);
int lenght = msg.arg1;
process.incrementprogressby(lenght);
}
if (msg.what== 2 ){
int max =msg.arg1;
process.setmax(max);
toast.maketext(getapplicationcontext(), max+ "" , 1 ).show();
}
};
};
@override
protected void oncreate(bundle savedinstancestate) {
super .oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
name=(textview)findviewbyid(r.id.tv_name);
process=(progressbar)findviewbyid(r.id.pb_download);
start = (button)findviewbyid(r.id.bt_start);
stop = (button)findviewbyid(r.id.bt_stop);
start.setonclicklistener( this );
stop.setonclicklistener( this );
downloader= new downloader( "http://img4.duitang.com/uploads/item/201206/11/20120611174542_5krmj.jpeg" , environment.getexternalstoragedirectory().getpath()+ "/test1.jpg" , 1 , getapplicationcontext(), handler);
}
@override
public void onclick(view v) {
// todo auto-generated method stub
if (v==start){
new thread( new runnable() {
@override
public void run() {
// todo auto-generated method stub
loadinfo loadinfo = downloader.getdownloadinfos();
message msg =handler.obtainmessage();
msg.what= 2 ;
msg.arg1=loadinfo.getfilesize();
handler.sendmessage(msg);
downloader.download();
}
}).start();
return ;
}
if (v==stop){
downloader.pause();
return ;
}
}
}
|
运行效果
总体比较简单,基本实现了 断点下载的功能,而且开启了多个线程去实现分块下载,对初学的同学有一定的帮助。
这些代码我也是从网上各种搜集而来,自己亲自动手敲了一遍,并做了一些小的改动,对整个断点下载的过程有了一个深刻的认识。因此平时要多敲代码,善于总结,必能有所收获。