http://www.it165.net/pro/html/201211/4210.html
最近做项目卡壳了,要做个Android的应用市场,其他方面都还好说,唯独这个下载管理算是给我难住了,究其原因,一是之前没有做过类似的功能,二是这个项目催的着实的急促,以至于都没什么时间能仔细研究这方面的内容,三是我这二把刀的基本功实在是不太扎实啊。不过好在经高人指点,再加上bing以及stackoverflow的帮助,好歹算是有些成果,下面就将这小小的成果分享一下,虽然是使用的AsyncTask来完成,但是个人觉得还是service要更靠谱些,不过那个得等有空儿再研究了。
AsyncTask是何物我就不再赘述了,度娘,谷哥,必应都会告诉你的,不过建议大家看看文章最后参考资料的第二个链接,写的还是非常详细的。我认为它实际上就是个简单的迷你的Handler,反正把一些异步操作扔给它以后,就只需要等着它执行完就齐活了。
那么怎么用这玩意儿实现一个下载管理的功能?大体的思路是这样的:
1.点击下载按钮以后,除了要让AsyncTask开始执行外,还要把下载的任务放到HashMap里面保存,这样做的好处就是能够在列表页进行管理,比如暂停、继续下载、取消。
2.下载管理页的列表,使用ScrollView,而非ListView。这样做的好处就是为了能方便的更新ProgressBar进度。
那咱先来说说启动下载任务。
01.
btnDownload.setOnClickListener(
new
OnClickListener() {
02.
public
void
onClick(View v) {
03.
String url = datas.get(position).get(
"url"
);
04.
Async asyncTask =
null
;
// 下载renwu
05.
boolean
isHas =
false
;
06.
// 判断当前要下载的这个连接是否已经正在进行,如果正在进行就阻止在此启动一个下载任务
07.
for
(String urlString : AppConstants.listUrl) {
08.
if
(url.equalsIgnoreCase(urlString)) {
09.
isHas =
true
;
10.
break
;
11.
}
12.
}
13.
14.
// 如果这个连接的下载任务还没有开始,就创建一个新的下载任务启动下载,并这个下载任务加到下载列表中
15.
if
(isHas ==
false
) {
16.
asyncTask =
new
Async();
// 创建新异步
17.
asyncTask.setDataMap(datas.get(position));
18.
asyncTask.setContext(context);
19.
AppConstants.mapTask.put(url, asyncTask);
20.
// 当调用AsyncTask的方法execute时,就会去自动调用doInBackground方法
21.
asyncTask.executeOnExecutor(Executors.newCachedThreadPool(), url);
22.
}
23.
}
24.
});
这里我为什么写asyncTask.executeOnExecutor(Executors.newCachedThreadPool(), url);而不是asyncTask.execute(url);呢?先卖个关子,回头咱再说。
下面来看看Async里都干了啥。
001.
package
com.test.muldownloadtest.task;
002.
003.
import
java.io.File;
004.
import
java.io.IOException;
005.
import
java.io.InputStream;
006.
import
java.io.RandomAccessFile;
007.
import
java.net.HttpURLConnection;
008.
import
java.net.MalformedURLException;
009.
import
java.net.URL;
010.
import
java.util.HashMap;
011.
012.
import
com.test.muldownloadtest.AppConstants;
013.
import
com.test.muldownloadtest.bean.DBHelper;
014.
015.
import
android.content.Context;
016.
import
android.database.Cursor;
017.
import
android.database.sqlite.SQLiteDatabase;
018.
import
android.os.AsyncTask;
019.
import
android.os.Environment;
020.
import
android.os.Handler;
021.
import
android.os.Message;
022.
import
android.widget.ListView;
023.
import
android.widget.ProgressBar;
024.
import
android.widget.TextView;
025.
026.
// AsyncTask<Params, Progress, Result>
027.
public
class
Async
extends
AsyncTask<String, Integer, String> {
028.
029.
/* 用于查询数据库 */
030.
private
DBHelper dbHelper;
031.
032.
// 下载的文件的map,也可以是实体Bean
033.
private
HashMap<String, String> dataMap =
null
;
034.
private
Context context;
035.
036.
private
boolean
finished =
false
;
037.
private
boolean
paused =
false
;
038.
039.
private
int
curSize =
0
;
040.
041.
private
int
length =
0
;
042.
043.
private
Async startTask =
null
;
044.
private
boolean
isFirst =
true
;
045.
046.
private
String strUrl;
047.
048.
@Override
049.
protected
String doInBackground=\
‘#\‘
" /span>
050.
051.
dbHelper =
new
DBHelper(context);
052.
053.
strUrl = Params[
0
];
054.
String name = dataMap.get(
"name"
);
055.
String appid = dataMap.get(
"appid"
);
056.
int
startPosition =
0
;
057.
058.
URL url =
null
;
059.
HttpURLConnection httpURLConnection =
null
;
060.
InputStream inputStream =
null
;
061.
RandomAccessFile outputStream =
null
;
062.
// 文件保存路径
063.
String path = Environment.getExternalStorageDirectory().getPath();
064.
// 文件名
065.
String fileName = strUrl.substring(strUrl.lastIndexOf(
‘/‘
));
066.
try
{
067.
length = getContentLength(strUrl);
068.
startPosition = getDownloadedLength(strUrl, name);
069.
070.
/** 判断是否是第一次启动任务,true则保存数据到数据库并下载,
071.
* false则更新数据库中的数据 start
072.
*/
073.
boolean
isHas =
false
;
074.
for
(String urlString : AppConstants.listUrl) {
075.
if
(strUrl.equalsIgnoreCase(urlString)) {
076.
isHas =
true
;
077.
break
;
078.
}
079.
}
080.
if
(
false
== isHas) {
081.
saveDownloading(name, appid, strUrl, path, fileName, startPosition, length,
1
);
082.
}
083.
else
if
(
true
== isHas) {
084.
updateDownloading(curSize, name, strUrl);
085.
}
086.
/** 判断是否是第一次启动任务,true则保存数据到数据库并下载,
087.
* false则更新数据库中的数据 end
088.
*/
089.
090.
// 设置断点续传的开始位置
091.
url =
new
URL=\
‘#\‘
" /span>
092.
httpURLConnection = (HttpURLConnection)url.openConnection();
093.
httpURLConnection.setAllowUserInteraction(
true
);
094.
httpURLConnection.setRequestMethod(
"GET"
);
095.
httpURLConnection.setReadTimeout(
5000
);
096.
httpURLConnection.setRequestProperty(
"User-Agent"
,
"NetFox"
);
097.
httpURLConnection.setRequestProperty(
"Range"
,
"bytes="
+ startPosition +
"-"
);
098.
inputStream = httpURLConnection.getInputStream();
099.
100.
File outFile =
new
File(path+fileName);
101.
// 使用java中的RandomAccessFile 对文件进行随机读写操作
102.
outputStream =
new
RandomAccessFile(outFile,
"rw"
);
103.
// 设置开始写文件的位置
104.
outputStream.seek(startPosition);
105.
106.
byte
[] buf =
new
byte
[
1024
*
100
];
107.
int
read =
0
;
108.
curSize = startPosition;
109.
while
(
false
== finished) {
110.
while
(
true
== paused) {
111.
// 暂停下载
112.
Thread.sleep(
500
);
113.
}
114.
read = inputStream.read(buf);
115.
if
(read==-
1
) {
116.
break
;
117.
}
118.
outputStream.write(buf,
0
,read);
119.
curSize = curSize+read;
120.
// 当调用这个方法的时候会自动去调用onProgressUpdate方法,传递下载进度
121.
publishProgress((
int
)(curSize*
100
.0f/length));
122.
if
(curSize == length) {
123.
break
;
124.
}
125.
Thread.sleep(
500
);
126.
updateDownloading(curSize, name, strUrl);
127.
}
128.
if
(
false
== finished) {
129.
finished =
true
;
130.
deleteDownloading(strUrl, name);
131.
}
132.
inputStream.close();
133.
outputStream.close();
134.
httpURLConnection.disconnect();
135.
}
136.
catch
(MalformedURLException e) {
137.
e.printStackTrace();
138.
}
139.
catch
(IOException e) {
140.
e.printStackTrace();
141.
}
142.
catch
(InterruptedException e) {
143.
e.printStackTrace();
144.
}
145.
finally
{
146.
finished =
true
;
147.
deleteDownloading(strUrl, name);
148.
if
(inputStream!=
null
) {
149.
try
{
150.
inputStream.close();
151.
if
(outputStream!=
null
) {
152.
outputStream.close();
153.
}
154.
if
(httpURLConnection!=
null
) {
155.
httpURLConnection.disconnect();
156.
}
157.
}
158.
catch
(IOException e) {
159.
e.printStackTrace();
160.
}
161.
}
162.
}
163.
// 这里的返回值将会被作为onPostExecute方法的传入参数
164.
return
strUrl;
165.
}
166.
167.
/**
168.
* 暂停下载
169.
*/
170.
public
void
pause() {
171.
paused =
true
;
172.
}
173.
174.
/**
175.
* 继续下载
176.
*/
177.
public
void
continued() {
178.
paused =
false
;
179.
}
180.
181.
/**
182.
* 停止下载
183.
*/
184.
@Override
185.
protected
void
onCancelled() {
186.
finished =
true
;
187.
deleteDownloading(dataMap.get(
"url"
), dataMap.get(
"name"
));
188.
super
.onCancelled();
189.
}
190.
191.
/**
192.
* 当一个下载任务成功下载完成的时候回来调用这个方法,
193.
* 这里的result参数就是doInBackground方法的返回值
194.
*/
195.
@Override
196.
protected
void
onPostExecute(String result) {
197.
try
{
198.
String name = dataMap.get(
"name"
);
199.
System.out.println(
"name===="
+name);
200.
// 判断当前结束的这个任务在任务列表中是否还存在,如果存在就移除
201.
if
(AppConstants.mapTask.containsKey(result)) {
202.
if
(AppConstants.mapTask.get(result) !=
null
) {
203.
finished =
true
;
204.
deleteDownloading(result, name);
205.
}
206.
}
207.
}
208.
catch
(NumberFormatException e) {
209.
e.printStackTrace();
210.
}
211.
super
.onPostExecute(result);
212.
}
213.
214.
@Override
215.
protected
void
onPreExecute() {
216.
super
.onPreExecute();
217.
}
218.
219.
/**
220.
* 更新下载进度,当publishProgress方法被调用的时候就会自动来调用这个方法
221.
*/
222.
@Override
223.
protected
void
onProgressUpdate(Integer... values) {
224.
super
.onProgressUpdate(values);
225.
}
226.
227.
228.
/**
229.
* 获取要下载内容的长度
230.
* @param urlString
231.
* @return
232.
*/
233.
private
int
getContentLength(String urlString){
234.
try
{
235.
URL url =
new
URL(urlString);
236.
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
237.
return
connection.getContentLength();
238.
}
239.
catch
(MalformedURLException e) {
240.
e.printStackTrace();
241.
}
242.
catch
(IOException e) {
243.
e.printStackTrace();
244.
}
245.
return
0
;
246.
}
247.
248.
/**
249.
* 从数据库获取已经下载的长度
250.
* @param url
251.
* @param name www.it165.net
252.
* @return
253.
*/
254.
private
int
getDownloadedLength(String url, String name) {
255.
int
downloadedLength =
0
;
256.
SQLiteDatabase db = dbHelper.getReadableDatabase();
257.
String sql =
"SELECT downloadBytes FROM fileDownloading WHERE downloadUrl=? AND name=?"
;
258.
Cursor cursor = db.rawQuery(sql,
new
String[] { url, name });
259.
while
(cursor.moveToNext()) {
260.
downloadedLength = cursor.getInt(
0
);
261.
}
262.
db.close();
263.
return
downloadedLength;
264.
}
265.
266.
/**
267.
* 保存下载的数据
268.
* @param name
269.
* @param appid
270.
* @param url
271.
* @param downloadedLength
272.
*/
273.
private
void
saveDownloading(String name, String appid, String url, String savePath, String fileName,
int
downloadBytes,
int
totalBytes,
int
status) {
274.
SQLiteDatabase db = dbHelper.getWritableDatabase();
275.
try
{
276.
db.beginTransaction();
277.
String sql =
"INSERT INTO fileDownloading(name, appid, downloadUrl, savePath, fileName, downloadBytes, totalBytes, downloadStatus) "
+
278.
"values(?,?,?,?,?,?,?,?)"
;
279.
db.execSQL(sql,
new
Object[]{ name, appid, url, savePath, fileName, downloadBytes, totalBytes, status});
280.
db.setTransactionSuccessful();
281.
boolean
isHas =
false
;
282.
// 判断当前要下载的这个连接是否已经正在进行,如果正在进行就阻止在此启动一个下载任务
283.
for
(String urlString : AppConstants.listUrl) {
284.
if
(url.equalsIgnoreCase(urlString)) {
285.
isHas =
true
;
286.
break
;
287.
}
288.
}
289.
if
(
false
== isHas) {
290.
AppConstants.listUrl.add(url);
291.
}
292.
if
(
false
== isFirst) {
293.
AppConstants.mapTask.put(url, startTask);
294.
}
295.
}
296.
finally
{
297.
db.endTransaction();
298.
db.close();
299.
}
300.
}
301.
302.
/**
303.
* 更新下载数据
304.
* @param cursize
305.
* @param name
306.
* @param url
307.
*/
308.
private
void
updateDownloading(
int
cursize, String name, String url) {
309.
SQLiteDatabase db = dbHelper.getWritableDatabase();
310.
try
{
311.
db.beginTransaction();
312.
String sql =
"UPDATE fileDownloading SET downloadBytes=? WHERE name=? AND downloadUrl=?"
;
313.
db.execSQL(sql,
new
String[] { cursize +
""
, name, url });
314.
db.setTransactionSuccessful();
315.
}
finally
{
316.
db.endTransaction();
317.
db.close();
318.
}
319.
}
320.
321.
/**
322.
* 删除下载数据
323.
* @param url
324.
* @param name
325.
*/
326.
private
void
deleteDownloading(String url, String name) {
327.
if
(
true
== finished) {
328.
// 删除保存的URL。这个listurl主要是为了在列表中按添加下载任务的顺序进行显示
329.
for
(
int
i =
0
; i < AppConstants.listUrl.size(); i++) {
330.
if
(url.equalsIgnoreCase(AppConstants.listUrl.get(i))) {
331.
AppConstants.listUrl.remove(i);
332.
}
333.
}
334.
// 删除已经完成的下载任务
335.
if
(AppConstants.mapTask.containsKey(url)) {
336.
AppConstants.mapTask.remove(url);
337.
}
338.
}
339.
SQLiteDatabase db = dbHelper.getWritableDatabase();
340.
String sql =
"DELETE FROM fileDownloading WHERE downloadUrl=? AND name=?"
;
341.
db.execSQL(sql,
new
Object[] { url, name });
342.
db.close();
343.
}
344.
345.
public
void
setDataMap(HashMap<String, String> dataMap) {
346.
this
.dataMap = dataMap;
347.
}
348.
349.
public
HashMap<String, String> getDataMap() {
350.
return
dataMap;
351.
}
352.
353.
public
boolean
isPaused() {
354.
return
paused;
355.
}
356.
357.
public
int
getCurSize() {
358.
return
curSize;
359.
}
360.
361.
public
int
getLength() {
362.
return
length;
363.
}
364.
365.
public
void
setContext(Context context) {
366.
this
.context = context;
367.
}
368.
369.
public
Context getContext() {
370.
return
context;
371.
}
372.
373.
public
void
setListView(ListView listView) {
374.
this
.listView = listView;
375.
}
376.
}
好了,下载任务已经启动了,接下来就该开始管理了。先说说之前错误的思路,估计大多数的网友可能跟我一样,一想到列表首先想到的就是ListView,这多简单啊,放一个ListView,继承BaseAdapter写个自己的Adapter,然后一展现,完事了,so easy。我也是这么想的,这省事啊,用了以后才发现,确实省事,不过更新ProgressBar的时候可是给我愁死了,无论怎么着都不能正常更新ProgressBar。在这个地方钻了一周的牛角尖,昨儿个突然灵光乍现,干嘛给自己挖个坑,谁说列表就非得用ListView了,我自己写个列表不就得了。 先来看看列表页都有些什么
01.
package
com.test.muldownloadtest;
02.
03.
import
android.app.Activity;
04.
import
android.os.Bundle;
05.
import
android.view.View;
06.
import
android.view.View.OnClickListener;
07.
import
android.view.Window;
08.
import
android.widget.Button;
09.
import
android.widget.LinearLayout;
10.
import
android.widget.ScrollView;
11.
12.
public
class
DownloadManagerActivity
extends
Activity
implements
OnClickListener {
13.
14.
private
ScrollView scDownload;
15.
private
LinearLayout llDownloadLayout;
16.
17.
@Override
18.
protected
void
onCreate(Bundle savedInstanceState) {
19.
super
.onCreate(savedInstanceState);
20.
21.
this
.requestWindowFeature(Window.FEATURE_NO_TITLE);
22.
23.
this
.setContentView(R.layout.download_manager_layout);
24.
25.
initView();
26.
}
27.
28.
@Override
29.
protected
void
onResume() {
30.
super
.onResume();
31.
32.
refreshItemView();
33.
}
34.
35.
private
void
initView(){
36.
37.
Button btnGoback = (Button)
this
.findViewById(R.id.btnGoback);
38.
btnGoback.setOnClickListener(
this
);
39.
40.
scDownload = (ScrollView)
this
.findViewById(R.id.svDownload);
41.
scDownload.setSmoothScrollingEnabled(
true
);
42.
43.
llDownloadLayout = (LinearLayout)
this
.findViewById(R.id.llDownloadLyout);
44.
}
45.
46.
/**
47.
* 列表中的每一项
48.
*/
49.
private
void
refreshItemView(){
50.
for
(
int
i =
0
; i < AppConstants.listUrl.size(); i++) {
51.
DownloadItemView downloadItemView =
new
DownloadItemView(
this
, AppConstants.listUrl.get(i), i);
52.
downloadItemView.setId(i);
53.
downloadItemView.setTag(
"downloadItemView_"
+i);
54.
llDownloadLayout.addView(downloadItemView);
55.
}
56.
}
57.
58.
public
void
onClick(View v) {
59.
switch
(v.getId()) {
60.
case
R.id.btnExit:
61.
this
.finish();
62.
break
;
63.
case
R.id.btnGoback:
64.
this
.finish();
65.
break
;
66.
default
:
67.
break
;
68.
}
69.
}
70.
}
很简单,一个ScrollView,在这个ScrollView中在内嵌一个LinearLayout,用这个LinearLayout来存储每一个列表项。其实列表项很简单,最基本只要三个控件就行了——ProgressBar、TextView、Button。一个是进度条,一个显示百分比,一个用来暂停/继续,偷个懒,这个布局文件就不列出来了,咱就看看这个Button都干嘛了。
01.
public
void
onClick(View v) {
02.
switch
(v.getId()) {
03.
case
R.id.btnPauseOrResume:
04.
String btnTag = (String) btnPauseOrResume.getTag();
05.
if
(btnTag.equals(
"pause"
)) {
06.
resumeDownload();
07.
}
08.
else
if
(btnTag.equals(
"resume"
)) {
09.
pauseDownload();
10.
}
11.
break
;
12.
default
:
13.
break
;
14.
}
15.
}
16.
17.
private
void
pauseDownload(){
18.
btnPauseOrResume.setTag(
"pause"
);
19.
btnPauseOrResume.setText(R.string.download_resume);
20.
21.
Async pauseTask =
null
;
22.
// 判断当前被停止的这个任务在任务列表中是否存在,如果存在就暂停
23.
if
(AppConstants.linkedMapDownloading.containsKey(urlString)) {
24.
pauseTask = AppConstants.linkedMapDownloading.get(urlString);
25.
if
(pauseTask !=
null
) {
26.
pauseTask.pause();
27.
}
28.
}
29.
}
30.
31.
private
void
resumeDownload(){
32.
btnPauseOrResume.setTag(
"resume"
);
33.
btnPauseOrResume.setText(R.string.download_pause);
34.
35.
Async continueTask =
null
;
36.
// 判断当前被停止的这个任务在任务列表中是否存在,如果存在就继续
37.
if
(AppConstants.linkedMapDownloading.containsKey(urlString)) {
38.
continueTask = AppConstants.linkedMapDownloading.get(urlString);
39.
if
(continueTask !=
null
) {
40.
continueTask.continued();
41.
}
42.
}
43.
handler.postDelayed(runnable,
1000
);
44.
}
简单吧,就是判断一下当前按钮的Tag,然后根据Tag的值,来判断是继续下载,还是暂停下载。而这个暂停还是继续,其实只是修改下Async中的暂停标记的值,即paused是true还是false。 到此,核心功能展示完毕。附效果图一张