最近做项目卡壳了,要做个Android的应用市场,其他方面都还好说,唯独这个下载管理算是给我难住了,究其原因,一是之前没有做过类似的功能,二是这个项目催的着实的急促,以至于都没什么时间能仔细研究这方面的内容,三是我这二把刀的基本功实在是不太扎实啊。不过好在经高人指点,再加上bing以及stackoverflow的帮助,好歹算是有些成果,下面就将这小小的成果分享一下,虽然是使用的AsyncTask来完成,但是个人觉得还是service要更靠谱些,不过那个得等有空儿再研究了。

AsyncTask是何物我就不再赘述了,度娘,谷哥,必应都会告诉你的,不过建议大家看看文章最后参考资料的第二个链接,写的还是非常详细的。我认为它实际上就是个简单的迷你的Handler,反正把一些异步操作扔给它以后,就只需要等着它执行完就齐活了。

那么怎么用这玩意儿实现一个下载管理的功能?大体的思路是这样的:
1.点击下载按钮以后,除了要让AsyncTask开始执行外,还要把下载的任务放到HashMap里面保存,这样做的好处就是能够在列表页进行管理,比如暂停、继续下载、取消。
2.下载管理页的列表,使用ScrollView,而非ListView。这样做的好处就是为了能方便的更新ProgressBar进度。

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

        
  1. btnDownload.setOnClickListener(newOnClickListener(){
  2. publicvoidonClick(Viewv){
  3. Stringurl=datas.get(position).get("url");
  4. AsyncasyncTask=null;// 下载renwu
  5. booleanisHas=false;
  6. //判断当前要下载的这个连接是否已经正在进行,如果正在进行就阻止在此启动一个下载任务
  7. for(StringurlString:AppConstants.listUrl){
  8. if(url.equalsIgnoreCase(urlString)){
  9. isHas=true;
  10. break;
  11. }
  12. }
  13. //如果这个连接的下载任务还没有开始,就创建一个新的下载任务启动下载,并这个下载任务加到下载列表中
  14. if(isHas==false){
  15. asyncTask=newAsync();//创建新异步
  16. asyncTask.setDataMap(datas.get(position));
  17. asyncTask.setContext(context);
  18. AppConstants.mapTask.put(url,asyncTask);
  19. //当调用AsyncTask的方法execute时,就会去自动调用doInBackground方法
  20. asyncTask.executeOnExecutor(Executors.newCachedThreadPool(),url);
  21. }
  22. }
  23. });

这里我为什么写asyncTask.executeOnExecutor(Executors.newCachedThreadPool(), url);而不是asyncTask.execute(url);呢?先卖个关子,回头咱再说。

下面来看看Async里都干了啥。

        
  1. packagecom.test.muldownloadtest.task;
  2. importjava.io.File;
  3. importjava.io.IOException;
  4. importjava.io.InputStream;
  5. importjava.io.RandomAccessFile;
  6. importjava.net.HttpURLConnection;
  7. importjava.net.MalformedURLException;
  8. importjava.net.URL;
  9. importjava.util.HashMap;
  10. importcom.test.muldownloadtest.AppConstants;
  11. importcom.test.muldownloadtest.bean.DBHelper;
  12. importandroid.content.Context;
  13. importandroid.database.Cursor;
  14. importandroid.database.sqlite.SQLiteDatabase;
  15. importandroid.os.AsyncTask;
  16. importandroid.os.Environment;
  17. importandroid.os.Handler;
  18. importandroid.os.Message;
  19. importandroid.widget.ListView;
  20. importandroid.widget.ProgressBar;
  21. importandroid.widget.TextView;
  22. //AsyncTask<Params,Progress,Result>
  23. publicclassAsyncextendsAsyncTask<String,Integer,String>{
  24. /*用于查询数据库*/
  25. privateDBHelperdbHelper;
  26. //下载的文件的map,也可以是实体Bean
  27. privateHashMap<String,String>dataMap=null;
  28. privateContextcontext;
  29. privatebooleanfinished=false;
  30. privatebooleanpaused=false;
  31. privateintcurSize=0;
  32. privateintlength=0;
  33. privateAsyncstartTask=null;
  34. privatebooleanisFirst=true;
  35. privateStringstrUrl;
  36. @Override
  37. protectedStringdoInBackground(String...Params){
  38. dbHelper=newDBHelper(context);
  39. strUrl=Params[0];
  40. Stringname=dataMap.get("name");
  41. Stringappid=dataMap.get("appid");
  42. intstartPosition=0;
  43. URLurl=null;
  44. HttpURLConnectionhttpURLConnection=null;
  45. InputStreaminputStream=null;
  46. RandomAccessFileoutputStream=null;
  47. //文件保存路径
  48. Stringpath=Environment.getExternalStorageDirectory().getPath();
  49. //文件名
  50. StringfileName=strUrl.substring(strUrl.lastIndexOf('/'));
  51. try{
  52. length=getContentLength(strUrl);
  53. startPosition=getDownloadedLength(strUrl,name);
  54. /**判断是否是第一次启动任务,true则保存数据到数据库并下载,
  55. *false则更新数据库中的数据start
  56. */
  57. booleanisHas=false;
  58. for(StringurlString:AppConstants.listUrl){
  59. if(strUrl.equalsIgnoreCase(urlString)){
  60. isHas=true;
  61. break;
  62. }
  63. }
  64. if(false==isHas){
  65. saveDownloading(name,appid,strUrl,path,fileName,startPosition,length,1);
  66. }
  67. elseif(true==isHas){
  68. updateDownloading(curSize,name,strUrl);
  69. }
  70. /**判断是否是第一次启动任务,true则保存数据到数据库并下载,
  71. *false则更新数据库中的数据end
  72. */
  73. //设置断点续传的开始位置
  74. url=newURL(strUrl);
  75. httpURLConnection=(HttpURLConnection)url.openConnection();
  76. httpURLConnection.setAllowUserInteraction(true);
  77. httpURLConnection.setRequestMethod("GET");
  78. httpURLConnection.setReadTimeout(5000);
  79. httpURLConnection.setRequestProperty("User-Agent","NetFox");
  80. httpURLConnection.setRequestProperty("Range","bytes="+startPosition+"-");
  81. inputStream=httpURLConnection.getInputStream();
  82. FileoutFile=newFile(path+fileName);
  83. //使用java中的RandomAccessFile对文件进行随机读写操作
  84. outputStream=newRandomAccessFile(outFile,"rw");
  85. //设置开始写文件的位置
  86. outputStream.seek(startPosition);
  87. byte[]buf=newbyte[1024*100];
  88. intread=0;
  89. curSize=startPosition;
  90. while(false==finished){
  91. while(true==paused){
  92. //暂停下载
  93. Thread.sleep(500);
  94. }
  95. read=inputStream.read(buf);
  96. if(read==-1){
  97. break;
  98. }
  99. outputStream.write(buf,0,read);
  100. curSize=curSize+read;
  101. //当调用这个方法的时候会自动去调用onProgressUpdate方法,传递下载进度
  102. publishProgress((int)(curSize*100.0f/length));
  103. if(curSize==length){
  104. break;
  105. }
  106. Thread.sleep(500);
  107. updateDownloading(curSize,name,strUrl);
  108. }
  109. if(false==finished){
  110. finished=true;
  111. deleteDownloading(strUrl,name);
  112. }
  113. inputStream.close();
  114. outputStream.close();
  115. httpURLConnection.disconnect();
  116. }
  117. catch(MalformedURLExceptione){
  118. e.printStackTrace();
  119. }
  120. catch(IOExceptione){
  121. e.printStackTrace();
  122. }
  123. catch(InterruptedExceptione){
  124. e.printStackTrace();
  125. }
  126. finally{
  127. finished=true;
  128. deleteDownloading(strUrl,name);
  129. if(inputStream!=null){
  130. try{
  131. inputStream.close();
  132. if(outputStream!=null){
  133. outputStream.close();
  134. }
  135. if(httpURLConnection!=null){
  136. httpURLConnection.disconnect();
  137. }
  138. }
  139. catch(IOExceptione){
  140. e.printStackTrace();
  141. }
  142. }
  143. }
  144. //这里的返回值将会被作为onPostExecute方法的传入参数
  145. returnstrUrl;
  146. }
  147. /**
  148. *暂停下载
  149. */
  150. publicvoidpause(){
  151. paused=true;
  152. }
  153. /**
  154. *继续下载
  155. */
  156. publicvoidcontinued(){
  157. paused=false;
  158. }
  159. /**
  160. *停止下载
  161. */
  162. @Override
  163. protectedvoidonCancelled(){
  164. finished=true;
  165. deleteDownloading(dataMap.get("url"),dataMap.get("name"));
  166. super.onCancelled();
  167. }
  168. /**
  169. *当一个下载任务成功下载完成的时候回来调用这个方法,
  170. *这里的result参数就是doInBackground方法的返回值
  171. */
  172. @Override
  173. protectedvoidonPostExecute(Stringresult){
  174. try{
  175. Stringname=dataMap.get("name");
  176. System.out.println("name===="+name);
  177. //判断当前结束的这个任务在任务列表中是否还存在,如果存在就移除
  178. if(AppConstants.mapTask.containsKey(result)){
  179. if(AppConstants.mapTask.get(result)!=null){
  180. finished=true;
  181. deleteDownloading(result,name);
  182. }
  183. }
  184. }
  185. catch(NumberFormatExceptione){
  186. e.printStackTrace();
  187. }
  188. super.onPostExecute(result);
  189. }
  190. @Override
  191. protectedvoidonPreExecute(){
  192. super.onPreExecute();
  193. }
  194. /**
  195. *更新下载进度,当publishProgress方法被调用的时候就会自动来调用这个方法
  196. */
  197. @Override
  198. protectedvoidonProgressUpdate(Integer...values){
  199. super.onProgressUpdate(values);
  200. }
  201. /**
  202. *获取要下载内容的长度
  203. *@paramurlString
  204. *@return
  205. */
  206. privateintgetContentLength(StringurlString){
  207. try{
  208. URLurl=newURL(urlString);
  209. HttpURLConnectionconnection=(HttpURLConnection)url.openConnection();
  210. returnconnection.getContentLength();
  211. }
  212. catch(MalformedURLExceptione){
  213. e.printStackTrace();
  214. }
  215. catch(IOExceptione){
  216. e.printStackTrace();
  217. }
  218. return0;
  219. }
  220. /**
  221. *从数据库获取已经下载的长度
  222. *@paramurl
  223. *@paramname
  224. *@return
  225. */
  226. privateintgetDownloadedLength(Stringurl,Stringname){
  227. intdownloadedLength=0;
  228. SQLiteDatabasedb=dbHelper.getReadableDatabase();
  229. Stringsql="SELECTdownloadBytesFROMfileDownloadingWHEREdownloadUrl=?ANDname=?";
  230. Cursorcursor=db.rawQuery(sql,newString[]{url,name});
  231. while(cursor.moveToNext()){
  232. downloadedLength=cursor.getInt(0);
  233. }
  234. db.close();
  235. returndownloadedLength;
  236. }
  237. /**
  238. *保存下载的数据
  239. *@paramname
  240. *@paramappid
  241. *@paramurl
  242. *@paramdownloadedLength
  243. */
  244. privatevoidsaveDownloading(Stringname,Stringappid,Stringurl,StringsavePath,StringfileName,intdownloadBytes,inttotalBytes,intstatus){
  245. SQLiteDatabasedb=dbHelper.getWritableDatabase();
  246. try{
  247. db.beginTransaction();
  248. Stringsql="INSERTINTOfileDownloading(name,appid,downloadUrl,savePath,fileName,downloadBytes,totalBytes,downloadStatus)"+
  249. "values(?,?,?,?,?,?,?,?)";
  250. db.execSQL(sql,newObject[]{name,appid,url,savePath,fileName,downloadBytes,totalBytes,status});
  251. db.setTransactionSuccessful();
  252. booleanisHas=false;
  253. //判断当前要下载的这个连接是否已经正在进行,如果正在进行就阻止在此启动一个下载任务
  254. for(StringurlString:AppConstants.listUrl){
  255. if(url.equalsIgnoreCase(urlString)){
  256. isHas=true;
  257. break;
  258. }
  259. }
  260. if(false==isHas){
  261. AppConstants.listUrl.add(url);
  262. }
  263. if(false==isFirst){
  264. AppConstants.mapTask.put(url,startTask);
  265. }
  266. }
  267. finally{
  268. db.endTransaction();
  269. db.close();
  270. }
  271. }
  272. /**
  273. *更新下载数据
  274. *@paramcursize
  275. *@paramname
  276. *@paramurl
  277. */
  278. privatevoidupdateDownloading(intcursize,Stringname,Stringurl){
  279. SQLiteDatabasedb=dbHelper.getWritableDatabase();
  280. try{
  281. db.beginTransaction();
  282. Stringsql="UPDATEfileDownloadingSETdownloadBytes=?WHEREname=?ANDdownloadUrl=?";
  283. db.execSQL(sql,newString[]{cursize+"",name,url});
  284. db.setTransactionSuccessful();
  285. }finally{
  286. db.endTransaction();
  287. db.close();
  288. }
  289. }
  290. /**
  291. *删除下载数据
  292. *@paramurl
  293. *@paramname
  294. */
  295. privatevoiddeleteDownloading(Stringurl,Stringname){
  296. if(true==finished){
  297. //删除保存的URL。这个listurl主要是为了在列表中按添加下载任务的顺序进行显示
  298. for(inti=0;i<AppConstants.listUrl.size();i++){
  299. if(url.equalsIgnoreCase(AppConstants.listUrl.get(i))){
  300. AppConstants.listUrl.remove(i);
  301. }
  302. }
  303. //删除已经完成的下载任务
  304. if(AppConstants.mapTask.containsKey(url)){
  305. AppConstants.mapTask.remove(url);
  306. }
  307. }
  308. SQLiteDatabasedb=dbHelper.getWritableDatabase();
  309. Stringsql="DELETEFROMfileDownloadingWHEREdownloadUrl=?ANDname=?";
  310. db.execSQL(sql,newObject[]{url,name});
  311. db.close();
  312. }
  313. publicvoidsetDataMap(HashMap<String,String>dataMap){
  314. this.dataMap=dataMap;
  315. }
  316. publicHashMap<String,String>getDataMap(){
  317. returndataMap;
  318. }
  319. publicbooleanisPaused(){
  320. returnpaused;
  321. }
  322. publicintgetCurSize(){
  323. returncurSize;
  324. }
  325. publicintgetLength(){
  326. returnlength;
  327. }
  328. publicvoidsetContext(Contextcontext){
  329. this.context=context;
  330. }
  331. publicContextgetContext(){
  332. returncontext;
  333. }
  334. publicvoidsetListView(ListViewlistView){
  335. this.listView=listView;
  336. }
  337. }

好了,下载任务已经启动了,接下来就该开始管理了。先说说之前错误的思路,估计大多数的网友可能跟我一样,一想到列表首先想到的就是ListView,这多简单啊,放一个ListView,继承BaseAdapter写个自己的Adapter,然后一展现,完事了,so easy。我也是这么想的,这省事啊,用了以后才发现,确实省事,不过更新ProgressBar的时候可是给我愁死了,无论怎么着都不能正常更新ProgressBar。在这个地方钻了一周的牛角尖,昨儿个突然灵光乍现,干嘛给自己挖个坑,谁说列表就非得用ListView了,我自己写个列表不就得了。

先来看看列表页都有些什么

        
  1. packagecom.test.muldownloadtest;
  2. importandroid.app.Activity;
  3. importandroid.os.Bundle;
  4. importandroid.view.View;
  5. importandroid.view.View.OnClickListener;
  6. importandroid.view.Window;
  7. importandroid.widget.Button;
  8. importandroid.widget.LinearLayout;
  9. importandroid.widget.ScrollView;
  10. publicclassDownloadManagerActivityextendsActivityimplementsOnClickListener{
  11. privateScrollViewscDownload;
  12. privateLinearLayoutllDownloadLayout;
  13. @Override
  14. protectedvoidonCreate(BundlesavedInstanceState){
  15. super.onCreate(savedInstanceState);
  16. this.requestWindowFeature(Window.FEATURE_NO_TITLE);
  17. this.setContentView(R.layout.download_manager_layout);
  18. initView();
  19. }
  20. @Override
  21. protectedvoidonResume(){
  22. super.onResume();
  23. refreshItemView();
  24. }
  25. privatevoidinitView(){
  26. ButtonbtnGoback=(Button)this.findViewById(R.id.btnGoback);
  27. btnGoback.setOnClickListener(this);
  28. scDownload=(ScrollView)this.findViewById(R.id.svDownload);
  29. scDownload.setSmoothScrollingEnabled(true);
  30. llDownloadLayout=(LinearLayout)this.findViewById(R.id.llDownloadLyout);
  31. }
  32. /**
  33. *列表中的每一项
  34. */
  35. privatevoidrefreshItemView(){
  36. for(inti=0;i<AppConstants.listUrl.size();i++){
  37. DownloadItemViewdownloadItemView=newDownloadItemView(this,AppConstants.listUrl.get(i),i);
  38. downloadItemView.setId(i);
  39. downloadItemView.setTag("downloadItemView_"+i);
  40. llDownloadLayout.addView(downloadItemView);
  41. }
  42. }
  43. publicvoidonClick(Viewv){
  44. switch(v.getId()){
  45. caseR.id.btnExit:
  46. this.finish();
  47. break;
  48. caseR.id.btnGoback:
  49. this.finish();
  50. break;
  51. default:
  52. break;
  53. }
  54. }
  55. }

很简单,一个ScrollView,在这个ScrollView中在内嵌一个LinearLayout,用这个LinearLayout来存储每一个列表项。其实列表项很简单,最基本只要三个控件就行了——ProgressBar、TextView、Button。一个是进度条,一个显示百分比,一个用来暂停/继续,偷个懒,这个布局文件就不列出来了,咱就看看这个Button都干嘛了。

        
  1. publicvoidonClick(Viewv){
  2. switch(v.getId()){
  3. caseR.id.btnPauseOrResume:
  4. StringbtnTag=(String)btnPauseOrResume.getTag();
  5. if(btnTag.equals("pause")){
  6. resumeDownload();
  7. }
  8. elseif(btnTag.equals("resume")){
  9. pauseDownload();
  10. }
  11. break;
  12. default:
  13. break;
  14. }
  15. }
  16. privatevoidpauseDownload(){
  17. btnPauseOrResume.setTag("pause");
  18. btnPauseOrResume.setText(R.string.download_resume);
  19. AsyncpauseTask=null;
  20. //判断当前被停止的这个任务在任务列表中是否存在,如果存在就暂停
  21. if(AppConstants.linkedMapDownloading.containsKey(urlString)){
  22. pauseTask=AppConstants.linkedMapDownloading.get(urlString);
  23. if(pauseTask!=null){
  24. pauseTask.pause();
  25. }
  26. }
  27. }
  28. privatevoidresumeDownload(){
  29. btnPauseOrResume.setTag("resume");
  30. btnPauseOrResume.setText(R.string.download_pause);
  31. AsynccontinueTask=null;
  32. //判断当前被停止的这个任务在任务列表中是否存在,如果存在就继续
  33. if(AppConstants.linkedMapDownloading.containsKey(urlString)){
  34. continueTask=AppConstants.linkedMapDownloading.get(urlString);
  35. if(continueTask!=null){
  36. continueTask.continued();
  37. }
  38. }
  39. handler.postDelayed(runnable,1000);
  40. }

简单吧,就是判断一下当前按钮的Tag,然后根据Tag的值,来判断是继续下载,还是暂停下载。而这个暂停还是继续,其实只是修改下Async中的暂停标记的值,即paused是true还是false。

到此,核心功能展示完毕。附效果图一张

Demo下载地址 http://www.eoeandroid.com/forum.php?mod=viewthread&tid=230817&page=1&extra=#pid2067386

参考资料:
1.http://developer.android.com/reference/android/os/AsyncTask.html
2.http://www.myexception.cn/android/725267.html
3.http://blog.csdn.net/shimiso/article/details/6763664
4.http://blog.csdn.net/shimiso/article/details/6763986
5.http://lichen.blog.51cto.com/697816/486868

更多相关文章

  1. android利用httpclient实现post、get请求restful接口进行json和f
  2. Android(安卓)AsyncTask 的使用
  3. Qt for Android编译报错提示:Install to device:No Buildfile:bui
  4. Android(安卓)图片平铺效果bitmap
  5. 热更新-Android与Lua相互通信
  6. android中bitmap压缩的几种方法详解
  7. android vold初始化及sd卡挂载流程
  8. Android(安卓)onTouchEvent 返回值不同时对事件的传递的影响
  9. 【Android(安卓)前沿技术】用MediaPlayer+TextureView封装好的视

随机推荐

  1. Android读写XML(中)——SAX
  2. AirPods怎么连接Android设备 AirPods与安
  3. Android(安卓)入门文档_Android(安卓)4.0
  4. Android项目源码混淆问题解决方法
  5. android的系统优势
  6. Android(安卓)高级面试题及答案,android试
  7. 自定义Android标题栏修改TitleBar的布局
  8. Android开发平台振动器系统详解
  9. Android系统
  10. Intent详解(二)----Intent过滤器