1、DownloadManager簡單介紹
DownloadManager是系統開放給第三方應用使用的類,包含兩個靜態內部類DownloadManager.Query和DownloadManager.Request。DownloadManager.Request用來請求一個下載,DownloadManager.Query用來查詢下載信息,這兩個類的具體功能會在後面穿插介紹。DownloadManager的源碼可見DownloadManager@Grepcode。css
DownloadManager主要提供了下面幾個接口:
public long enqueue(Request request)執行下載,返回downloadId,downloadId可用於後面查詢下載信息。若網絡不知足條件、Sdcard掛載中、超過最大併發數等異常會等待下載,正常則直接下載。
public int remove(long… ids)刪除下載,若下載中取消下載。會同時刪除下載文件和記錄。
public Cursor query(Query query)查詢下載信息。java
public static Long getRecommendedMaxBytesOverMobile(Context context經過移動網絡下載的最大字節數
public String getMimeTypeForDownloadedFile(long id)獲得下載的mimeType,如何設置後面會進行介紹android
其它:經過查看代碼咱們能夠發現還有個CursorTranslator私有靜態內部類。這個類主要對Query作了一層代理。將DownloadProvider和DownloadManager之間作個映射。將DownloadProvider中的十幾種狀態對應到了DownloadManager中的五種狀態,DownloadProvider中的失敗、暫停緣由轉換爲了DownloadManager的緣由。git
2、下載管理示例
下面具體介紹利用DownloadManager進行下載。
一、AndroidManifest中添加權限github
1
2
|
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
網絡訪問權限是必須的,下載地址爲sdcard的話須要添加sdcard寫權限。數組
二、調用DownloadManager.Request開始下載網絡
1
2
3
4
5
6
7
8
9
10
11
|
DownloadManager downloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
String apkUrl = "http://img.meilishuo.net/css/images/AndroidShare/Meilishuo_3.6.1_10006.apk";
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
request.setDestinationInExternalPublicDir("Trinea", "MeiLiShuo.apk");
// request.setTitle("MeiLiShuo");
// request.setDescription("MeiLiShuo desc");
// request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
// request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
// request.setMimeType("application/cn.trinea.download.file");
long downloadId = downloadManager.enqueue(request);
|
上面調用downloadManager的enqueue接口進行下載,返回惟一的downloadId。併發
DownloadManager.Request除了構造函數的Uri必須外,其餘設置都爲可選設置。下面逐個介紹下:
request.setDestinationInExternalPublicDir(「Trinea」, 「MeiLiShuo.apk」);表示設置下載地址爲sd卡的Trinea文件夾,文件名爲MeiLiShuo.apk。app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
File file = Environment.getExternalStoragePublicDirectory(dirType);
Xlog.v(XLOGTAG, "setExternalPublicDir: dirType " +
dirType + " subPath " + subPath +
"file" + file);
if (file.exists()) {
if (!file.isDirectory()) {
throw new IllegalStateException(file.getAbsolutePath() +
" already exists and is not a directory");
}
} else {
if (!file.mkdir()) {
throw new IllegalStateException("Unable to create directory: "+
file.getAbsolutePath());
}
}
setDestinationFromBase(file, subPath);
return this;
}
|
從源碼中咱們能夠看出下載完整目錄爲Environment.getExternalStoragePublicDirectory(dirType)。不過file是經過file.mkdir()建立的,這樣若是上級目錄不存在就會新建文件夾異常。因此下載前咱們最好本身調用File的mkdirs方法遞歸建立子目錄,以下:ide
1
2
|
File folder = new File(folderName);
return (folder.exists() && folder.isDirectory()) ? true : folder.mkdirs();
|
不然,會報異常
1
2
|
java
.lang.IllegalStateException: Unable to create directory: /storage/sdcard0/Trinea/aa
at
android.app.DownloadManager$Request.setDestinationInExternalPublicDir(DownloadManager.java)
|
其餘設置下載路徑接口爲setDestinationUri,setDestinationInExternalFilesDir,setDestinationToSystemCache。其中setDestinationToSystemCache僅限系統app使用。
request.allowScanningByMediaScanner();表示容許MediaScanner掃描到這個文件,默認不容許。
request.setTitle(「MeiLiShuo」);設置下載中通知欄提示的標題
request.setDescription(「MeiLiShuo desc」);設置下載中通知欄提示的介紹
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
表示下載進行中和下載完成的通知欄是否顯示。默認只顯示下載中通知。VISIBILITY_VISIBLE_NOTIFY_COMPLETED表示下載完成後顯示通知欄提示。VISIBILITY_HIDDEN表示不顯示任何通知欄提示,這個須要在AndroidMainfest中添加權限android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
表示下載容許的網絡類型,默認在任何網絡下都容許下載。有NETWORK_MOBILE、NETWORK_WIFI、NETWORK_BLUETOOTH三種及其組合可供選擇。若是隻容許wifi下載,而當前網絡爲3g,則下載會等待。
request.setAllowedOverRoaming(boolean allow)移動網絡狀況下是否容許漫遊。
request.setMimeType(「application/cn.trinea.download.file」);
設置下載文件的mineType。由於下載管理Ui中點擊某個已下載完成文件及下載完成點擊通知欄提示都會根據mimeType去打開文件,因此咱們能夠利用這個屬性。好比上面設置了mimeType爲application/cn.trinea.download.file,咱們能夠同時設置某個Activity的intent-filter爲application/cn.trinea.download.file,用於響應點擊的打開文件。
1
2
3
4
5
6
7
|
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/cn.trinea.download.file" />
</intent-filter>
|
request.addRequestHeader(String header, String value)
添加請求下載的網絡連接的http頭,好比User-Agent,gzip壓縮等
3 下載進度狀態監聽及查詢
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
class DownloadChangeObserver extends ContentObserver {
public DownloadChangeObserver(){
super(handler);
}
@Override
public void onChange(boolean selfChange) {
updateView();
}
}
public void updateView() {
int[] bytesAndStatus = downloadManagerPro.getBytesAndStatus(downloadId);
handler.sendMessage(handler.obtainMessage(0, bytesAndStatus[0], bytesAndStatus[1],
bytesAndStatus[2]));
}
private DownloadChangeObserver downloadObserver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.download_manager_demo);
……
downloadObserver = new DownloadChangeObserver();
}
@Override
protected void onResume() {
super.onResume();
/** observer download change **/
getContentResolver().registerContentObserver(DownloadManagerPro.CONTENT_URI, true,
downloadObserver);
}
@Override
protected void onPause() {
super.onPause();
getContentResolver().unregisterContentObserver(downloadObserver);
}
|
其中咱們會監聽Uri.parse(「content://downloads/my_downloads」)。而後查詢下載狀態和進度,發送handler進行更新,handler中處理就是設置進度條和狀態等。
其中DownloadManagerPro.getBytesAndStatus的主要代碼以下,可直接引入TrineaAndroidCommon@Github(歡迎star和fork^_^)做爲你項目的library(如何拉取代碼及添加公共庫):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public int[] getBytesAndStatus(long downloadId) {
int[] bytesAndStatus = new int[] { -1, -1, 0 };
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = null;
try {
c = downloadManager.query(query);
if (c != null && c.moveToFirst()) {
bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
} finally {
if (c != null) {
c.close();
}
}
return bytesAndStatus;
}
|
從上面代碼能夠看出咱們主要調用DownloadManager.Query()進行查詢。DownloadManager.Query爲下載管理對外開放的信息查詢類,主要包括如下接口:
setFilterById(long… ids)根據下載id進行過濾
setFilterByStatus(int flags)根據下載狀態進行過濾
setOnlyIncludeVisibleInDownloadsUi(boolean value)根據是否在download ui中可見進行過濾。
orderBy(String column, int direction)根據列進行排序,不過目前僅支持DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP和DownloadManager.COLUMN_TOTAL_SIZE_BYTES排序。
4 下載成功監聽
下載完成後,下載管理會發出DownloadManager.ACTION_DOWNLOAD_COMPLETE這個廣播,並傳遞downloadId做爲參數。經過接受廣播咱們能夠打開對下載完成的內容進行操做。代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
class CompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// get complete download id
long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
// to do here
}
};
private CompleteReceiver completeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.download_manager_demo);
…
completeReceiver = new CompleteReceiver();
/** register download success broadcast **/
registerReceiver(completeReceiver,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(completeReceiver);
}
|
五、響應通知欄點擊
(1) 響應下載中通知欄點擊
點擊下載中通知欄提示,系統會對下載的應用單獨發送Action爲DownloadManager.ACTION_NOTIFICATION_CLICKED廣播。intent.getData爲content://downloads/all_downloads/29669,最後一位爲downloadId。
若是同時下載多個應用,intent會包含DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS這個key,表示下載的的downloadId數組。這裏設計到下載管理通知欄的顯示機制,會在下一篇具體介紹。
(2) 響應下載完成通知欄點擊
下載完後會調用下面代碼進行處理,從中咱們能夠發現系統會調用View action根據mimeType去查詢。因此能夠利用咱們在介紹的DownloadManager.Request的setMimeType函數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private void openDownload(Context context, Cursor cursor) {
String filename = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl._DATA));
String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE));
Uri path = Uri.parse(filename);
// If there is no scheme, then it must be a file
if (path.getScheme() == null) {
path = Uri.fromFile(new File(filename));
}
Intent activityIntent = new Intent(Intent.ACTION_VIEW);
mimetype = DownloadDrmHelper.getOriginalMimeType(context, filename, mimetype);
activityIntent.setDataAndType(path, mimetype);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(activityIntent);
} catch (ActivityNotFoundException ex) {
Log.d(Constants.TAG, "no activity for " + mimetype, ex);
}
}
|
若是界面上過多元素須要更新,且網速較快不斷的執行onChange會對頁面性能有必定影響。推薦ScheduledExecutorService按期查詢,以下:
1
2
3
4
5
6
7
8
9
|
public static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable command = new Runnable() {
@Override
public void run() {
updateView();
}
};
scheduledExecutorService.scheduleAtFixedRate(command, 0, 3, TimeUnit.SECONDS);
|
表示3秒定時刷新