Android中的DownloadManager使用

前言

在Android开发中经常会用到将一个文件下载到本地存储卡,这个过程看起来很容易,就是一个网络请求过程而已,但是有的时候我们需要把下载的内容存储到一个可以公共访问的位置,供其他应用共享,这个时候我们可以使用Android官方提供的一个DownloadManage来很方便的实现。事实上DownloadManage自API 9就已经存在了,中间官方只做了一些小的调整。

下载文件

下面通过一个下载图片的小Demo来一步步理解DownloadManager的使用和涉及到的一些内在知识,类的基本关系如下。

Demo结构

接下来开始一个简单的UI实现,放一个按钮并添加点击事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
android:id="@+id/download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download"
android:layout_centerInParent="true"/>
</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//下载图片文件路径
private static final String URI_STRING = "http://dp2px.com/images/head.png";

private Button download;

private Downloader downloader;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//按钮点击事件
download = (Button) findViewById(R.id.download);
download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//下载或取消下载
downloadOrCancel();
}
});
//获取download对象(Downloader实现了DownloadManager的逻辑)
downloader = Downloader.newInstance(this);
}

void downloadOrCancel() {
if (downloader.isDownloading()) {
cancel();
} else {
download();
}
updateUi();
}

private void cancel() {
downloader.cancel();
}

private void download() {
Uri uri = Uri.parse(URI_STRING);
downloader.download(uri);
}

上面代码的逻辑很简单,点击按钮来根据当前下载状态切换是否下载或者暂停,此刻你也许已经看了出来,问题的核心在于Downloader类的实现。Downloader类可能会让你大吃一惊,它是如此的简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Downloader{

private final Listener listener;
private final DownloadManager downloadManager;

private long downloadId = -1;

private Downloader(DownloadManager downloadManager, Listener listener) {
this.downloadManager = downloadManager;
this.listener = listener;
}

//创建DowloadManager对象并初始化实例。
static Downloader newInstance(Listener listener) {
Context context = listener.getContext();
//通过Context.DOWNLOAD_SERVICE获取DownloadManager实例
DownloadManager downloadManager = (DownloadManager)
context.getSystemService(Context.DOWNLOAD_SERVICE);
return new Downloader(downloadManager, listener);
}

//开始下载
void download(Uri uri) {
if (!isDownloading()) {
DownloadManager.Request request = new DownloadManager.Request(uri);
downloadId = downloadManager.enqueue(request);
}
}

//判断是否在下载中
boolean isDownloading() {
return downloadId >= 0;
}

//取消下载
void cancel() {
if (isDownloading()) {
downloadManager.remove(downloadId);
downloadId = -1;
}
}

//下载结果监听回调接口
interface Listener {
void fileDownloaded(Uri uri, String mimeType);
Context getContext();
}
}

点击我们的下载按钮,可以下载图片了,不过这个时候我们感知不到,因为我们上面代码没有监听下载过程和结果,DownloadManager的下载结果是通过广播来接收的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class DownloadReceiver extends BroadcastReceiver {

private final Listener listener;

DownloadReceiver(Listener listener) {
this.listener = listener;
}

@Override
public void onReceive(Context context, Intent intent) {
//获取广播结果
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
listener.downloadComplete(downloadId);
}

//注册广播
public void register(Context context) {
IntentFilter downloadFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
context.registerReceiver(this, downloadFilter);
}

//取消注册
public void unregister(Context context) {
context.unregisterReceiver(this);
}

//广播结果回调接口
interface Listener {
void downloadComplete(long downloadId);
}
}

这个广播我们在Dowloader中注册,从DownloadManager中取的下载结果并处理下载结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Downloader implements DownloadReceiver.Listener {

//...省略

@Override
public void downloadComplete(long completedDownloadId) {
if (downloadId == completedDownloadId) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
downloadId = -1;
unregister();
Cursor cursor = downloadManager.query(query);
while (cursor.moveToNext()) {
getFileInfo(cursor);
}
cursor.close();
}
}

void register() {
if (receiver == null) {
receiver = new DownloadReceiver(this);
receiver.register(listener.getContext());
}
}

void unregister() {
if (receiver != null) {
receiver.unregister(listener.getContext());
}
receiver = null;
}

private void getFileInfo(Cursor cursor) {
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
Long id = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
Uri uri = downloadManager.getUriForDownloadedFile(id);
String mimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
listener.fileDownloaded(uri, mimeType);
}
}

interface Listener {
void fileDownloaded(Uri uri, String mimeType);
Context getContext();
}
}

在分析DownloadManager中如何拿到下载到的文件数据之前,我们切记要在MainActivity中的声明周期中调用Downloader的unregister,以便在退出MainActivity的时候释放掉资源。当我们退出MainActivity的时候不需要关心下载结果,而下载过程还在后台进行。

1
2
3
4
5
@Override
protected void onDestroy() {
downloader.unregister();
super.onDestroy();
}

我们注意到上面在getFileInfo中最终获得的是一个content://...的Uri对象,这个Uri对于其他应用访问是很友好的,允许它们通过DownloadManager提供的ContentProvider访问内容,关于ContentProvider请参考我的另一篇博文《FileProvider》

Download

DownloadManager在后台有一个下载服务,当我们中途下载失败或者系统重启等特殊情况下会尝试重新下载的。

下载设置

通知栏

在我们下载文件的过程中通知栏会多一条消息,这个消息的显示内容我们可以通过setNotificationVisibility()自定义样式。也可以使用DownloadManager.Request对象来控制让通知栏不显示,但是需要获得DOWNLOAD_WITHOUT_NOTIFICATION权限。

使用DownloadManager.Request对象的setTitle()setDescription()方法更改代码如下:

1
2
3
4
5
6
7
8
9
void download(Uri uri) {
if (!isDownloading()) {
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setTitle("下载图片");
request.setDescription("正在下载图片,请稍等...");
downloadId = downloadManager.enqueue(request);
register();
}
}

Header头

有时候我们需要在提交服务器的时候添加一些Header消息头,同样可以使用DownloadManager.Request对象的addRequestHeader()方法添加。

下载限制

有的时候我们需要下载的文件非常大,这个时候可能需要去限制下载的流量,可以使用DownloadManager.Request对象的setAllowedOverRoaming()方法来设置下载流量大小,可以使用setAllowedNetworkTypes()方法来设置和过滤特定的网络类型(在API 16之后可以使用setAllowedOverMetered()方法代替)

系统可见

还可以使用DownloadManager.Request对象的allowScanningByMediaScanner()方法来设置是否下载完成后对系统可见。对系统可见的意思是,是否系统可以扫描,例如我们在下载图片的时候指定它,系统就会扫描该文件,然后在图像库中可以看到它。

我们可以控制内容可见的另一种方法是DownloadManager.Request对象的setVisibleInDownloadsUi()方法,这个方法来控制在下载过程中是否在系统下载应用程序中可见。

保存位置

默认情况下,我们的文件是被下载到系统的一个默认区域,这个区域是应用程序的私有区域,这个区域来存放文件的文件只可供应用程序内部访问,如果卸载应用程序会删除它。但是有两种情况默认的存储区域并不合适。

第一种:我们存储的文件非常大,这个时候不适合存放在应用程序私有区域,应该存储在外部存储区域上。这个时候可以使用setDestinationInExternalFilesDir()来存储到应用程序的外部存储私有区域。

第二种:我们存储的文件非常大并且需要被其他应用程序共享,上面的setDestinationInExternalFilesDir()不能方便的被其他文件共享,这个时候需要使用setDestinationInExternalPublicDir()来存储,这个会被系统扫描到。

1
2
3
private static final String DIRECTORY = "Download/DownloadManager";
//...
request.setDestinationInExternalPublicDir(DIRECTORY, uri.getLastPathSegment());

注意:外部存储区域需要WRITE_EXTERNAL_STORAGE权限,而且应用卸载后文件仍然存在。

评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Babel Banner Buffer Bulma ByteBuffer C++ C11 C89 C99 CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage ES2015 ESLint Element Error Exception Extensions File FileProvider Flow Fresco GCC Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid I/O IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Lint Linux Looper MQTT MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Remote-SSH Retrofit Runnable RxAndroid RxJava SE0 SSH Spring SpringBoot Statubar Task Theme Thread Tkinter UI UIKit UML VM virtualBox VS Code VUE ValueAnimator ViewPropertyAnimator Vue Web Web前端 Workbench api apk bookmark by关键字 compileOnly css c语言 databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box vscode 七牛 下载 中介者模式 串口 临潼石榴 主题 书签 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内存 内核 内部分享 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 头文件 奇点 字符串 字符集 存储引擎 宇宙 宏定义 实践 属性 属性动画 岐山擀面皮 岐山肉臊子 岐山香醋 工具 工厂模式 年终总结 开发技巧 异常 弱引用 恒星 打包 技巧 指针 插件 摄影 操作系统 攻略 故事 数据库 数据类型 数组 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 枚举 架构 查询 标准库 标签选择器 样式 核心 框架 案例 桥接模式 检测工具 模块化 模板引擎 模板方法模式 油泼辣子 泛型 洛川苹果 浅色状态栏 源码 源码分析 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 眉县猕猴桃 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 结构 结构体 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 联合 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 虚拟机 补码 补齐 表单 表达式 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 链表 锐化 错误 键盘 闭包 降噪 陕西地方特产 面向对象 项目优化 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×