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进度。

  那咱先来说说启动下载任务。

 

view source print ?
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里都干了啥。

 

view source print ?
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 
027. public class Async extends AsyncTask {
028.  
029. /* 用于查询数据库 */  
030. private DBHelper dbHelper;
031.  
032. // 下载的文件的map,也可以是实体Bean
033. private HashMap 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 dataMap) {
346. this.dataMap = dataMap;
347. }
348.  
349. public HashMap 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了,我自己写个列表不就得了。  先来看看列表页都有些什么

 

view source print ?
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都干嘛了。

 

view source print ?
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。  到此,核心功能展示完毕。附效果图一张
 

Android使用AsyncTask实现可以断点续传的DownloadManager功能_第1张图片

你可能感兴趣的:(Android使用AsyncTask实现可以断点续传的DownloadManager功能)