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