前言
DownloadManager是Android系统提供的下载工具,不过不支持断点续传以及暂停/继续,这一点不是很好,不过对于意见简单的下载场景还是比较实用的,所以本篇主要介绍下其简单用法。
使用DownloadManager进行下载
要想使用DownloadManager,首先得获取系统的DownloadManager服务,然后创建一个DownloadManager.Request对象进行设置,最后通过DownloadManager的enqueue方法将DownloadManager.Request对象加入下载队列,这样系统就会开始下载了,比较重要的是enqueue方法返回的id,通过这个id我们可以取消下载,获取下载任务的进度等。
DownloadManager.Request常用设置可以参考下面的代码注释。
//开始下载 public void beginDownload(View view) { //创建一个下载对象 DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mApkUri)); //DownloadManager.Request.NETWORK_MOBILE //DownloadManager.Request.NETWORK_WIFI //设置允许下载的网络条件,分为流量以及WIFI,默认为全部网络都允许下载 request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); //设置下载的时候时候显示在通知栏上面,分为如下几种 //DownloadManager.Request.VISIBILITY_HIDDEN 永远不显示,不过需要权限android.permission.DOWNLOAD_WITHOUT_NOTIFICATION //DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED 下载完成的时候显示 //DownloadManager.Request.VISIBILITY_VISIBLE 下载中显示(默认) //可以组合使用 //| DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); //设置是否允许使用漫游流量,默认是允许 //request.setAllowedOverRoaming(true); //设置通知栏标题 request.setTitle("标题"); //设置通知栏描述 request.setDescription("不可描述"); //设置Mime类型,默认为下面这个 //request.setMimeType("application/vnd.android.package-archive"); //request.setDestinationUri(); //下载到外置SD卡的DownLoad目录下 //request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "a.apk"); //下载到外置缓存路径,当本应用被卸载的时候会被一同删除 File file = new File(getExternalCacheDir(), "a.apk"); request.setDestinationUri(Uri.fromFile(file)); //文件是否允许被MediaScanner扫描,默认为false //request.allowScanningByMediaScanner(); DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); id = dm.enqueue(request); showInfo("Download id = " + id); }
取消下载
取消下载只需要使用enqueue方法返回的id即可取消下载,取消下载以后,临时文件会一并被删除。
/** * 停止下载,已经下载的部分文件也会被一起删除 */ public void stopDownload(View view) { DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); dm.remove(id); showInfo("Remove Download id = " + id); }
监听任务栏的点击事件以及下载完毕事件
首先对于DownloadManager的下载任务来说,如果在通知栏显示,我们可以通过广播获取到通知栏的点击事件,可以实现类似点击通知栏停止下载的功能。对于下载完毕的广播,不仅仅是当下载完成以后会调用,当任务通过remove方法删除以后,也会发出下载完毕的广播。
//注册下载完毕监听,以及点击通知栏监听 IntentFilter filter = new IntentFilter(); filter.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED); filter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE); registerReceiver(myBroadcastReceiver, filter); /** * 接收通知栏点击事件以及下载完成事件 */ private BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(action)) { showInfo("点击了通知栏"); stopDownload(null); } //当没有下载完成,通过remove删除掉任务,也会发出完成广播 if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) { showInfo("下载完毕"); } } };
对下载任务进行监听
首先我们得知道,DownloadManager的下载信息保存在/data/data/com.android.providers.downloads/databases/downloads.db中,由于系统提供了ContentProvider,所以我们可以通过ContentResolver以及ContentObserver去查询里面的数据以及观察其的变化。
这里为了方便,我将所有可以查询到的数据全部封装为一个类,大致如下
public class DownloadInfo { /** * 任务id */ private long id; /** * 下载地址 */ private String uri; /** * 本地文件路径 /mnt/sdcard/Download/weixin_1080.apk */ private String local_filename; /** * 下载路径 file:///mnt/sdcard/Download/weixin_1080.apk * <p> * 下载成功以后才有值 */ private String hint; /** * 类似下面这样 * content://media/external/file/11 */ private String mediaprovider_uri; /** * 下载状态 * 失败 {@link android.app.DownloadManager#STATUS_FAILED} * 暂停 {@link android.app.DownloadManager#STATUS_PAUSED} * 准备 {@link android.app.DownloadManager#STATUS_PENDING} * 下载中 {@link android.app.DownloadManager#STATUS_RUNNING} * 成功 {@link android.app.DownloadManager#STATUS_SUCCESSFUL} */ private int status; /** * 文件总大小 */ private long totalSize; /** * 已经下载的大小 */ private long bytes_so_far; /** * 失败原因 */ private String reason; /** * 任务title */ private String title; /** * 任务description */ private String description; /** * media_type,默认为application/vnd.android.package-archive */ private String mediaType; /** * 最后一次修改的时间 */ private long lastModifiedTimestamp; private Context mContext; public DownloadInfo(Context context) { this.mContext = context; } public void query(int id) { this.id = id; DownloadManager dm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); //只能查询自己的下载记录,别人的无数据返回 Cursor c = dm.query(new DownloadManager.Query().setFilterById(this.id)); if (c.moveToFirst()) { this.uri = c.getString(c.getColumnIndex("uri")); this.local_filename = c.getString(c.getColumnIndex("local_filename")); this.hint = c.getString(c.getColumnIndex("hint")); this.status = c.getInt(c.getColumnIndex("status")); this.totalSize = c.getLong(c.getColumnIndex("total_size")); this.bytes_so_far = c.getLong(c.getColumnIndex("bytes_so_far")); this.reason = c.getString(c.getColumnIndex("reason")); this.title = c.getString(c.getColumnIndex("title")); this.description = c.getString(c.getColumnIndex("description")); this.mediaprovider_uri = c.getString(c.getColumnIndex("mediaprovider_uri")); this.mediaType = c.getString(c.getColumnIndex("media_type")); this.lastModifiedTimestamp = c.getLong(c.getColumnIndex("last_modified_timestamp")); } c.close(); } /** * 获取下载百分比 */ public int getDownloadPercent() { if (this.totalSize == 0) { return 0; } return (int) (this.bytes_so_far * 100 / this.totalSize); } /** * 是否下载成功 */ public boolean isSuccessful() { return this.status == DownloadManager.STATUS_SUCCESSFUL; } }
然后我们使用ContentObserver观察数据库的变化,就能随时了解下载状态以及下载进度了。
private static final Uri DOWNLOAD_URI = Uri.parse("content://downloads/my_downloads"); /** * 监听下载进度 * 下载信息全部保存在/data/data/com.android.providers.downloads/databases/downloads.db中 * 所以我们可以直接使用ContentObserver监听其(content://downloads/my_downloads)数据变化,然后查询出已经下载的字节数和总字节数 */ public void progresslistener(View view) { getContentResolver().registerContentObserver(DOWNLOAD_URI, true, mContentObserver); } private ContentObserver mContentObserver = new ContentObserver(null) { /** * 这个在非主线程 */ public void onChange(boolean selfChange, Uri uri) { Log.d(TAG, "selfChange=" + selfChange + ", uri=" + uri); long id = -1; try { //获取下载id id = Long.parseLong(uri.getLastPathSegment()); DownloadInfo downinfo = new DownloadInfo(MainActivity.this); //查询对应id的下载信息,不过只能查询到自己下载的数据 downinfo.query((int) id); Log.i(TAG, "onChange: downinfo" + downinfo.toString()); } catch (NumberFormatException e) { Log.d(TAG, "Unknown uri received!"); } Log.i(TAG, "id = " + id); } };
其他一下猜想
前面已经介绍了,系统下载信息是保存在/data/data/com.android.providers.downloads/databases/downloads.db中的,我们到处数据库,观察其结构,发现里面有部分字段可以标识是否正在下载等,那么是不是只要我们手动去设置这个值就可以控制下载/暂停了呢,下面给出测试代码。
//仅仅在部分机型上有效,大部分机型会出现异常 public void pause(View view) { ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues(); //1 停止下载 values.put("control", 1); //Value of {@link #COLUMN_STATUS} 193 pause by app values.put("status", 193); Uri temp = ContentUris.withAppendedId(Uri.parse("content://downloads/my_downloads"), id); int nRet = resolver.update(temp, values, null, null); if (nRet >= 1) { showInfo("暂停ok"); } else { showInfo("暂停error"); } } //仅仅在部分机型上有效,大部分机型会出现异常 public void resume(View view) { ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues(); //0 This download is allowed to run. values.put("control", 0); //Value of {@link #COLUMN_STATUS} when the download is currently running. 192 values.put("status", 192); Uri temp = ContentUris.withAppendedId(Uri.parse("content://downloads/my_downloads"), id); int nRet = resolver.update(temp, values, null, null); if (nRet >= 1) { showInfo("开始ok"); } else { showInfo("开始error"); } }
不够分别测试了下,貌似可以暂停,但是继续下载会出现功能上的异常(UI上显示正在下载,但是实际没有开始下载),个人猜想是由于权限的问题,Android系统为了安全,可能不允许非系统应用的修改生效,不过也没有具体查看源码,纯属猜测
Demo地址:GitHub