DownloadManager
downloadmanager概述
DownloadManager是Android SDK中封装的下载文件类,可以很方便开发者使用下载文件。其具体看官方APIhttps://developer.android.com/reference/android/app/DownloadManager.html使用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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115public class DownloadUtils {
private DownloadManager mDownloadManager;
private Context mContext;
private long downloadId;
private String apkName;
public DownloadUtils(Context context) {
mContext = context;
}
public void download(String url, String name) {
final String packageName = "com.android.providers.downloads";
int state = mContext.getPackageManager().getApplicationEnabledSetting(packageName);
//检测下载管理器是否被禁用
if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|| state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
|| state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext).setTitle("温馨提示").setMessage
("系统下载管理器被禁止,需手动打开").setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + packageName));
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
mContext.startActivity(intent);
}
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.create().show();
} else {
//正常下载流程
apkName = name;
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setAllowedOverRoaming(false);
//通知栏显示 request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle(AppUtils.getAppName(mContext));
request.setDescription("正在下载中...");
request.setVisibleInDownloadsUi(true);
//设置下载存放的文件夹和文件名字 request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, apkName);
//获取DownloadManager
mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
downloadId = mDownloadManager.enqueue(request);
mContext.registerReceiver(mReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
checkStatus();
}
};
/**
* 检查下载状态
*/
private void checkStatus() {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor cursor = mDownloadManager.query(query);
if (cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
switch (status) {
//下载暂停
case DownloadManager.STATUS_PAUSED:
break;
//下载延迟
case DownloadManager.STATUS_PENDING:
break;
//正在下载
case DownloadManager.STATUS_RUNNING:
break;
//下载完成
case DownloadManager.STATUS_SUCCESSFUL:
installAPK();
break;
//下载失败
case DownloadManager.STATUS_FAILED:
Toast.makeText(mContext, "下载失败", Toast.LENGTH_SHORT).show();
break;
}
}
cursor.close();
}
/**
* 7.0兼容
*/
private void installAPK() {
File apkFile =
new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), apkName);
Intent intent = new Intent(Intent.ACTION_VIEW);
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".provider", apkFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
}
mContext.startActivity(intent);
}
}
android 7.0 DownloadManager不在按照文件名分享私人存储的文件
按照开发者官网上的描述,DownloadManager不在支持使用COLUMN_LOCAL_FILENAME访问路径,其会触发SecurityException。官方建议有DownloadManager公开的文件,首选
的访问方式是使用ContentResolver.openFileDescriptor()。如果使用downloadManager做下载功能的且没有做7.0适配的话,在7.0读取数据库内容时会遇到如下错误:
1
java.lang.SecurityException: COLUMN_LOCAL_FILENAME is deprecated; use ContentResolver.openFileDescriptor() instead
在Android 7.0中通过DownloadManager根据downId获取的uri安装apk是会得到如下错误:1
2Caused by: android.os.FileUriExposedException:
file:///storage/emulated/0/Download/myApp.apk exposed beyond app through Intent.getData()
同时在7.0上通过DownloadManager根据downId获取的uri变为:1
content://downloads/all_downloads/430
这些都是由于Android7.0执行了“StrictMode API 政策禁”的原因,可以用FileProvider来解决这一问题。
- 原来的代码如下:
1
2
3
4
5
6
7
8DownloadManager manager = (DownloadManager) mContext
.getSystemService(Context.DOWNLOAD_SERVICE);
Cursor downloadCursor = dm.query(myDownloadQuery);
if (myDownload != null && downloadCursor.moveToFirst()) {
int fileNameIdx = downloadCursor
.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
String filePath = downloadCursor.getString(fileNameIdx);
}
7.0获取文件路径
- DownloadManager.COLUMN_LOCAL_URI:DownloadManager的数据库表中应该是存放了对应的URI,目测应该是file: 协议开头。那看来可以通过查询这个URI,随后将URI转换成文件path路径。
- 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12DownloadManager manager = (DownloadManager) mContext
.getSystemService(Context.DOWNLOAD_SERVICE);
Cursor downloadCursor= dm.query(myDownloadQuery);
if (downloadCursor!= null && downloadCursor.moveToFirst()) {
String filePath = null;
String downloadFileLocalUri = downloadCursor
.getString(downloadCursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
if (downloadFileLocalUri != null) {
filePath = new File(Uri.parse(downloadFileLocalUri).getPath())
.getAbsolutePath();
}
}
FileProvider
Why FileProvider?
- 老生常谈的内容,为什么Android推荐使用FileProvider呢?其实FileProvider也不是一个新概念了,Google很早就提出这个概念了。这是因为Android上的权限管理过于松散,但是随着系统版本的不断提升,权限的不断收紧,对于进程之间的数据共享就产生了问题。于是FileProvider就出现了。
- 它通过在AndroidManifest中定义Provider,为指定的文件提供一个ContentURI,通过这个URI临时赋予第三方应用授权处理指定的某些文件。简而言之,也可以说成是使用ContentURI代替了FileURI。
fileprovider 使用场景
- 正如上面我们说到的DownloadManager的变更中,我们通过DownloadManager下载了一个APK。此时我们需要发送一个Intent,带着我们下载下来的APK的URI,通知系统的APK安装管理服务,安装这个应用。
- 此时,这个Intent就要带着这个URI离开我们的App,去到另一个应用。此时,我们就需要对这个URI进行处理。如果发送的Intent带的是一个FileURI,就会导致:
FileUriExposedException。
使用方法:
<1> AndroidManifest.xml:在manifest中加入如下代码:1
2
3
4
5
6
7
8
9
10<provider
android:exported="false"
android:grantUriPermissions="true"
android:authorities="com.×××.fileprovider"
android:name="android.support.v4.content.FileProvider">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"
/>
</provider>
- exported要设置为false,否则会报安全异常。
- grantUriPermissions设置为true,表示的是设置URI临时访问权限。
- authorities可以随便写,不过一般最好是包名.fileprovider。当然也可以是其他。
<2> file_paths.xml:2>
在manifest中有下面这一句:
1
android:resource="@xml/file_paths"
我们需要在res/xml目录下新建一个file_paths.xml的文件,内容如下:
1
2
3
4
5
6
7
8<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<root-path
name="/"
path="/" />
</paths>
</resources>作用是指定临时授权的路径,其选项有:
选项 | 含义 |
---|---|
root-paty | 根目录(/) |
external-path | Context.getExternalStorageDirectory() |
file-path | Context.getFilesDir() |
cache-path | Context.getCacheDir() |
其中root-path指的是Android系统根目录,众所周知Android是基于Linux内核,所以Android的根目录就是“/”
<3> 使用FileProvider:3>
当我们拿到一个Apk文件时,需要启动安装界面进行安装。则可以使用以下方式:
1
2
3
4
5
6
7
8
9
10
11if (Build.VERSION.SDK_INT >= 24) {
File apkFile=new File(apkFile Path);
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri contentUri = FileProvider.getUriForFile(mContext,
"com.×××.fileprovider", apkFile);
intent.setDataAndType(contentUri,
"application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}使用FileProvider.getUriForFile()将一个FileURI转换成ContentURI
- 并且不要忘记添加Intent.FLAG_GRANT_READ_URI_PERMISSION临时的读或者写权限。这样就完成一个FileProvider共享的使用了。
后记
- 已兼容7.0私有文件权限问题
- 对于部分机型默认或者一些原因,下载管理器是被禁用掉的,必须手动开启或者写代码去跳转到设置界面开启,代码中已兼容。
来源:
http://www.ryanhuen.tech/2017/02/28/Android-N-Diff/
http://www.jianshu.com/p/3eb4106133f4
http://blog.csdn.net/yulianlin/article/details/52775160