Android系统下载管理DownloadManager

一. DownloadManager简单介绍

DownloadManger是android 2.3(api level 9)开始 提供的用于优化处理长时间的下载操作。DownloadManager 处理Http/Https连接并监控连接中的状态变化及系统重启来确保每一个下载任务顺利完成。大多数涉及到下载的情况中使用DownloadManager都是很好的选择,尤其是后台继续下载,下载状态回调,断点续传,下载环境设置,下载文件的操作等方面,支持的很好。

DownloadManager是系统开放给第三方应用使用的类,包含两个静态内部类DownloadManager.Query和DownloadManager.Request。DownloadManager.Request用来请求一个下载,DownloadManager.Query用来查询下载信息,具体接口信息可参看最后的api说明。

二. DownloadManager使用

DownloadManager主要对外提供了以下接口:

  • public long enqueue(Request request)执行下载,返回downloadId,downloadId可用于后面查询下载信息。若网络不满足条件、Sdcard挂载中、超过最大并发数等异常会等待下载,正常则直接下载。

  • int remove(long… ids) 删除下载,若下载中取消下载。会同时删除下载文件和记录。

  • Cursor query(Query query) 查询下载信息。

  • getMaxBytesOverMobile(Context context) 返回移动网络下载的最大值

  • rename(Context context, long id, String displayName) 重命名已下载项的名字

  • getRecommendedMaxBytesOverMobile(Context context) 获取建议的移动网络下载的大小

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

三. DownloadManager下载示例

1.下载所需权限

<uses-permission android:name="android.permission.INTERNET" />;

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>;

2.调用DownloadManager.Request开始下载

DownloadManager downloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);

String apkUrl = "https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";

DownloadManager.Request request = new 

DownloadManager.Request(Uri.parse(apkUrl));

request.setDestinationInExternalPublicDir("dirType", "/mydownload/QQ.apk");

// request.setTitle("TX QQ");

// request.setDescription("This is TX QQ");

// 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);

上面代码是将一个下载url加入到系统下载队列,在条件满足时会自动开始下载,调用enqueue()返回的是一个long型的id值,该id为该下载项的唯一id。

可以对Request(下载项)进行属性设置,除了构造参数的URI必填,其他条件都是可选的,如下载的本地路径,下载后的重命名,下载通知的显示与不显示或者通知的样式,下载允许的网络类型,下载项的MimeType等。

request.setMimeType(“application/cn.trinea.download.file”);

设置下载文件的mineType。因为下载管理Ui中点击某个已下载完成文件及下载完成点击通知栏提示都会根据mimeType去打开文件,所以我们可以利用这个属性。比如上面设置了mimeType为application/cn.trinea.download.file,我们可以同时设置某个Activity的intent-filter为application/cn.trinea.download.file,用于响应点击的打开文件。

<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>

添加请求下载的网络链接的http头,比如User-Agent,gzip压缩等:

request.addRequestHeader(String header, String value);

3.下载进度状态的监听及查询

DownloadManager下载工具并没有提供相应的回调接口用于返回实时的下载进度状态,但通过安卓的四大组件之一ContentProvider,我们可以监听到当前的下载项的进度状态变化。

downloadManager.getUriForDownloadedFile(id);

该方法会返回一个下载项的Uri,如:content://downloads/my_downloads/125
ContentObserver——内容观察者,可监听观察特定Uri指向的数据库项的变化,进而进行相应的处理,其中我们会监听Uri.parse(“content://downloads/my_downloads”)。然后查询下载状态和进度,进行下一步操作如发送handler进行更新UI。

下面的方法可根据下载项的id,查询下载项的下载进度状态并返回“当前已下载字节”、“总字节”、“当前下载状态”:

 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排序。

需要对ContentProvider提供的内容进行观察,需要重新ContentObserver中的 onChange(boolean selfChange)方法,并在activity/fragment的相应生命周期方法中去注册/注销Observer:

 class DownloadStatusObserver extends ContentObserver {
        public DownloadStatusObserver() {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange) {
            int[] bytesAndStatus = getBytesAndStatus(downloadId);
            int currentSize = bytesAndStatus[0];//当前大小
            int totalSize = bytesAndStatus[1];//总大小
            int status = bytesAndStatus[2];//下载状态

        }

    }

@Override
    protected void onResume() {
        super.onResume();
        getContentResolver().registerContentObserver(CONTENT_URI, true, observer);
}

 @Override
    protected void onDestroy() {
        super.onDestroy();
        getContentResolver().unregisterContentObserver(observer);
    }
  • 如果界面上过多元素需要更新,且网速较快不断的执行onChange会对页面性能有一定影响。推荐ScheduledExecutorService定期查询,如下:

      //三秒定时刷新一次
      public static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
      Runnable command = new Runnable() {
      
              @Override
              public void run() {
                  updateView();
              }
          };
      scheduledExecutorService.scheduleAtFixedRate(command, 0, 3, TimeUnit.SECONDS);
    

4.下载成功监听

下载完成后,下载管理会发出DownloadManager.ACTION_DOWNLOAD_COMPLETE这个广播,并传递downloadId作为参数。通过接受广播我们可以打开对下载完成的内容进行操作。代码如下:

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);
}

5、响应通知栏点击

  • 响应下载中通知栏点击

点击下载中通知栏提示,系统会对下载的应用单独发送Action为DownloadManager.ACTION_NOTIFICATION_CLICKED广播。

intent.getData为content://downloads/all_downloads/29669,最后一位为downloadId。

如果同时下载多个应用,intent会包含DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS这个key,表示下载的的downloadId数组。

  • 响应下载完成通知栏点击

下载完后会调用下面代码进行处理,从中我们可以发现系统会调用View action根据mimeType去查询。所以可以利用我们在介绍的DownloadManager.Request的setMimeType函数。

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);
    }
}

6、通过隐式意图打开系统下载界面

Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
startActivity(intent);

四.DownloadManager/Request/Query 接口参考

DownloadManager:
 long enqueue(Request request) 

将一个新的下载项加入队列,该下载项将在downloadmanager准备好执行该下载项/连接可用时自动开始下载,返回该下载的唯一id

int remove(long... ids)

移除一个或多个下载项,返回改变的个数

Cursor query(Query query) 

执行一个query Qurey类下面有介绍

openDownloadedFile(long id)

打开一个文件(该文件必须已下载完成)

getUriForDownloadedFile(long id) 

返回一个已下载项的Uri,如:content://downloads/my_downloads/103

getMimeTypeForDownloadedFile(long id)

返回一个已下载项mimetype

restartDownload(long... ids)      

重新已完成的下载项(下载成功,下载失败)开始下载;

getMaxBytesOverMobile(Context context)

返回移动网络下载的最大值

rename(Context context, long id, String displayName) 

重命名已下载项的名字

getRecommendedMaxBytesOverMobile(Context context)

获取建议的移动网络下载的大小

addCompletedDownload(String title, String description,boolean isMediaScannerScannable, String mimeType, String path, long length,boolean showNotification)

添加一个文件到系统的下载文件数据库中,并且可以在系统下载app中显示出该文件的下载条目;
根据api上的说明,该方法对于使一个文件成为被MediaScanner可扫描的文件很有用,调用该方法,可将指定的文件变成scannable by MediaScanner by setting the param isMediaScannerScannable to true.如相册gallary应用中就很有用


Request
setDestinationUri(Uri uri) :

自定义下载本地路径,传入一个Uri类型;

setDestinationInExternalFilesDir(Context context, String dirType,String subPath)  :

自定义下载本地路径,为内置sd卡的Android/data/package-name/ 父目录下,该目录下的内容会随应用的卸载而删除清空数据,dirType:文件的类型(Environment.DIRECTORY_DOWNLOADS,Environment.DIRECTORY_DCIM,......);
subPath:父目录下得子目录路径(/apk/myPath/myapk.apk)

setDestinationInExternalPublicDir(String dirType, String subPath) :

同上,下载到sd卡上共享的公共父目录下的subPath子目录下

allowScanningByMediaScanner() :

从字面意思可以明白,是允许该下载项可以被MediaScanner(多媒体文件的扫描工作)扫描到。(MediaScanner认识:http://blog.csdn.net/hellofeiya/article/details/8255898

addRequestHeader(String header, String value) :

添加一个Http请求头信息到http头信息列表末尾

setTitle(CharSequence title)/setDescription(CharSequence description) 

设置通知栏中下载通知的显示样式

setMimeType(String mimeType)

Set the MIME content type of this download. This will override the content type declared in the server's response.
设置MimeType,会覆盖服务器返回的已声明的content type

setShowRunningNotification(boolean show)(已过时)

设置开始下载时是否通过通知的形式显示该下载项状态,默认显示

setNotificationVisibility(int visibility)替代上面方法

visibility取值:
{@link #VISIBILITY_HIDDEN}, {@link #VISIBILITY_VISIBLE}, {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}.

setAllowedNetworkTypes(int flags)

设置下载时允许的网络环境,默认允许所有网络环境

setAllowedOverRoaming(boolean allow)

Set whether this download may proceed over a metered network connection. By default, metered networks are allowed.
设置是否允许漫游网络下的下载,默认允许漫游下载

setRequiresCharging(boolean requiresCharging)

设置是否必须充电状态下下载,默认否

setRequiresDeviceIdle(boolean requiresDeviceIdle)

设置是否必须手机处于空闲状态下载,默认否

setVisibleInDownloadsUi(boolean isVisible)

设置下载项是否在系统的 下载 app中显示 默认显示,打开系统自带下载app可查看该下载项


Query
setFilterById(long... ids)

根据id对downloadmanager中的下载项进行筛选,返回cursor

setFilterByStatus(int flags) 

根据status对downloadmanager中的下载项进行筛选,返回cursor

setOnlyIncludeVisibleInDownloadsUi(boolean value)

设置为true,则是对仅仅显示在下载界面的下载项进行筛选,反之,未显示的也将被筛选

orderBy(String column, int direction)

对返回的Cursor所携带的数据根据column,direction进行排序
column取值 COLUMN_LAST_MODIFIED_TIMESTAMP
COLUMN_TOTAL_SIZE_BYTES
direction取值 ORDER_ASCENDING
ORDER_DESCENDING

具体参考DownloadManager官方API



参考:
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容