從Android 2.3(API level 9)開始Android用系統服務(Service)的方式提供了Download Manager來最佳化處理長時間的下載操作。Download Manager處理HTTP串連並監控串連中的狀態變化以及系統重啟來確保每一個下載任務順利完成。
在大多數涉及到下載的情況中使用Download Manager都是不錯的選擇,特別是當使用者切換不同的應用以後下載需要在後台繼續進行,以及當下載任務順利完成非常重要的情況(DownloadManager對於斷點續傳功能支援很好)。
要想使用Download Manager,使用getSystemService方法請求系統的DOWNLOAD_SERVICE服務,程式碼片段如下:
String serviceString = Context.DOWNLOAD_SERVICE; DownloadManager downloadManager; downloadManager = (DownloadManager) getSystemService(serviceString);
下載檔案
要請求一個下載操作,需要建立一個DownloadManager.Request對象,將要請求下載的檔案的Uri傳遞給Download Manager的enqueue方法,程式碼片段如下所示:
String serviceString = Context.DOWNLOAD_SERVICE; DownloadManager downloadManager; downloadManager = (DownloadManager)getSystemService(serviceString); Uri uri = Uri.parse("http://developer.android.com/shareables/icon_templates-v4.0.zip"); DownloadManager.Request request = new Request(uri); long reference = downloadManager.enqueue(request);
在這裡返回的reference變數是系統為當前的下載請求分配的一個唯一的ID,我們可以通過這個ID重新獲得這個下載任務,進行一些自己想要進行的操作或者查詢
下載的狀態以及取消下載等等。
我們可以通過addRequestHeader方法為DownloadManager.Request對象request添加HTTP頭,也可以通過setMimeType方法重寫從伺服器返回的mime type。
我們還可以指定在什麼串連狀態下執行下載操作。setAllowedNetworkTypes方法可以用來限定在WiFi還是行動電話通訊下進行下載,setAllowedOverRoaming方法
可以用來阻止手機在漫遊狀態下下載。
下面的程式碼片段用於指定一個較大的檔案只能在WiFi下進行下載:
request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
Android API level 11 介紹了getRecommendedMaxBytesOverMobile類方法(靜態方法),返回一個當前行動電話通訊串連下的最大建議位元組數,可以來判斷下載
是否應該限定在WiFi條件下。
調用enqueue方法之後,只要資料連線可用並且Download Manager可用,下載就會開始。
要在下載完成的時候獲得一個系統通知(notification),註冊一個廣播接受者來接收ACTION_DOWNLOAD_COMPLETE廣播,這個廣播會包含一個
EXTRA_DOWNLOAD_ID資訊在intent中包含了已經完成的這個下載的ID,程式碼片段如下所示:
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); if (myDownloadReference == reference) { } } }; registerReceiver(receiver, filter);
使用Download Manager的openDownloadedFile方法可以開啟一個已經下載完成的檔案,返回一個ParcelFileDescriptor對象。我們可以通過Download Manager來查詢下載檔案的儲存地址,如果在下載時制定了路徑和檔案名稱,我們也可以直接操作檔案。
我們可以為ACTION_NOTIFICATION_CLICKED action註冊一個廣播接受者,當使用者從通知欄點擊了一個下載項目或者從Downloads app點擊可一個下載的項目的
時候,系統就會發出一個點擊下載項的廣播。
程式碼片段如下:
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_NOTIFICATION_CLICKED); BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String extraID = DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS; long[] references = intent.getLongArrayExtra(extraID); for (long reference : references) if (reference == myDownloadReference) { // Do something with downloading file. } } }; registerReceiver(receiver, filter);
定製Download Manager Notifications的樣式
預設情況下,通知欄中會顯示被Download Manager管理的每一個download每一個Notification會顯示當前的下載進度和檔案的名字。
通過Download Manager可以為每一個download request定製Notification的樣式,包括完全隱藏Notification。下面的程式碼片段顯示了通過setTitle和setDescription
方法來定製顯示在檔案下載Notification中顯示的文字。
request.setTitle(“Earthquakes”); request.setDescription(“Earthquake XML”);
setNotificationVisibility方法可以用來控制什麼時候顯示Notification,甚至是隱藏該request的Notification。有以下幾個參數:
- Request.VISIBILITY_VISIBLE:在下載進行的過程中,通知欄中會一直顯示該下載的Notification,當下載完成時,該Notification會被移除,這是預設的參數值。
- Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED:在下載過程中通知欄會一直顯示該下載的Notification,在下載完成後該Notification會繼續顯示,直到使用者點擊該
- Notification或者消除該Notification。
- Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION:只有在下載完成後該Notification才會被顯示。
- Request.VISIBILITY_HIDDEN:不顯示該下載請求的Notification。如果要使用這個參數,需要在應用的資訊清單檔中加上DOWNLOAD_WITHOUT_NOTIFICATION許可權。
指定下載儲存地址
預設情況下,所有通過Download Manager下載的檔案都儲存在一個共用下載快取中,使用系統產生的檔案名稱每一個Request對象都可以制定一個下載
儲存的地址,通常情況下,所有的下載檔案都應該儲存在外部儲存中,所以我們需要在應用資訊清單檔中加上WRITE_EXTERNAL_STORAGE許可權:
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
下面的程式碼片段是在外部儲存中指定一個任意的儲存位置的方法:
request.setDestinationUri(Uri.fromFile(f));
f是一個File對象。
如果下載的這個檔案是你的應用所專用的,你可能會希望把這個檔案放在你的應用在外部儲存中的一個專有檔案夾中。注意這個檔案夾不提供存取控制,
所以其他的應用也可以訪問這個檔案夾。在這種情況下,如果你的應用卸載了,那麼在這個檔案夾也會被刪除。
下面的程式碼片段是指定隱藏檔的路徑是應用在外部儲存中的專用檔案夾的方法:
request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, “Bugdroid.png”);
如果下載的檔案希望被其他的應用共用,特別是那些你下載下來希望被Media Scanner掃描到的檔案(比如音樂檔案),那麼你可以指定你的下載路徑在
外部儲存的公用檔案夾之下,下面的程式碼片段是將檔案存放到外部儲存中的公用音樂檔案夾的方法:
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC, "Android_Rock.mp3");
在預設的情況下,通過Download Manager下載的檔案是不能被Media Scanner掃描到的,進而這些下載的檔案(音樂、視頻等)就不會在Gallery和Music Player這樣的應用中看到。
為了讓下載的音樂檔案可以被其他應用掃描到,我們需要調用Request對象的allowScaningByMediaScanner方法。
如果我們希望下載的檔案可以被系統的Downloads應用掃描到並管理,我們需要調用Request對象的setVisibleInDownloadsUi方法,傳遞參數true。
取消和刪除下載
Download Manager的remove方法可以用來取消一個準備進行的下載,中止一個進行中的下載,或者刪除一個已經完成的下載。
remove方法接受若干個download 的ID作為參數,你可以設定一個或者幾個你想要取消的下載的ID,如下程式碼片段所示:
downloadManager.remove(REFERENCE_1, REFERENCE_2, REFERENCE_3);
該方法返回成功取消的下載的個數,如果一個下載被取消了,所有相關聯的檔案,部分下載的檔案和完全下載的檔案都會被刪除。
查詢Download Manager
你可以通過查詢Download Manager來獲得下載任務的狀態,進度,以及各種細節,通過query方法返回一個包含了下載任務細節的Cursor。
query方法傳遞一個DownloadManager.Query對象作為參數,通過DownloadManager.Query對象的setFilterById方法可以篩選我們希望查詢的下載任務的ID。也可以使用setFilterByStatus方法篩選我們希望查詢的某一種狀態的下載任務,傳遞的參數是DownloadManager.STATUS_*常量,可以指定進行中、暫停、失敗、完成四種狀態。
Download Manager包含了一系列COLUMN_*靜態String常量,可以用來查詢Cursor中的結果列索引。我們可以查詢到下載任務的各種細節,包括狀態,檔案大小,已經下載的位元組數,標題,描述,URI,本地檔案名稱和URI,媒體類型以及Media Provider download URI。
下面的程式碼片段是通過註冊監聽下載完成事件的廣播接受者來查詢下載完成檔案的本地檔案名稱和URI的實現方法:
@Override public void onReceive(Context context, Intent intent) { long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); if (myDownloadReference == reference) { Query myDownloadQuery = new Query(); myDownloadQuery.setFilterById(reference); Cursor myDownload = downloadManager.query(myDownloadQuery); if (myDownload.moveToFirst()) { int fileNameIdx = myDownload.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME); int fileUriIdx = myDownload.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI); String fileName = myDownload.getString(fileNameIdx); String fileUri = myDownload.getString(fileUriIdx); // TODO Do something with the file. Log.d(TAG, fileName + " : " + fileUri); } myDownload.close(); } }
對於暫停和失敗的下載,我們可以通過查詢COLUMN_REASON列查詢出原因的整數位。
對於STATUS_PAUSED狀態的下載,可以通過DownloadManager.PAUSED_*靜態常量來翻譯出原因的整數位,進而判斷出下載是由於等待網路連接還是等待WiFi串連還是準備重新下載三種原因而暫停。
對於STATUS_FAILED狀態的下載,我們可以通過DownloadManager.ERROR_*來判斷失敗的原因,可能是錯誤碼(失敗原因)包括沒有存放裝置,
儲存空間不足,重複的檔案名稱,或者HTTP errors。
下面的代碼是如何查詢出當前所有的暫停下載任務,提取出暫停原因以及檔案名稱,下載標題以及當前進度的實現方法:
// Obtain the Download Manager Service. String serviceString = Context.DOWNLOAD_SERVICE; DownloadManager downloadManager; downloadManager = (DownloadManager)getSystemService(serviceString); // Create a query for paused downloads. Query pausedDownloadQuery = new Query(); pausedDownloadQuery.setFilterByStatus(DownloadManager.STATUS_PAUSED); // Query the Download Manager for paused downloads. Cursor pausedDownloads = downloadManager.query(pausedDownloadQuery); // Find the column indexes for the data we require. int reasonIdx = pausedDownloads.getColumnIndex(DownloadManager.COLUMN_REASON); int titleIdx = pausedDownloads.getColumnIndex(DownloadManager.COLUMN_TITLE); int fileSizeIdx = pausedDownloads.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES); int bytesDLIdx = pausedDownloads.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); // Iterate over the result Cursor. while (pausedDownloads.moveToNext()) { // Extract the data we require from the Cursor. String title = pausedDownloads.getString(titleIdx); int fileSize = pausedDownloads.getInt(fileSizeIdx); int bytesDL = pausedDownloads.getInt(bytesDLIdx); // Translate the pause reason to friendly text. int reason = pausedDownloads.getInt(reasonIdx); String reasonString = "Unknown"; switch (reason) { case DownloadManager.PAUSED_QUEUED_FOR_WIFI : reasonString = "Waiting for WiFi"; break; case DownloadManager.PAUSED_WAITING_FOR_NETWORK : reasonString = "Waiting for connectivity"; break; case DownloadManager.PAUSED_WAITING_TO_RETRY : reasonString = "Waiting to retry"; break; default : break; } // Construct a status summary StringBuilder sb = new StringBuilder(); sb.append(title).append("\n"); sb.append(reasonString).append("\n"); sb.append("Downloaded ").append(bytesDL).append(" / " ).append(fileSize); // Display the status Log.d("DOWNLOAD", sb.toString()); } // Close the result Cursor. pausedDownloads.close();
附:DownloadManager的一些重要功能和參數整理
DownloadManager類提供了以下幾種方法來處理,
long enqueue(DownloadManager.Request request) //存入隊列一個新的下載項
ParcelFileDescriptor openDownloadedFile(long id) //開啟一個下載後的檔案用於讀取,參數中的long型id是一個provider中的一條記錄。
Cursor query(DownloadManager.Query query) //查詢一個下載,返回一個Cursor
int remove(long... ids) //取消下載同時移除這些條從下載管理中。
我們可以看到提供的方法都比較簡單,給我們操作的最終封裝成為一個provider資料庫的方式進行添加、查詢和移除,但是對於查詢和新增工作的細節,我們要看看DownloadManager.Request類和DownloadManager.Query 類了。
一、DownloadManager.Request類的成員和定義
- DownloadManager.Request addRequestHeader(String header, String value) // 添加一個Http請求前序,對於這兩個參數,Android開發網給大家舉個小例子,比如說User-Agent值可以為Android123或 Windows XP等等了,主要是給伺服器提供標識。
- DownloadManager.Request setAllowedNetworkTypes(int flags) //設定允許使用的網路類型,這一步Android 2.3做的很好,目前有兩種定義分別為NETWORK_MOBILE和NETWORK_WIFI我們可以選擇使用移動網路或Wifi方式來下載。
- DownloadManager.Request setAllowedOverRoaming(boolean allowed) //對於下載,考慮到流量費用,這裡是否允許使用漫遊。
- DownloadManager.Request setDescription(CharSequence description) //設定一個描述資訊,主要是最終顯示的notification提示,可以隨便寫個自己區別
- DownloadManager.Request setDestinationInExternalFilesDir(Context context, String dirType, String subPath) //設定目標儲存在外部目錄,一般位置可以用 getExternalFilesDir()方法擷取。
- DownloadManager.Request setDestinationInExternalPublicDir(String dirType, String subPath) //設定外部儲存的公用目錄,一般通過getExternalStoragePublicDirectory()方法擷取。
- DownloadManager.Request setDestinationUri(Uri uri) //設定需要下載目標的Uri,可以是http、ftp等等了。
- DownloadManager.Request setMimeType(String mimeType) //設定mime類型,這裡看伺服器配置,一般國家化的都為utf-8編碼。
- DownloadManager.Request setShowRunningNotification(boolean show) //是否顯示下載進度的提示
- DownloadManager.Request setTitle(CharSequence title) //設定notification的標題
- DownloadManager.Request setVisibleInDownloadsUi(boolean isVisible) //設定下載管理類在處理過程中的介面是否顯示
- 當然了Google還提供了一個簡單的方法來執行個體化本類,這個構造方法為DownloadManager.Request(Uri uri) ,我們直接填寫一個Uri即可,上面的設定使用預設情況。
二、DownloadManager.Query類
對於當前下載內容的狀態,我們可以使用DownloadManager.Query類來擷取,本類比較簡單,僅僅提供了兩個方法。
- DownloadManager.Query setFilterById(long... ids) //根據id來過濾尋找。
- DownloadManager.Query setFilterByStatus(int flags) //根據任務的狀態來尋找。
詳細的狀態在android.app.DownloadManager類中有定義,目前Android 2.3中的定義為:
- int STATUS_FAILED 失敗
- int STATUS_PAUSED 暫停
- int STATUS_PENDING 等待將開始
- int STATUS_RUNNING 正在處理中
- int STATUS_SUCCESSFUL 已經下載成功
最後Android開發網提醒大家要說的是因為DownloadManager類提供的query方法返回一個Cursor對象,這些狀態儲存在這個遊標的COLUMN_STATUS 欄位中。
1. 下載的狀態完成均是以廣播的形式通知大家,目前API Level為9定義了下面三種Intent的action
(1)ACTION_DOWNLOAD_COMPLETE下載完成的動作。
(2)ACTION_NOTIFICATION_CLICKED 當使用者單擊notification中下載管理的某項時觸發。
(3)ACTION_VIEW_DOWNLOADS 查看下載項
2. 對於一個尚未完成的項,在Cursor中我們尋找COLUMN_REASON欄位,可能有以下定義:
(1)int ERROR_CANNOT_RESUME 不能夠繼續,由於一些其他原因。
(2)int ERROR_DEVICE_NOT_FOUND 外部存放裝置沒有找到,比如SD卡沒有插入。
(3)int ERROR_FILE_ALREADY_EXISTS 要下載的檔案已經存在了,Android123提示下載管理類是不會覆蓋已經存在的檔案,所以如果需要重新下載,請先刪除以前的檔案。
(1)int ERROR_FILE_ERROR 可能由於SD卡原因導致了檔案錯誤。
(2)int ERROR_HTTP_DATA_ERROR 在Http傳輸過程中出現了問題。
(3)int ERROR_INSUFFICIENT_SPACE 由於SD卡空間不足造成的
(4)int ERROR_TOO_MANY_REDIRECTS 這個Http有太多的重新導向,導致無法正常下載
(5)int ERROR_UNHANDLED_HTTP_CODE 無法擷取http出錯的原因,比如說遠程伺服器沒有響應。
(6)int ERROR_UNKNOWN 未知的錯誤類型.
3. 有關暫停一些狀態,同樣COLUMN_REASON欄位的值可能是以下定義
(1)int PAUSED_QUEUED_FOR_WIFI 由於移動網路資料問題,等待WiFi串連能用後再重新進入下載隊列。
(2)int PAUSED_UNKNOWN 未知原因導致了任務下載的暫停.
(3)int PAUSED_WAITING_FOR_NETWORK 可能由於沒有網路連接而無法下載,等待有可用的網路連接恢複。.
(4)int PAUSED_WAITING_TO_RETRY 由於重重原因導致下載暫停,等待重試。