来源:http://blog.csdn.net/u011638883/article/details/17347015
实现了一下Android中的文件多线程下载模块,支持自定义线程数、断点续传、下载任务的删除,添加等功能,这里封装了一下,功能已全部实现。不过由于使用的是最简单的手动线程数组及消息通知实现,可能还存在某些小问题。笔者会在后面的使用过程中再进行优化完善。先看一下程序测试效果,这里指定了5个下载任务,以及2个下载线程,具体如下:
要运行以上Demo需要自己搭建服务器,和简单,只需要把所需的文件拷贝到Tomcat中的../webapps/ROOT文件夹下即可,以下是笔者的电脑:
其中的0.mp3,1.mp3....11.mp3及本Demo的测试数据。
除此之外还需要引入Afinal类库,已包含在工程下载中了(下载地址在本文最后)
主要类介绍
主界面MainActivity,负责初始化任务参数,用户可以单击列表中的按钮来删除、暂停、继续任务。可以看到有一个refreshHandler负责根据不同的Message来对列表进行不同的刷新操作。
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
|
package
com.wly.filedownloader;
import
java.util.List;
import
com.wly.filedownloader.dao.BeanHelper;
import
net.tsz.afinal.FinalDb;
import
net.tsz.afinal.exception.AfinalException;
import
android.os.Bundle;
import
android.os.Handler;
import
android.os.Message;
import
android.app.Activity;
import
android.app.AlertDialog;
import
android.content.DialogInterface;
import
android.graphics.Bitmap.Config;
import
android.view.LayoutInflater;
import
android.view.Menu;
import
android.view.View;
import
android.view.View.OnClickListener;
import
android.view.ViewGroup;
import
android.widget.BaseAdapter;
import
android.widget.Button;
import
android.widget.ListAdapter;
import
android.widget.ListView;
import
android.widget.ProgressBar;
import
android.widget.RelativeLayout;
import
android.widget.TextView;
public
class
MainActivity
extends
Activity{
DownloadHelper downHelper;
private
LayoutInflater inflater;
private
ListView lv;
//最大支持同时进行的线程数
private
int
maxThread =
2
;
//测试下载任务数量
private
int
taskSize =
5
;
private
DynamicArray<bean> waitArray;
//等待列表
private
DynamicArray<bean> workArray;
//用来存放已完成及下载中的任务
List<bean> list;
//专门负责刷新界面的Handler
private
Handler refreshHandler =
new
Handler() {
@Override
public
void
handleMessage(Message msg) {
super
.handleMessage(msg);
switch
(msg.what) {
case
Conf.State_FINISH:
myAdapter.notifyDataSetChanged();
break
;
case
Conf.State_CANCEL:
for
(
int
i=
0
;i<workarray.size();i++) bean=
""
beanarray=
"new"
bundle=
""
case
=
""
cell=
""
conf.msg_statechanged:=
""
conf.state_download:=
""
conf.state_fileerror:=
""
firstv=
"lv.getFirstVisiblePosition();"
for
=
""
i=
"firstV;"
int
=
""
lastv=
"lv.getLastVisiblePosition();"
lv=
"(ListView)"
override=
""
protected
=
""
view=
""
void
=
""
workarray=
"new"
>();
waitArray =
new
DynamicArray<bean>();
for
(
int
i=
0
;i<beanarray.length;i++) .mp3=
""
10.0
.
2.2
:
8080
=
""
aa_=
""
bean=
""
http:=
""
i=
""
> list = FinalDb.create(
this
).findAll(Bean.
class
);
if
(list.size() ==
0
) {
for
(
int
i=
0
;i<beanarray.length;i++) baseadapter=
""
bean=
""
button=
""
cancelbtn=
"(Button)convertView.findViewById(R.id.download_btn_cancel);"
convertview=
"inflater.inflate(R.layout.download_list_item,"
czise:=
""
downhelper=
"new"
download_level=
"(RelativeLayout)convertView.findViewById(R.id.download_level);"
downloader=
""
else
=
""
final
=
""
finish_level=
"(RelativeLayout)convertView.findViewById(R.id.finish_level);"
guid=
"bean.getGuid();"
id:=
""
inflater=
"LayoutInflater.from(this);"
int
=
""
isenabled:=
""
isfinished:=
""
list=
"FinalDb.create(this).findAll(Bean.class);"
myadapter=
"new"
override=
""
pausebtn=
"(Button)convertView.findViewById(R.id.download_btn_pause);"
percent:=
""
position=
""
progressbar=
""
protected
=
""
public
=
""
relativelayout=
""
resumebtn=
"(Button)convertView.findViewById(R.id.download_btn_resume);"
string=
""
textview=
""
title=
"(TextView)convertView.findViewById(R.id.download_title);"
title:=
""
url:=
""
view=
""
viewgroup=
""
viewholder=
""
void
=
""
waitcancelbtn=
"(Button)convertView.findViewById(R.id.wait_btn_cancel);"
waiting_level=
"(RelativeLayout)convertView.findViewById(R.id.waiting_level);"
> 暂停/继续
download_level.setVisibility(View.VISIBLE);
waiting_level.setVisibility(View.INVISIBLE);
finish_level.setVisibility(View.INVISIBLE);
if
(downloader.getstate() == Conf.State_DOWNLOAD) {
//下载中
resumeBtn.setVisibility(View.INVISIBLE);
pauseBtn.setVisibility(View.VISIBLE);
}
else
{
//暂停中
resumeBtn.setVisibility(View.VISIBLE);
pauseBtn.setVisibility(View.INVISIBLE);
resumeBtn.setText(继续);
}
}
else
if
(bean.getIsFinished() == Conf.TRUE) {
//对应下载进度100 => 完成
download_level.setVisibility(View.INVISIBLE);
waiting_level.setVisibility(View.INVISIBLE);
finish_level.setVisibility(View.VISIBLE);
}
else
{
//对应下载进度0 => 开始/等待
//检查是否处于等待状态
System.out.println(--开始/等待--);
if
(downloader !=
null
) {
download_level.setVisibility(View.VISIBLE);
waiting_level.setVisibility(View.INVISIBLE);
finish_level.setVisibility(View.INVISIBLE);
resumeBtn.setText(开始);
}
else
{
download_level.setVisibility(View.INVISIBLE);
waiting_level.setVisibility(View.VISIBLE);
finish_level.setVisibility(View.INVISIBLE);
}
}
pauseBtn.setOnClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
downloader.pause();
}
});
resumeBtn.setOnClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
if
(downloader !=
null
&& downloader.isActive()) {
downloader.recovery();
System.out.println(--recovery--);
}
else
{
downloader.start();
System.out.println(--start--);
}
}
});
//下载中取消按钮
cancelBtn.setOnClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
downloader.cancel();
}
});
//等待中取消按钮
waitCancelBtn.setOnClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
if
(position < workArray.size()) {
downloader.cancel();
}
else
{
waitArray.delete(position-workArray.size());
BeanHelper.delete(MainActivity.
this
,bean);
myAdapter.notifyDataSetChanged();
}
}
});
convertView.setId(position);
return
convertView;
}
@Override
public
long
getItemId(
int
position) {
return
0
;
}
@Override
public
Object getItem(
int
position) {
if
(position < workArray.size()) {
return
workArray.getObjectAt(position);
}
else
{
return
waitArray.getObjectAt(position-workArray.size());
}
}
@Override
public
int
getCount() {
return
waitArray.size() + workArray.size();
}
};
}
</beanarray.length;i++)></beanarray.length;i++)></bean></workarray.size();i++)></bean></bean></bean>
|
下载器调度类DownloaderHelper,负责检查任务队列和下载器数组,并将下载任务分配给空闲的下载器。
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
package
com.wly.filedownloader;
import
com.wly.filedownloader.Downloader.MyDownloadListener;
import
android.app.AlertDialog;
import
android.content.Context;
import
android.content.DialogInterface;
import
android.os.Handler;
import
android.os.Message;
/**
* 本类中包含两个队列一个是等待中的实体Bean,另一个是下载中的任务队列, 当添加一个任务实体Bean时,会先检查是否有空余的下载器没有使用,如果有,就
* 使用refreshDownloaderArray将该任务Bean从等待下载队列移动到下载中队列
* 同样的,当一个任务下载成功后,先从下载中队列移除,然后得到一个空闲的下载器,最后调用插入流程,将一个新的任务Bean 添加到到下载中队列中去
* 最后是删除(取消),如果发生在等待队列则删除数据即可,如果发生在下载中队列则复用下载完成逻辑即可。
*
* @author wly
*
*/
public
class
DownloadHelper {
private
DynamicArray<bean> waitArray;
private
DynamicArray<bean> workArray;
private
int
maxThread;
// 最大同时进行任务数量,默认是1
private
Context mContext;
/**
* 下载器队列
*/
private
Downloader[] downladerArray;
private
Handler mHandler;
// Activity中的Handler
private
long
lastRefreshTime =
0
;
private
int
refresh_time =
0
;
// 每次刷新进度的最少间隔时间
// 做一层过滤,控制界面刷新频率
private
Handler handler =
new
Handler() {
@Override
public
void
handleMessage(Message msg) {
super
.handleMessage(msg);
if
(msg.what == Conf.State_FILEERROR) {
System.out.println(--文件异常,发送重置对应下载任务界面显示信息!!!--);
}
else
{
// 每隔refresh_time时间发送一次刷新界面信息
if
((System.currentTimeMillis() - lastRefreshTime) > refresh_time) {
mHandler.sendEmptyMessage(Conf.State_DOWNLOAD);
lastRefreshTime = System.currentTimeMillis();
}
}
}
};
private
MyDownloadListener myDownloadListener =
new
MyDownloadListener() {
@Override
public
void
finished(Bean bean) {
// 1.尝试新的任务给空闲的下载器
refreshDownloaderArray();
// 2.刷新界面
Message msg =
new
Message();
msg.what = Conf.State_FINISH;
mHandler.sendMessage(msg);
System.out.println(--finished);
}
@Override
public
void
started() {
Message msg =
new
Message();
msg.what = Conf.MSG_STATECHANGED;
mHandler.sendMessage(msg);
}
@Override
public
void
paused() {
Message msg =
new
Message();
msg.what = Conf.MSG_STATECHANGED;
mHandler.sendMessage(msg);
}
@Override
public
void
resumed() {
Message msg =
new
Message();
msg.what = Conf.MSG_STATECHANGED;
mHandler.sendMessage(msg);
}
@Override
public
void
canceled(String guid) {
// 发送消息给Activity
Message msg =
new
Message();
msg.what = Conf.State_CANCEL;
msg.obj = guid;
mHandler.sendMessage(msg);
}
/**
* 准备就绪(主要是文件校验过程),可以开始下载了,
*/
@Override
public
void
ready(String guid) {
System.out.println(--准备就绪,可以开始下载了--);
}
};
/**
* 创建实体Bean的队列
*
* @param context
* @param maxThread
* @param beans
* @param handler
*/
public
DownloadHelper(Context context,
int
maxThread,
DynamicArray<bean> dArray, DynamicArray<bean> workArray,
Handler handler) {
this
.maxThread = maxThread;
this
.mContext = context;
this
.waitArray = dArray;
this
.workArray = workArray;
this
.mHandler = handler;
downladerArray =
new
Downloader[maxThread];
}
/**
* 添加任务到队列
*
* @param bean
*/
public
void
insert(Bean bean) {
waitArray.insert(bean);
refreshDownloaderArray();
}
/**
* 初始启动Activity时,为下载器数组中的下载器分配下载任务
*/
public
void
assignTaskToDownloaders() {
int
i =
0
;
while
(i < maxThread && !waitArray.isEmpty()) {
// 为下载器分配下载任务
Bean bean = waitArray.poll();
workArray.insert(bean);
// 不管任务完成与否都插入到工作队列中去
if
(bean.getIsFinished() == Conf.FALSE) {
// 只为未完成的任务分配下载器
downladerArray[i] =
new
Downloader(mContext);
downladerArray[i].assignTask(bean, myDownloadListener, handler);
i++;
}
}
}
/**
* 取消整个队列任务
*/
public
void
kill() {
for
(Downloader d : downladerArray) {
if
(d !=
null
) {
d.kill();
}
}
}
/**
* 检查下载器队列的状态,负责刷新下载器中的任务。通常在新增、更新、删除任务后调用
*/
public
void
refreshDownloaderArray() {
for
(
int
i =
0
; i < maxThread; i++) {
if
(downladerArray[i] !=
null
&& downladerArray[i].isIdle()) {
if
(!waitArray.isEmpty()) {
Bean bean = waitArray.poll();
workArray.insert(bean);
// 为下载器分配下载任务
downladerArray[i].assignTask(bean, myDownloadListener,
handler);
// 开始下载
downladerArray[i].recovery();
}
else
{
downladerArray[i].kill();
}
}
}
}
/**
* 根据guid查询得到其对应的实体bean对象
*
* @param guid
* @return
*/
public
Downloader getDownloaderByGuid(String guid) {
for
(Downloader d : downladerArray) {
if
(d !=
null
&& d.getBean().getGuid().equals(guid)) {
return
d;
}
}
return
null
;
}
// /**
// * 检查网络环境
// */
// public boolean isNetAvailable() {
// return true;
// }
}
</bean></bean></bean></bean>
|
下载器类Downloader,负责下载数据,将下载状态进行持久化。需要特别说明一下的是,这里的下载器在启动后会进入一个下载循环,即使当前任务断开服务器连接,进入暂停状态,本下载器不会停止,只是进入了wait状态。除此之外,下载器中还包含自定义接口MyDownloadListener,用于向Activity反馈当前的下载状态,以更新界面。
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
|
package
com.wly.filedownloader;
import
java.io.File;
import
java.io.FileNotFoundException;
import
java.io.IOException;
import
java.io.InputStream;
import
java.io.RandomAccessFile;
import
java.net.HttpURLConnection;
import
java.net.MalformedURLException;
import
java.net.ProtocolException;
import
java.net.URL;
import
com.wly.filedownloader.dao.BeanHelper;
import
android.app.AlertDialog;
import
android.content.Context;
import
android.content.DialogInterface;
import
android.os.Handler;
import
android.os.Message;
/**
* 下载器
* @author wly
*
*/
public
class
Downloader
extends
Thread {
private
Bean mBean;
private
MyDownloadListener mListener;
//用于表征当前下载器是否处于空闲状态,如果是的话,可以为其分配新的下载任务,进入空闲状态只有两种方法:下载完成和取消下载
private
boolean
isIdle;
private
boolean
isAlive =
false
;
//表示当前下载器是否已经激活,默认未激活
private
Handler handler;
private
Context mContext;
private
int
beanPercent;
private
int
state;
/**
* 创建对象
* @param context
*/
public
Downloader(Context context) {
isIdle =
true
;
this
.mContext = context;
state = Conf.State_WAITTASK;
}
public
boolean
isPause() {
return
(state == Conf.State_PAUSE);
}
public
boolean
isRun() {
return
(state == Conf.State_DOWNLOAD);
}
/**
* 返回当前下载器的激活状态
* @return
*/
public
boolean
isActive() {
return
isAlive;
}
/**
* 等到当前下载器状态
* @return
*/
public
int
getstate() {
return
state;
}
/**
* 分配任务
* @param id
* @param bean
* @param listener
* @param handler
*/
public
void
assignTask(Bean bean,MyDownloadListener listener,Handler handler) {
this
.mBean = bean;
this
.mListener = listener;
this
.handler = handler;
isIdle =
false
;
}
public
Bean getBean() {
return
this
.mBean;
}
/**
* 取消下载任务
*/
public
void
cancel() {
//先暂停当前任务
if
(!isPause()) {
pause();
}
AlertDialog.Builder dialog =
new
AlertDialog.Builder(mContext);
dialog.setTitle(提示)
.setMessage(确定要取消当前选中的任务吗?)
.setPositiveButton(是的,
new
DialogInterface.OnClickListener() {
@Override
public
void
onClick(DialogInterface dialog,
int
which) {
if
(state == Conf.State_WAITTASK || state == Conf.State_PAUSE) {
isIdle =
true
;
BeanHelper.delete(mContext, mBean);
mListener.canceled(mBean.getGuid());
mBean.setPercent(
0
);
state = Conf.State_WAITTASK;
}
else
{
state = Conf.State_CANCELING;
}
}
}).setNegativeButton(取消,
new
DialogInterface.OnClickListener() {
@Override
public
void
onClick(DialogInterface dialog,
int
which) {
recovery();
//恢复下载
}
}).show();
}
/**
* 暂停任务
*/
public
void
pause() {
state = Conf.State_PAUSE;
mListener.paused();
}
public
void
recovery() {
state = Conf.State_DOWNLOAD;
mListener.resumed();
interrupt();
}
public
boolean
isPaused() {
return
state == Conf.State_PAUSE;
}
/**
* 取消当前任务
*/
public
void
kill() {
System.out.println(Kill the downloader with guid: + mBean.getGuid());
state = Conf.State_WAITTASK;
isAlive =
false
;
}
public
boolean
isIdle() {
return
isIdle;
}
@Override
public
void
run() {
synchronized
(
this
){
long
fileSize =
0
;
RandomAccessFile randomAccessFile =
null
;
long
completedSize =
0
;
isAlive =
true
;
isIdle =
false
;
state = Conf.State_DOWNLOAD;
InputStream is =
null
;
HttpURLConnection conn =
null
;
File file =
null
;
mBean.setIsEnabled(Conf.TRUE);
while
(isAlive) {
//如果有可下载任务,就进行下载
if
(state == Conf.State_DOWNLOAD) {
//1.读取当前下载任务信息,如果是断点继续下载,则进行文件连续性判断
beanPercent = mBean.getPercent();
completedSize = mBean.getCompleteSize();
//检查本地文件是否存在
File pathFile =
new
File(Conf.getSaveDir(mContext));
if
(!pathFile.exists()) {
pathFile.mkdirs();
}
file =
new
File(Conf.getSaveDir(mContext)
+ File.separator + mBean.getTitle());
System.out.println(新任务: + mBean.getTitle());
if
(!file.exists()) {
if
(mBean.getCompleteSize() ==
0
) {
//新任务
try
{
file.createNewFile();
}
catch
(IOException e) {
e.printStackTrace();
}
}
else
{
//暂停任务,但是文件不存在,则重置下载任务
mBean =
new
Bean(mBean.getGuid(), mBean.getUrl(), mBean.getTitle(), Conf.State_WAITTASK,
0
);
BeanHelper.update(mContext, mBean);
beanPercent = mBean.getPercent();
completedSize = mBean.getCompleteSize();
Message msg =
new
Message();
msg.what = Conf.State_FILEERROR;
handler.sendMessage(msg);
}
}
//2.与服务器检验文件的统一性
//verifyFileWithServer();
//从当前进度点开始数据下载
URL url;
try
{
url =
new
URL(mBean.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(
true
);
conn.setDoOutput(
true
);
conn.setConnectTimeout(Conf.NET_TIMEOUT_CONNECT);
conn.setReadTimeout(Conf.NET_TIMEOUT_READ);
conn.setRequestMethod(GET);
fileSize = conn.getContentLength();
//得到文件尺寸
try
{
randomAccessFile =
new
RandomAccessFile(file, rwd);
randomAccessFile.setLength(mBean.getCompleteSize());
beanPercent = (
int
)((
double
)(completedSize) / fileSize *
100
);
randomAccessFile.seek((
long
)beanPercent);
}
catch
(FileNotFoundException e) {
e.printStackTrace();
//重置下载任务
}
catch
(IOException e) {
e.printStackTrace();
//重置下载任务
}
//准备就绪
mListener.started();
mBean.setIsEnabled(Conf.TRUE);
mListener.ready(mBean.getGuid());
}
catch
(MalformedURLException e1) {
e1.printStackTrace();
}
catch
(ProtocolException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
//开始下载
try
{
is = conn.getInputStream();
int
length = -
1
;
byte
[] buffer =
new
byte
[
2048
];
while
(state == Conf.State_DOWNLOAD &&
(length=is.read(buffer)) != -
1
) {
randomAccessFile.write(buffer,
0
, length);
completedSize += length;
beanPercent = (
int
)((
double
)(completedSize) / fileSize *
100
);
mBean.setPercent(beanPercent);
Message msg =
new
Message();
handler.sendMessage(msg);
System.out.println(percent: + beanPercent);
//当前任务已完成,进入空闲状态
if
(beanPercent ==
100
) {
System.out.println(--一个下载任务完成--);
mBean.setIsFinished(Conf.TRUE);
mBean.setIsEnabled(Conf.FALSE);
//1.数据持久化动作
mBean.setPercent(beanPercent);
mBean.setCompleteSize(completedSize);
BeanHelper.update(mContext, mBean);
System.out.println(---更新状态完毕---);
//2.下载完毕,发送通知,刷新界面,等待新任务
//告知下载队列,请求新任务
isIdle =
true
;
state = Conf.State_WAITTASK;
mListener.finished(mBean);
beanPercent =
0
;
completedSize =
0
;
// try {
// sleep(1000);
// System.out.println(--等待分配新的任务--);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
break
;
}
}
}
catch
(IOException e1) {
e1.printStackTrace();
}
}
if
(state == Conf.State_PAUSE) {
//1.断开连接
if
(conn !=
null
) {
try
{
is.close();
conn.disconnect();
}
catch
(IOException e) {
e.printStackTrace();
}
System.out.println(---断开连接----);
//2.进行数据持久化
mBean.setIsEnabled(Conf.TRUE);
mBean.setPercent(beanPercent);
mBean.setCompleteSize(completedSize);
BeanHelper.update(mContext, mBean);
System.out.println(---更新状态---);
}
}
if
(state == Conf.State_CANCELING) {
//1.删除数据库数据
BeanHelper.delete(mContext, mBean);
//2.刷新界面
isIdle =
true
;
mListener.canceled(mBean.getGuid());
state = Conf.State_WAITTASK;
}
// try {
// System.out.println(下载器 + mBean.getGuid() + 正处于暂停/空闲状态);
// sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
try
{
wait();
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(This Downloader + mBean.getGuid() + has been killed!!!);
}
}
/**
* 从文件/网路路径中解析出文件名
* @param filePath
* @return
*/
public
String getFileNameFromPath(String filePath) {
String[] ss = filePath.split(
//);
return
ss[ss.length-
1
];
}
/**
* 下载过程的生命周期回调接口,用于刷新界面。注意:界面刷新是在数据更改之后的
* @author wly
*
*/
public
interface
MyDownloadListener {
/**
* 下载完成,发送通知Helper,Helper再通过Handler通知Activity
* @param downloaderID 下载器在下载器数组中的位置索引
*/
public
void
finished(Bean bean);
/**
* 已经开始下载,下载中,本方法的调用在ready()之后
* @param id
*/
public
void
started();
/**
* 已经暂停下载
* @param id
*/
public
void
paused();
/**
* 已经恢复下载
* @param id
*/
public
void
resumed();
/**
* 已经取消下载
* @param guid
*/
public
void
canceled(String guid);
/**
* 已经准备就绪(文件校验,断点读取等),可以开始下载
* @param guid
*/
public
void
ready(String guid);
}
}
|
自定义动态数组类DynamicArray,是下载任务容器,集合了队列和数组的特性。这是由模块的需求决定的,下载任务应该用优先队列(FIFO特性)来做,但是由于下载队列又需要支持用户的删除某个指定任务的功能(数组访问指定索引特性)。
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
|
package
com.wly.filedownloader;
/**
* 动态数组,结合队列(FIFO)和数组(根据索引进行删除元素)的特性
*
* @author wly
*
*/
public
class
DynamicArray<t> {
private
T[] elems;
private
int
mRight;
// 右侧有内容索引值,即队列尾
private
int
mLeft;
// 左侧有内容索引值,即队列首
private
int
INCREATE_STEP =
12
;
// public static void main(String[] args) {
// DynamicArray<student> array = new DynamicArray<student>();
// array.insert(new Student(A));
// array.insert(new Student(B));
// array.insert(new Student(C));
// array.insert(new Student(D));
// array.insert(new Student(E));
// array.insert(new Student(F));
//
// array.poll();
// array.peek();
// array.delete(2);
// array.getObjectAt(2);
// System.out.println(array.size());
//
// }
static
class
Student {
String name;
public
Student(String name) {
this
.name = name;
}
}
public
DynamicArray() {
elems = (T[])
new
Object[INCREATE_STEP];
mLeft =
0
;
mRight =
0
;
}
/**
* 插入一个元素到数组
*
* @param t
*/
public
void
insert(T t) {
// 扩展数组
if
(mRight >= elems.length) {
T[] temp = (T[])
new
Object[elems.length + INCREATE_STEP];
for
(
int
i =
0
; i < elems.length; i++) {
temp[i] = elems[i];
}
elems = temp;
temp =
null
;
}
if
(elems[mRight] ==
null
) {
elems[mRight++] = t;
}
else
{
elems[mRight++] = t;
}
}
public
T peek() {
if
(!isEmpty()) {
return
elems[mLeft];
}
return
null
;
}
/**
* 弹出一个元素,将数组起点到p之间的元素都往右移动一位
*
* @return
*/
public
T poll() {
if
(mLeft == mRight) {
System.out.println(数组为空,无法移除);
return
null
;
}
else
{
T t = elems[mLeft];
elems[mLeft++] =
null
;
return
t;
}
}
/**
* 删除mLeft和mRight之间的元素,从0开始
*
* @param p
*/
public
void
delete(
int
p) {
p = p + mLeft;
if
(p >= mRight) {
System.out.println(无效的索引值,无法进行删除);
}
else
{
for
(
int
i = p; i > mLeft; i--) {
elems[i] = elems[i -
1
];
}
elems[mLeft] =
null
;
}
mLeft++;
}
/**
* 返回数组实际保存的有效个数
*
* @return
*/
public
int
size() {
return
(mRight - mLeft);
}
/**
* 得到mLeft和mRight之间第p个元素,从0开始
*
* @param p
* @return
*/
public
T getObjectAt(
int
p) {
p = p + mLeft;
if
(p >= mRight) {
System.out.println(无效的索引值,无法进行查找);
return
null
;
}
else
{
return
elems[p];
}
}
/**
* 数组是否为空
*
* @return
*/
public
boolean
isEmpty() {
return
(mRight <= mLeft);
}
}
</student></student></t>
|
实体Bean类
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
|
package
com.wly.filedownloader;
import
net.tsz.afinal.annotation.sqlite.Id;
import
net.tsz.afinal.annotation.sqlite.Table;
/**
* 下载任务实体类
* @author wly
*
*/
@Table
(name=Bean)
public
class
Bean {
@Id
private
int
id;
//数据表查询主键
private
String url;
private
String title;
private
int
isEnabled;
//对应percent=(0,100),0表示false,1表示true
private
int
isFinished;
//对应percent=100,0表示false,1表示true
private
int
percent;
//表示当前下载任务的进度,需要持久化
private
long
completeSize;
private
String guid;
//下载实体类唯一性标识id
/**
* 默认构造函数,必须有
*/
public
Bean() {
}
/**
* 新建任务,没有状态和进度,默认isEnabled、isFinished都为false
*/
public
Bean(String url,String title) {
this
.url = url;
this
.title = title;
this
.percent =
0
;
this
.completeSize =
0
;
this
.isEnabled = Conf.FALSE;
this
.isFinished = Conf.FALSE;
}
/**
* 从本地持久化处新建对象,包含状态和进度
* @param url
* @param title
* @param state 0初始状态,1下载中,2暂停中
* @param percent
*/
public
Bean(String guid,String url, String title,
int
state,
int
percent) {
super
();
this
.guid = guid;
this
.url = url;
this
.title = title;
this
.percent = percent;
}
public
String getUrl() {
return
url;
}
public
void
setUrl(String url) {
this
.url = url;
}
public
String getTitle() {
return
title;
}
public
void
setTitle(String title) {
this
.title = title;
}
public
int
getPercent() {
return
percent;
}
public
void
setPercent(
int
percent) {
this
.percent = percent;
}
public
int
getId() {
return
id;
}
public
void
setId(
int
id) {
this
.id = id;
}
public
long
getCompleteSize() {
return
completeSize;
}
public
void
setCompleteSize(
long
completeSize) {
this
.completeSize = completeSize;
}
public
String getGuid() {
return
guid;
}
public
void
setGuid(String guid) {
this
.guid = guid;
}
public
int
getIsEnabled() {
return
isEnabled;
}
public
void
setIsEnabled(
int
isEnabled) {
this
.isEnabled = isEnabled;
}
public
int
getIsFinished() {
return
isFinished;
}
public
void
setIsFinished(
int
isFinished) {
this
.isFinished = isFinished;
}
}
|
最后,配置参数类Conf
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
|
package
com.wly.filedownloader;
import
java.io.File;
import
android.content.Context;
import
android.os.Environment;
/**
* 看出配置类
* @author wly
*
*/
public
class
Conf {
public
final
static
int
NET_TIMEOUT_READ =
5000
;
//读取超时
public
final
static
int
NET_TIMEOUT_CONNECT =
20000
;
//连接超时
public
final
static
int
State_DOWNLOAD =
1
;
//下载中状态
public
final
static
int
State_PAUSE =
2
;
//暂停状态
public
final
static
int
State_FINISH =
3
;
//完成状态
public
final
static
int
State_CANCEL =
4
;
//DownloadBean被取消
public
final
static
int
State_WAITTASK =
5
;
//等待下载任务状态,可能刚下载完一个任务,可能还没还是下载任务
public
final
static
int
State_CANCELING =
7
;
//任务取消中
public
final
static
int
State_FILEERROR =
6
;
//文件异常,文件版本不连续
public
final
static
int
MSG_STATECHANGED =
9
;
//表示列表状态发生概念,通知Adapter刷新界面
public
final
static
int
RETRY_TIMES =
3
;
//网络请求重试次数
public
final
static
int
FALSE =
0
;
public
final
static
int
TRUE =
1
;
/**
* 得到本地文件保存路径
* @return
*/
public
static
String getSaveDir(Context context) {
String fileDir;
if
(android.os.Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED)) {
//判断SD卡是否存在
File f = context.getExternalCacheDir();
if
(
null
== f) {
fileDir = Environment.getExternalStorageDirectory().getPath()
+ File.separator + context.getPackageName()
+ File.separator + cache;
}
else
{
fileDir = f.getPath();
}
}
else
{
File f = context.getCacheDir();
fileDir = f.getPath();
}
return
fileDir;
}
}
|
几个实现过程中需要注意的地方
1、列表的进度反馈,因为列表需要支持拖动的同时刷新进度,但是如果简单的使用BaseAdapter.notifyDataSetChanged()来刷新的话,会出现两个问题: 一、列表拖动过程中有卡顿现象;二、列表上的按钮将变得难以响应单击事件。那么怎么解决这个问题呢?可以这么思考,如果不使用notifyDataSetChanged来刷新整张列表,而是直接取得列表项上的组件的引用,然后直接修改组件属性的话,就不会出现上述两个问题了。
解决:Google了很多,讲的多少使用ListView.getFirstVisiablePosition和getChildAt()配合使用,我也试了下,发现在拖动列表时会出现显示错乱的情况(主要问题是getChildAt得到的View并不是期望的View)。于是只能再自己想办法了,还好咱够聪明,难不倒咱。这里在getView中使用当前的为每个contentView分配一个id:
1
2
3
4
5
6
|
public
View getView(
final
int
position, View convertView, ViewGroup parent) {
.......
.......
convertView.setId(position);
return
convertView;
}
|
然后在需要刷新时根据id得到对应的列表项convertView对象,在取得其中的ProgressBar并为其设置进度即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//刷新可见区域列表进度值
int
firstV = lv.getFirstVisiblePosition();
int
lastV = lv.getLastVisiblePosition();
for
(
int
i = firstV; i <= lastV; i++) {
View cell = lv.findViewById(i);
cell.getId();
if
(cell !=
null
) {
Bean bean = workArray.getObjectAt(i);
if
(bean !=
null
) {
((ProgressBar)cell
.findViewById(R.id.download_progress))
.setProgress(bean.getPercent());
}
}
}
|
2、下载器队列的安排,因为有这样特定的需求,所以需要手动管理等待中队列和下载中队列。
3、下载模块的实现,可能不是很难,不过还是有一些需要注意的地方的。