DownloadManager速查笔记

/ 0评 / 0

前言

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

发表评论

您的电子邮箱地址不会被公开。