Android使用AsyncTask实现可以断点续传的DownloadManager功能
最近做项目卡壳了,要做个Android的应用市场,其他方面都还好说,唯独这个下载管理算是给我难住了,究其原因,一是之前没有做过类似的功能,二是这个项目催的着实的急促,以至于都没什么时间能仔细研究这方面的内容,三是我这二把刀的基本功实在是不太扎实啊。不过好在经高人指点,再加上bing以及stackoverflow的帮助,好歹算是有些成果,下面就将这小小的成果分享一下,虽然是使用的AsyncTask来完成,但是个人觉得还是service要更靠谱些,不过那个得等有空儿再研究了。
AsyncTask是何物我就不再赘述了,度娘,谷哥,必应都会告诉你的,不过建议大家看看文章最后参考资料的第二个链接,写的还是非常详细的。我认为它实际上就是个简单的迷你的Handler,反正把一些异步操作扔给它以后,就只需要等着它执行完就齐活了。
那么怎么用这玩意儿实现一个下载管理的功能?大体的思路是这样的:
1.点击下载按钮以后,除了要让AsyncTask开始执行外,还要把下载的任务放到HashMap里面保存,这样做的好处就是能够在列表页进行管理,比如暂停、继续下载、取消。
2.下载管理页的列表,使用ScrollView,而非ListView。这样做的好处就是为了能方便的更新ProgressBar进度。
那咱先来说说启动下载任务。
- btnDownload.setOnClickListener(newOnClickListener(){
- publicvoidonClick(Viewv){
- Stringurl=datas.get(position).get("url");
- AsyncasyncTask=null;// 下载renwu
- booleanisHas=false;
- //判断当前要下载的这个连接是否已经正在进行,如果正在进行就阻止在此启动一个下载任务
- for(StringurlString:AppConstants.listUrl){
- if(url.equalsIgnoreCase(urlString)){
- isHas=true;
- break;
- }
- }
- //如果这个连接的下载任务还没有开始,就创建一个新的下载任务启动下载,并这个下载任务加到下载列表中
- if(isHas==false){
- asyncTask=newAsync();//创建新异步
- asyncTask.setDataMap(datas.get(position));
- asyncTask.setContext(context);
- AppConstants.mapTask.put(url,asyncTask);
- //当调用AsyncTask的方法execute时,就会去自动调用doInBackground方法
- asyncTask.executeOnExecutor(Executors.newCachedThreadPool(),url);
- }
- }
- });
这里我为什么写asyncTask.executeOnExecutor(Executors.newCachedThreadPool(), url);而不是asyncTask.execute(url);呢?先卖个关子,回头咱再说。
下面来看看Async里都干了啥。
- packagecom.test.muldownloadtest.task;
- importjava.io.File;
- importjava.io.IOException;
- importjava.io.InputStream;
- importjava.io.RandomAccessFile;
- importjava.net.HttpURLConnection;
- importjava.net.MalformedURLException;
- importjava.net.URL;
- importjava.util.HashMap;
- importcom.test.muldownloadtest.AppConstants;
- importcom.test.muldownloadtest.bean.DBHelper;
- importandroid.content.Context;
- importandroid.database.Cursor;
- importandroid.database.sqlite.SQLiteDatabase;
- importandroid.os.AsyncTask;
- importandroid.os.Environment;
- importandroid.os.Handler;
- importandroid.os.Message;
- importandroid.widget.ListView;
- importandroid.widget.ProgressBar;
- importandroid.widget.TextView;
- //AsyncTask<Params,Progress,Result>
- publicclassAsyncextendsAsyncTask<String,Integer,String>{
- /*用于查询数据库*/
- privateDBHelperdbHelper;
- //下载的文件的map,也可以是实体Bean
- privateHashMap<String,String>dataMap=null;
- privateContextcontext;
- privatebooleanfinished=false;
- privatebooleanpaused=false;
- privateintcurSize=0;
- privateintlength=0;
- privateAsyncstartTask=null;
- privatebooleanisFirst=true;
- privateStringstrUrl;
- @Override
- protectedStringdoInBackground(String...Params){
- dbHelper=newDBHelper(context);
- strUrl=Params[0];
- Stringname=dataMap.get("name");
- Stringappid=dataMap.get("appid");
- intstartPosition=0;
- URLurl=null;
- HttpURLConnectionhttpURLConnection=null;
- InputStreaminputStream=null;
- RandomAccessFileoutputStream=null;
- //文件保存路径
- Stringpath=Environment.getExternalStorageDirectory().getPath();
- //文件名
- StringfileName=strUrl.substring(strUrl.lastIndexOf('/'));
- try{
- length=getContentLength(strUrl);
- startPosition=getDownloadedLength(strUrl,name);
- /**判断是否是第一次启动任务,true则保存数据到数据库并下载,
- *false则更新数据库中的数据start
- */
- booleanisHas=false;
- for(StringurlString:AppConstants.listUrl){
- if(strUrl.equalsIgnoreCase(urlString)){
- isHas=true;
- break;
- }
- }
- if(false==isHas){
- saveDownloading(name,appid,strUrl,path,fileName,startPosition,length,1);
- }
- elseif(true==isHas){
- updateDownloading(curSize,name,strUrl);
- }
- /**判断是否是第一次启动任务,true则保存数据到数据库并下载,
- *false则更新数据库中的数据end
- */
- //设置断点续传的开始位置
- url=newURL(strUrl);
- httpURLConnection=(HttpURLConnection)url.openConnection();
- httpURLConnection.setAllowUserInteraction(true);
- httpURLConnection.setRequestMethod("GET");
- httpURLConnection.setReadTimeout(5000);
- httpURLConnection.setRequestProperty("User-Agent","NetFox");
- httpURLConnection.setRequestProperty("Range","bytes="+startPosition+"-");
- inputStream=httpURLConnection.getInputStream();
- FileoutFile=newFile(path+fileName);
- //使用java中的RandomAccessFile对文件进行随机读写操作
- outputStream=newRandomAccessFile(outFile,"rw");
- //设置开始写文件的位置
- outputStream.seek(startPosition);
- byte[]buf=newbyte[1024*100];
- intread=0;
- curSize=startPosition;
- while(false==finished){
- while(true==paused){
- //暂停下载
- Thread.sleep(500);
- }
- read=inputStream.read(buf);
- if(read==-1){
- break;
- }
- outputStream.write(buf,0,read);
- curSize=curSize+read;
- //当调用这个方法的时候会自动去调用onProgressUpdate方法,传递下载进度
- publishProgress((int)(curSize*100.0f/length));
- if(curSize==length){
- break;
- }
- Thread.sleep(500);
- updateDownloading(curSize,name,strUrl);
- }
- if(false==finished){
- finished=true;
- deleteDownloading(strUrl,name);
- }
- inputStream.close();
- outputStream.close();
- httpURLConnection.disconnect();
- }
- catch(MalformedURLExceptione){
- e.printStackTrace();
- }
- catch(IOExceptione){
- e.printStackTrace();
- }
- catch(InterruptedExceptione){
- e.printStackTrace();
- }
- finally{
- finished=true;
- deleteDownloading(strUrl,name);
- if(inputStream!=null){
- try{
- inputStream.close();
- if(outputStream!=null){
- outputStream.close();
- }
- if(httpURLConnection!=null){
- httpURLConnection.disconnect();
- }
- }
- catch(IOExceptione){
- e.printStackTrace();
- }
- }
- }
- //这里的返回值将会被作为onPostExecute方法的传入参数
- returnstrUrl;
- }
- /**
- *暂停下载
- */
- publicvoidpause(){
- paused=true;
- }
- /**
- *继续下载
- */
- publicvoidcontinued(){
- paused=false;
- }
- /**
- *停止下载
- */
- @Override
- protectedvoidonCancelled(){
- finished=true;
- deleteDownloading(dataMap.get("url"),dataMap.get("name"));
- super.onCancelled();
- }
- /**
- *当一个下载任务成功下载完成的时候回来调用这个方法,
- *这里的result参数就是doInBackground方法的返回值
- */
- @Override
- protectedvoidonPostExecute(Stringresult){
- try{
- Stringname=dataMap.get("name");
- System.out.println("name===="+name);
- //判断当前结束的这个任务在任务列表中是否还存在,如果存在就移除
- if(AppConstants.mapTask.containsKey(result)){
- if(AppConstants.mapTask.get(result)!=null){
- finished=true;
- deleteDownloading(result,name);
- }
- }
- }
- catch(NumberFormatExceptione){
- e.printStackTrace();
- }
- super.onPostExecute(result);
- }
- @Override
- protectedvoidonPreExecute(){
- super.onPreExecute();
- }
- /**
- *更新下载进度,当publishProgress方法被调用的时候就会自动来调用这个方法
- */
- @Override
- protectedvoidonProgressUpdate(Integer...values){
- super.onProgressUpdate(values);
- }
- /**
- *获取要下载内容的长度
- *@paramurlString
- *@return
- */
- privateintgetContentLength(StringurlString){
- try{
- URLurl=newURL(urlString);
- HttpURLConnectionconnection=(HttpURLConnection)url.openConnection();
- returnconnection.getContentLength();
- }
- catch(MalformedURLExceptione){
- e.printStackTrace();
- }
- catch(IOExceptione){
- e.printStackTrace();
- }
- return0;
- }
- /**
- *从数据库获取已经下载的长度
- *@paramurl
- *@paramname
- *@return
- */
- privateintgetDownloadedLength(Stringurl,Stringname){
- intdownloadedLength=0;
- SQLiteDatabasedb=dbHelper.getReadableDatabase();
- Stringsql="SELECTdownloadBytesFROMfileDownloadingWHEREdownloadUrl=?ANDname=?";
- Cursorcursor=db.rawQuery(sql,newString[]{url,name});
- while(cursor.moveToNext()){
- downloadedLength=cursor.getInt(0);
- }
- db.close();
- returndownloadedLength;
- }
- /**
- *保存下载的数据
- *@paramname
- *@paramappid
- *@paramurl
- *@paramdownloadedLength
- */
- privatevoidsaveDownloading(Stringname,Stringappid,Stringurl,StringsavePath,StringfileName,intdownloadBytes,inttotalBytes,intstatus){
- SQLiteDatabasedb=dbHelper.getWritableDatabase();
- try{
- db.beginTransaction();
- Stringsql="INSERTINTOfileDownloading(name,appid,downloadUrl,savePath,fileName,downloadBytes,totalBytes,downloadStatus)"+
- "values(?,?,?,?,?,?,?,?)";
- db.execSQL(sql,newObject[]{name,appid,url,savePath,fileName,downloadBytes,totalBytes,status});
- db.setTransactionSuccessful();
- booleanisHas=false;
- //判断当前要下载的这个连接是否已经正在进行,如果正在进行就阻止在此启动一个下载任务
- for(StringurlString:AppConstants.listUrl){
- if(url.equalsIgnoreCase(urlString)){
- isHas=true;
- break;
- }
- }
- if(false==isHas){
- AppConstants.listUrl.add(url);
- }
- if(false==isFirst){
- AppConstants.mapTask.put(url,startTask);
- }
- }
- finally{
- db.endTransaction();
- db.close();
- }
- }
- /**
- *更新下载数据
- *@paramcursize
- *@paramname
- *@paramurl
- */
- privatevoidupdateDownloading(intcursize,Stringname,Stringurl){
- SQLiteDatabasedb=dbHelper.getWritableDatabase();
- try{
- db.beginTransaction();
- Stringsql="UPDATEfileDownloadingSETdownloadBytes=?WHEREname=?ANDdownloadUrl=?";
- db.execSQL(sql,newString[]{cursize+"",name,url});
- db.setTransactionSuccessful();
- }finally{
- db.endTransaction();
- db.close();
- }
- }
- /**
- *删除下载数据
- *@paramurl
- *@paramname
- */
- privatevoiddeleteDownloading(Stringurl,Stringname){
- if(true==finished){
- //删除保存的URL。这个listurl主要是为了在列表中按添加下载任务的顺序进行显示
- for(inti=0;i<AppConstants.listUrl.size();i++){
- if(url.equalsIgnoreCase(AppConstants.listUrl.get(i))){
- AppConstants.listUrl.remove(i);
- }
- }
- //删除已经完成的下载任务
- if(AppConstants.mapTask.containsKey(url)){
- AppConstants.mapTask.remove(url);
- }
- }
- SQLiteDatabasedb=dbHelper.getWritableDatabase();
- Stringsql="DELETEFROMfileDownloadingWHEREdownloadUrl=?ANDname=?";
- db.execSQL(sql,newObject[]{url,name});
- db.close();
- }
- publicvoidsetDataMap(HashMap<String,String>dataMap){
- this.dataMap=dataMap;
- }
- publicHashMap<String,String>getDataMap(){
- returndataMap;
- }
- publicbooleanisPaused(){
- returnpaused;
- }
- publicintgetCurSize(){
- returncurSize;
- }
- publicintgetLength(){
- returnlength;
- }
- publicvoidsetContext(Contextcontext){
- this.context=context;
- }
- publicContextgetContext(){
- returncontext;
- }
- publicvoidsetListView(ListViewlistView){
- this.listView=listView;
- }
- }
好了,下载任务已经启动了,接下来就该开始管理了。先说说之前错误的思路,估计大多数的网友可能跟我一样,一想到列表首先想到的就是ListView,这多简单啊,放一个ListView,继承BaseAdapter写个自己的Adapter,然后一展现,完事了,so easy。我也是这么想的,这省事啊,用了以后才发现,确实省事,不过更新ProgressBar的时候可是给我愁死了,无论怎么着都不能正常更新ProgressBar。在这个地方钻了一周的牛角尖,昨儿个突然灵光乍现,干嘛给自己挖个坑,谁说列表就非得用ListView了,我自己写个列表不就得了。
先来看看列表页都有些什么
- packagecom.test.muldownloadtest;
- importandroid.app.Activity;
- importandroid.os.Bundle;
- importandroid.view.View;
- importandroid.view.View.OnClickListener;
- importandroid.view.Window;
- importandroid.widget.Button;
- importandroid.widget.LinearLayout;
- importandroid.widget.ScrollView;
- publicclassDownloadManagerActivityextendsActivityimplementsOnClickListener{
- privateScrollViewscDownload;
- privateLinearLayoutllDownloadLayout;
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- this.requestWindowFeature(Window.FEATURE_NO_TITLE);
- this.setContentView(R.layout.download_manager_layout);
- initView();
- }
- @Override
- protectedvoidonResume(){
- super.onResume();
- refreshItemView();
- }
- privatevoidinitView(){
- ButtonbtnGoback=(Button)this.findViewById(R.id.btnGoback);
- btnGoback.setOnClickListener(this);
- scDownload=(ScrollView)this.findViewById(R.id.svDownload);
- scDownload.setSmoothScrollingEnabled(true);
- llDownloadLayout=(LinearLayout)this.findViewById(R.id.llDownloadLyout);
- }
- /**
- *列表中的每一项
- */
- privatevoidrefreshItemView(){
- for(inti=0;i<AppConstants.listUrl.size();i++){
- DownloadItemViewdownloadItemView=newDownloadItemView(this,AppConstants.listUrl.get(i),i);
- downloadItemView.setId(i);
- downloadItemView.setTag("downloadItemView_"+i);
- llDownloadLayout.addView(downloadItemView);
- }
- }
- publicvoidonClick(Viewv){
- switch(v.getId()){
- caseR.id.btnExit:
- this.finish();
- break;
- caseR.id.btnGoback:
- this.finish();
- break;
- default:
- break;
- }
- }
- }
很简单,一个ScrollView,在这个ScrollView中在内嵌一个LinearLayout,用这个LinearLayout来存储每一个列表项。其实列表项很简单,最基本只要三个控件就行了——ProgressBar、TextView、Button。一个是进度条,一个显示百分比,一个用来暂停/继续,偷个懒,这个布局文件就不列出来了,咱就看看这个Button都干嘛了。
- publicvoidonClick(Viewv){
- switch(v.getId()){
- caseR.id.btnPauseOrResume:
- StringbtnTag=(String)btnPauseOrResume.getTag();
- if(btnTag.equals("pause")){
- resumeDownload();
- }
- elseif(btnTag.equals("resume")){
- pauseDownload();
- }
- break;
- default:
- break;
- }
- }
- privatevoidpauseDownload(){
- btnPauseOrResume.setTag("pause");
- btnPauseOrResume.setText(R.string.download_resume);
- AsyncpauseTask=null;
- //判断当前被停止的这个任务在任务列表中是否存在,如果存在就暂停
- if(AppConstants.linkedMapDownloading.containsKey(urlString)){
- pauseTask=AppConstants.linkedMapDownloading.get(urlString);
- if(pauseTask!=null){
- pauseTask.pause();
- }
- }
- }
- privatevoidresumeDownload(){
- btnPauseOrResume.setTag("resume");
- btnPauseOrResume.setText(R.string.download_pause);
- AsynccontinueTask=null;
- //判断当前被停止的这个任务在任务列表中是否存在,如果存在就继续
- if(AppConstants.linkedMapDownloading.containsKey(urlString)){
- continueTask=AppConstants.linkedMapDownloading.get(urlString);
- if(continueTask!=null){
- continueTask.continued();
- }
- }
- handler.postDelayed(runnable,1000);
- }
简单吧,就是判断一下当前按钮的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
更多相关文章
- android利用httpclient实现post、get请求restful接口进行json和f
- Android(安卓)AsyncTask 的使用
- Qt for Android编译报错提示:Install to device:No Buildfile:bui
- Android(安卓)图片平铺效果bitmap
- 热更新-Android与Lua相互通信
- android中bitmap压缩的几种方法详解
- android vold初始化及sd卡挂载流程
- Android(安卓)onTouchEvent 返回值不同时对事件的传递的影响
- 【Android(安卓)前沿技术】用MediaPlayer+TextureView封装好的视