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, intdownloadBytes, 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。 到此,核心功能展示完畢。附效果圖一張

