最近作項目卡殼了,要作個Android的應用市場,其餘方面都還好說,惟獨這個下載管理算是給我難住了,究其緣由,一是以前沒有作過相似的功能,二是這個項目催的着實的急促,以致於都沒什麼時間能仔細研究這方面的內容,三是我這二把刀的基本功實在是不太紮實啊。不過好在經高人指點,再加上bing以及stackoverflow的幫助,好歹算是有些成果,下面就將這小小的成果分享一下,雖然是使用的AsyncTask來完成,可是我的以爲仍是service要更靠譜些,不過那個得等有空兒再研究了。php
AsyncTask是何物我就再也不贅述了,度娘,谷哥,必應都會告訴你的,不過建議你們看看文章最後參考資料的第二個連接,寫的仍是很是詳細的。我認爲它實際上就是個簡單的迷你的Handler,反正把一些異步操做扔給它之後,就只須要等着它執行完就齊活了。html
那麼怎麼用這玩意兒實現一個下載管理的功能?大致的思路是這樣的:
1.點擊下載按鈕之後,除了要讓AsyncTask開始執行外,還要把下載的任務放到HashMap裏面保存,這樣作的好處就是可以在列表頁進行管理,好比暫停、繼續下載、取消。
2.下載管理頁的列表,使用ScrollView,而非ListView。這樣作的好處就是爲了能方便的更新ProgressBar進度。java
那咱先來講說啓動下載任務。 android
- btnDownload.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- String url = datas.get(position).get("url");
- Async asyncTask = null; // 下載renwu
- boolean isHas = false;
- // 判斷當前要下載的這個鏈接是否已經正在進行,若是正在進行就阻止在此啓動一個下載任務
- for (String urlString : AppConstants.listUrl) {
- if (url.equalsIgnoreCase(urlString)) {
- isHas = true;
- break;
- }
- }
- // 若是這個鏈接的下載任務尚未開始,就建立一個新的下載任務啓動下載,並這個下載任務加到下載列表中
- if(isHas == false) {
- asyncTask = new Async(); // 建立新異步
- 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);呢?先賣個關子,回頭咱再說。sql
下面來看看Async裏都幹了啥。數據庫
- package com.test.muldownloadtest.task;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.RandomAccessFile;
- import java.net.HttpURLConnection;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.util.HashMap;
- import com.test.muldownloadtest.AppConstants;
- import com.test.muldownloadtest.bean.DBHelper;
- import android.content.Context;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteDatabase;
- import android.os.AsyncTask;
- import android.os.Environment;
- import android.os.Handler;
- import android.os.Message;
- import android.widget.ListView;
- import android.widget.ProgressBar;
- import android.widget.TextView;
- // AsyncTask<Params, Progress, Result>
- public class Async extends AsyncTask<String, Integer, String> {
- /* 用於查詢數據庫 */
- private DBHelper dbHelper;
- // 下載的文件的map,也能夠是實體Bean
- private HashMap<String, String> dataMap = null;
- private Context context;
- private boolean finished = false;
- private boolean paused = false;
- private int curSize = 0;
- private int length = 0;
- private Async startTask = null;
- private boolean isFirst = true;
- private String strUrl;
- @Override
- protected String doInBackground(String... Params) {
- dbHelper = new DBHelper(context);
- strUrl = Params[0];
- String name = dataMap.get("name");
- String appid = dataMap.get("appid");
- int startPosition = 0;
- URL url = null;
- HttpURLConnection httpURLConnection = null;
- InputStream inputStream = null;
- RandomAccessFile outputStream = null;
- // 文件保存路徑
- String path = Environment.getExternalStorageDirectory().getPath();
- // 文件名
- String fileName = strUrl.substring(strUrl.lastIndexOf('/'));
- try {
- length = getContentLength(strUrl);
- startPosition = getDownloadedLength(strUrl, name);
- /** 判斷是不是第一次啓動任務,true則保存數據到數據庫並下載,
- * false則更新數據庫中的數據 start
- */
- boolean isHas = false;
- for (String urlString : AppConstants.listUrl) {
- if (strUrl.equalsIgnoreCase(urlString)) {
- isHas = true;
- break;
- }
- }
- if (false == isHas) {
- saveDownloading(name, appid, strUrl, path, fileName, startPosition, length, 1);
- }
- else if (true == isHas) {
- updateDownloading(curSize, name, strUrl);
- }
- /** 判斷是不是第一次啓動任務,true則保存數據到數據庫並下載,
- * false則更新數據庫中的數據 end
- */
- // 設置斷點續傳的開始位置
- url = new URL(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();
- File outFile = new File(path+fileName);
- // 使用java中的RandomAccessFile 對文件進行隨機讀寫操做
- outputStream = new RandomAccessFile(outFile,"rw");
- // 設置開始寫文件的位置
- outputStream.seek(startPosition);
- byte[] buf = new byte[1024*100];
- int read = 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 (MalformedURLException e) {
- e.printStackTrace();
- }
- catch (IOException e) {
- e.printStackTrace();
- }
- catch (InterruptedException e) {
- 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 (IOException e) {
- e.printStackTrace();
- }
- }
- }
- // 這裏的返回值將會被做爲onPostExecute方法的傳入參數
- return strUrl;
- }
- /**
- * 暫停下載
- */
- public void pause() {
- paused = true;
- }
- /**
- * 繼續下載
- */
- public void continued() {
- paused = false;
- }
- /**
- * 中止下載
- */
- @Override
- protected void onCancelled() {
- finished = true;
- deleteDownloading(dataMap.get("url"), dataMap.get("name"));
- super.onCancelled();
- }
- /**
- * 當一個下載任務成功下載完成的時候回來調用這個方法,
- * 這裏的result參數就是doInBackground方法的返回值
- */
- @Override
- protected void onPostExecute(String result) {
- try {
- String name = 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 (NumberFormatException e) {
- e.printStackTrace();
- }
- super.onPostExecute(result);
- }
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- }
- /**
- * 更新下載進度,當publishProgress方法被調用的時候就會自動來調用這個方法
- */
- @Override
- protected void onProgressUpdate(Integer... values) {
- super.onProgressUpdate(values);
- }
- /**
- * 獲取要下載內容的長度
- * @param urlString
- * @return
- */
- private int getContentLength(String urlString){
- try {
- URL url = new URL(urlString);
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- return connection.getContentLength();
- }
- catch (MalformedURLException e) {
- e.printStackTrace();
- }
- catch (IOException e) {
- e.printStackTrace();
- }
- return 0;
- }
- /**
- * 從數據庫獲取已經下載的長度
- * @param url
- * @param name
- * @return
- */
- private int getDownloadedLength(String url, String name) {
- int downloadedLength = 0;
- SQLiteDatabase db = dbHelper.getReadableDatabase();
- String sql = "SELECT downloadBytes FROM fileDownloading WHERE downloadUrl=? AND name=?";
- Cursor cursor = db.rawQuery(sql, new String[] { url, name });
- while (cursor.moveToNext()) {
- downloadedLength = cursor.getInt(0);
- }
- db.close();
- return downloadedLength;
- }
- /**
- * 保存下載的數據
- * @param name
- * @param appid
- * @param url
- * @param downloadedLength
- */
- private void saveDownloading(String name, String appid, String url, String savePath, String fileName, int downloadBytes, int totalBytes, int status) {
- SQLiteDatabase db = dbHelper.getWritableDatabase();
- try {
- db.beginTransaction();
- String sql = "INSERT INTO fileDownloading(name, appid, downloadUrl, savePath, fileName, downloadBytes, totalBytes, downloadStatus) " +
- "values(?,?,?,?,?,?,?,?)";
- db.execSQL(sql, new Object[]{ name, appid, url, savePath, fileName, downloadBytes, totalBytes, status});
- db.setTransactionSuccessful();
- boolean isHas = false;
- // 判斷當前要下載的這個鏈接是否已經正在進行,若是正在進行就阻止在此啓動一個下載任務
- for (String urlString : 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();
- }
- }
- /**
- * 更新下載數據
- * @param cursize
- * @param name
- * @param url
- */
- private void updateDownloading(int cursize, String name, String url) {
- SQLiteDatabase db = dbHelper.getWritableDatabase();
- try {
- db.beginTransaction();
- String sql = "UPDATE fileDownloading SET downloadBytes=? WHERE name=? AND downloadUrl=?";
- db.execSQL(sql, new String[] { cursize + "", name, url });
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- db.close();
- }
- }
- /**
- * 刪除下載數據
- * @param url
- * @param name
- */
- private void deleteDownloading(String url, String name) {
- if (true == finished) {
- // 刪除保存的URL。這個listurl主要是爲了在列表中按添加下載任務的順序進行顯示
- for (int i = 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);
- }
- }
- SQLiteDatabase db = dbHelper.getWritableDatabase();
- String sql = "DELETE FROM fileDownloading WHERE downloadUrl=? AND name=?";
- db.execSQL(sql, new Object[] { url, name });
- db.close();
- }
- public void setDataMap(HashMap<String, String> dataMap) {
- this.dataMap = dataMap;
- }
- public HashMap<String, String> getDataMap() {
- return dataMap;
- }
- public boolean isPaused() {
- return paused;
- }
- public int getCurSize() {
- return curSize;
- }
- public int getLength() {
- return length;
- }
- public void setContext(Context context) {
- this.context = context;
- }
- public Context getContext() {
- return context;
- }
- public void setListView(ListView listView) {
- this.listView = listView;
- }
- }
好了,下載任務已經啓動了,接下來就該開始管理了。先說說以前錯誤的思路,估計大多數的網友可能跟我同樣,一想到列表首先想到的就是ListView,這多簡單啊,放一個ListView,繼承BaseAdapter寫個本身的Adapter,而後一展示,完事了,so easy。我也是這麼想的,這省事啊,用了之後才發現,確實省事,不過更新ProgressBar的時候但是給我愁死了,不管怎麼着都不能正常更新ProgressBar。在這個地方鑽了一週的牛角尖,昨兒個忽然靈光乍現,幹嗎給本身挖個坑,誰說列表就非得用ListView了,我本身寫個列表不就得了。app
先來看看列表頁都有些什麼dom
- package com.test.muldownloadtest;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.view.Window;
- import android.widget.Button;
- import android.widget.LinearLayout;
- import android.widget.ScrollView;
- public class DownloadManagerActivity extends Activity implements OnClickListener {
- private ScrollView scDownload;
- private LinearLayout llDownloadLayout;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- this.requestWindowFeature(Window.FEATURE_NO_TITLE);
- this.setContentView(R.layout.download_manager_layout);
- initView();
- }
- @Override
- protected void onResume() {
- super.onResume();
- refreshItemView();
- }
- private void initView(){
- Button btnGoback = (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);
- }
- /**
- * 列表中的每一項
- */
- private void refreshItemView(){
- for (int i = 0; i < AppConstants.listUrl.size(); i++) {
- DownloadItemView downloadItemView = new DownloadItemView(this, AppConstants.listUrl.get(i), i);
- downloadItemView.setId(i);
- downloadItemView.setTag("downloadItemView_"+i);
- llDownloadLayout.addView(downloadItemView);
- }
- }
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.btnExit:
- this.finish();
- break;
- case R.id.btnGoback:
- this.finish();
- break;
- default:
- break;
- }
- }
- }
很簡單,一個ScrollView,在這個ScrollView中在內嵌一個LinearLayout,用這個LinearLayout來存儲每個列表項。其實列表項很簡單,最基本只要三個控件就好了——ProgressBar、TextView、Button。一個是進度條,一個顯示百分比,一個用來暫停/繼續,偷個懶,這個佈局文件就不列出來了,咱就看看這個Button都幹嗎了。 異步
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.btnPauseOrResume:
- String btnTag = (String) btnPauseOrResume.getTag();
- if (btnTag.equals("pause")) {
- resumeDownload();
- }
- else if (btnTag.equals("resume")) {
- pauseDownload();
- }
- break;
- default:
- break;
- }
- }
- private void pauseDownload(){
- btnPauseOrResume.setTag("pause");
- btnPauseOrResume.setText(R.string.download_resume);
- Async pauseTask = null;
- // 判斷當前被中止的這個任務在任務列表中是否存在,若是存在就暫停
- if (AppConstants.linkedMapDownloading.containsKey(urlString)) {
- pauseTask = AppConstants.linkedMapDownloading.get(urlString);
- if (pauseTask != null) {
- pauseTask.pause();
- }
- }
- }
- private void resumeDownload(){
- btnPauseOrResume.setTag("resume");
- btnPauseOrResume.setText(R.string.download_pause);
- Async continueTask = 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。async
到此,核心功能展現完畢。附效果圖一張
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