Android使用AsyncTask實現可以斷點續傳的DownloadManager功能


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

\


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM