5.完全在後臺下載
5.1 問題
應用程式需要為裝置下載一個大的資源,如電影檔案,並且不要求使用者讓應用程式一直處於啟用狀態。
5.2解決方案
(API LEVEL 9)
使用DownloadManager API。DownloadManager是API Level 9中加入SDK的一個服務,它讓系統完全處理和管理需要多長時間執行的下載操作。使用這個服務最大的有點就是即使在下載失敗、連線改變甚至裝置重啟時,DownloadManager依然會繼續嘗試下載資源。
5.3 實現機制
以下程式碼清單是一個示例Activity,它使用DownloadManager來處理一個大圖片檔案的下載。完成下載後,這個圖片會顯示在一個ImageView上。在使用DownloadManager訪問Web上的內容時,確保在應用程式的清單檔案中聲明瞭android.permission.INTERNET許可權。
DownloadManager示例Activity
public class DownloadActivity extends Activity { private static final String DL_ID = "downloadId"; private SharedPreferences prefs; private DownloadManager dm; private ImageView imageView; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); imageView = new ImageView(this); setContentView(imageView); prefs = PreferenceManager.getDefaultSharedPreferences(this); dm = (DownloadManager)getSystemService(DOWNLOAD_SERVICE); } @Override public void onResume() { super.onResume(); if(!prefs.contains(DL_ID)) { //開始下載 Uri resource = Uri.parse("http://www.bigfoto.com/dog-animal.jpg"); DownloadManager.Request request = new DownloadManager.Request(resource); //設定允許的連線來處理下載request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI); request.setAllowedOverRoaming(false); //在通知欄上顯示 request.setTitle("Download Sample"); long id = dm.enqueue(request); //儲存唯一的 id prefs.edit().putLong(DL_ID, id).commit(); } else { //下載已經開始,檢查下載狀態 queryDownloadStatus(); } registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } @Override public void onPause() { super.onPause(); unregisterReceiver(receiver); } private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { queryDownloadStatus(); } }; private void queryDownloadStatus() { DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(prefs.getLong(DL_ID, 0)); Cursor c = dm.query(query); if(c.moveToFirst()) { int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); Log.d("DM Sample","Status Check: "+status); switch(status) { case DownloadManager.STATUS_PAUSED: case DownloadManager.STATUS_PENDING: case DownloadManager.STATUS_RUNNING: //什麼也不做,下載依然進行 break; case DownloadManager.STATUS_SUCCESSFUL: //下載完成,顯示圖片 try { ParcelFileDescriptor file = dm.openDownloadedFile(prefs.getLong(DL_ID, 0)); FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(file); imageView.setImageBitmap(BitmapFactory.decodeStream(fis)); } catch (Exception e) { e.printStackTrace(); } break; case DownloadManager.STATUS_FAILED: //清除下載並稍後重試 dm.remove(prefs.getLong(DL_ID, 0)); prefs.edit().clear().commit(); break; } } } }
要點:
本書出版時,SDK中存在一個bug,就是在使用DownloadManager時需要丟擲android.permission.ACCESS_ALL_DOWNLOADS異常。實際上是在清單中沒有宣告android.permission.INTERNET時才會丟擲這個異常。
示例中所有有用的工作都是在Activity.onResume()中完成的,這樣每次使用者返回到Activity時,應用程式都可以確定下載的狀態。每次下載都會呼叫DownloadManager.enqueue()並返回一個long型的ID值,該值可以用來引用管理器中的本次下載。在本例中,為了隨時監控和獲取下載的內容。這個請求至少要指定遠端資源的Uri。另外,還可以為這個請求設定很多有用的屬性來控制它的行為。這些有用的屬性包括:
- Request.setAllowedNetworkTyped() :指定獲取下載所使用的網路型別。
- Request.setAllowedOverRoaming() :設定當裝置處於漫遊模式時是否要下載。
- Request.setDescription() :設定下載在系統通知欄中顯示的描述。
- Request.setTitle :設定下載在系統通知欄中顯示的標題。
獲取ID後,應用程式會使用那個值來檢查下載的狀態。通過註冊BroadcastReceiver來監聽ACTION_DOWNLOAD_COMPLETE廣播,在下載完成後,會將圖片檔案設定到Activity的ImageView以進行響應。如果在下載完成時Activity處於暫停狀態,那麼就會在下次恢復Activity時檢測下載狀態,並設定ImageView的內容。
要注意ACTION_DOWNLOAD_COMPLETE是DownloadManager對其管理的所有下載任務發出的廣播。正因為如此,我們仍然必須檢查下載ID才能判斷我們的下載是否完成。
目標
在以下程式碼清單中,我們並沒有告訴DownloadManager檔案的下載位置。相反,當我們想要訪問檔案時,我們會使用DownloadManager.openDownloadedFile()方法和首選項中儲存的ID值來獲得一個ParcelFileDescriptor,它可轉換為應用程式可以讀取的資料流。這是訪問下載內容簡單而直接的方式,但還是有一些事項需要注意。
如果沒有指定目標位置,檔案會下載到共享下載快取中,系統隨時有權刪除這個快取中的檔案來回收空間。正因為如此,這種下載方式可以很方便地快速獲取資料,但如果您的下載需求是長期的,應該使用DownloadManager.Request的一個方法在外部儲存器上指定固定的目標位置:
- Request.setDestinationInExternalFilesDir() :設定目標位置為外部儲存器中的一個隱藏目錄。
- Request.setDestinationInExternalPublicDir() :設定目標位置為外部儲存器中的一個公共目錄。
- Request.setDestinationUri() :設定目標位置為外部儲存器中的一個檔案Uri。
注意:
所有在外部儲存器中設定目標位置的方法都需要應用程式在清單中宣告android.permission.WRITE_EXTERNAL_STORAGE許可權。
在呼叫DownloadManager.remove()來清單管理器列表中的條目或者使用者清理下載列表時,沒有明確指定目標位置的檔案通常會被移除;而外部儲存器中下載的檔案在這些檔案下則不會被系統移除。