ANDROID 系统下载

Android 2.3(API level 9)开始Android用系统服务(Service)的方式提供了Download Manager来优化处理长时间的下载操作。Download Manager处理HTTP
连接并监控连接中的状态变化以及系统重启来确保每一个下载任务顺利完成。
在大多数涉及到下载的情况中使用Download Manager都是不错的选择,特别是当用户切换不同的应用以后下载需要在后台继续进行,以及当下载任务顺利完成非常重要的情况(DownloadManager对于断点续传功能支持很好)。

一、DownloadManager简单介绍

DownloadManager是系统开放给第三方应用使用的类,包含两个静态内部类DownloadManager.Query和DownloadManager.Request。DownloadManager.Request用来请求一个下载,DownloadManager.Query用来查询下载信息,这两个类的具体功能会在后面穿插介绍。DownloadManager的源码可见DownloadManager@Grepcode

DownloadManager主要提供了下面几个接口:public long enqueue(Request request)执行下载,返回downloadId,downloadId可用于后面查询下载信息。若网络不满足条件、Sdcard挂载中、超过最大并发数等异常会等待下载,正常则直接下载。public int remove(long… ids)删除下载,若下载中取消下载。会同时删除下载文件和记录。public Cursor query(Query query)查询下载信息。

public static Long getRecommendedMaxBytesOverMobile(Context context通过移动网络下载的最大字节数public String getMimeTypeForDownloadedFile(long id)得到下载的mimeType,如何设置后面会进行介绍

其它:通过查看代码我们可以发现还有个CursorTranslator私有静态内部类。这个类主要对Query做了一层代理。将DownloadProvider和DownloadManager之间做个映射。将DownloadProvider中的十几种状态对应到了DownloadManager中的五种状态,DownloadProvider中的失败、暂停原因转换为了DownloadManager的原因。

简单使用

完成一个下载任务只需要4行代码,什么断点续传,大文件下载,通知栏进度显示....都不需要你操心。

//创建下载任务,downloadUrl就是下载链接
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
//指定下载路径和下载文件名
request.setDestinationInExternalPublicDir("/download/", fileName);
//获取下载管理器
DownloadManager downloadManager= (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
//将下载任务加入下载队列,否则不会进行下载
downloadManager.enqueue(request);

高级用法

具体的使用方法

要想使用Download Manager,使用getSystemService方法请求系统的DOWNLOAD_SERVICE服务,代码片段如下

//创建下载任务 DownloadManager.Request request = new DownloadManager.Request(Uri.parse(versionUrl)); request.setAllowedOverRoaming(false);//漫游网络是否可以下载 //设置文件类型,可以在下载结束后自动打开该文件 MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); String mimeString = mimeTypeMap.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(versionUrl)); request.setMimeType(mimeString); //在通知栏中显示,默认就是显示的 request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); request.setVisibleInDownloadsUi(true); //sdcard的目录下的download文件夹,必须设置 request.setDestinationInExternalPublicDir("/download/", versionName); //request.setDestinationInExternalFilesDir(),也可以自己制定下载路径 //将下载请求加入下载队列 downloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); //加入下载队列后会给该任务返回一个long型的id, //通过该id可以取消任务,重启任务等等,看上面源码中框起来的方法 mTaskId = downloadManager.enqueue(request);

要请求一个下载操作,需要创建一个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); 
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();  
```
####参考
>[Android系统下载管理DownloadManager功能介绍及使用示例](http://www.trinea.cn/android/android-downloadmanager/)
[Android DownloadManager 的使用](http://blog.csdn.net/carrey1989/article/details/8060155)
[Android快速实现文件下载(只有4行代码)](http://www.jianshu.com/p/46fd1c253701)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容