关闭

Android系统下载管理DownloadManager

412人阅读 评论(0) 收藏 举报

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



文/jiaming_(简书作者)
原文链接:http://www.jianshu.com/p/7ad92b3d9069
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:41437次
    • 积分:949
    • 等级:
    • 排名:千里之外
    • 原创:44篇
    • 转载:59篇
    • 译文:1篇
    • 评论:12条
    最新评论