前言
对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在应用外部公开 file:// URI
, 如果一项包含文件 URI 的 intent 离开应用,则应用出现故障,并出现 FileUriExposedException 异常。
要应用间共享文件,您应发送一项 content:// URI
,并授予 URI
临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。如需了解有关权限和共享文件的详细信息,请参阅 共享文件
FileProvider 实际上是 ContentProvider 的一个子类,它的作用也比较明显,file://Uri
不给用,那么换个 Uri 为 content://
来替代。
解决方法
1.在manifest中注册FileProvider
<application>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="hzyj.come.p2p.fileprovider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="name,authorities,grantUriPermissions,exported">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"
tools:replace="name,resource"
/>
</provider>
</application>
如果出现如下报错,可能是引入的第三方jar包或类库已经添加过
只需如上添加 :
tools:replace="name,authorities,grantUriPermissions,exported"
tools:replace="name,resource"
2、指定可用的文件路径
在项目的res目录下,创建xml文件夹,并新建一个file_paths.xml文件。通过这个文件来指定文件路径:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--path:需要临时授权访问的路径(.代表所有路径) name:就是你给这个访问路径起个名字-->
<external-path
name="external-path"
path="."/>
<files-path
name="files_path"
path="."/>
<external-cache-path
name="external_cache_path"
path="."/>
<external-files-path
name="external_files_path"
path="."/>
<cache-path
name="cache_path"
path="."/>
</paths>
在 paths
节点内部支持以下几个子节点,分别为:
子节点 | 含义 | 目录 |
---|---|---|
<root-path> | 代表设备的根目录 new File("/") | 根目录 |
<files-path> | 代表 context.getFileDir() | /data/data/com.xxx.app/files |
<cache-path> | 代表 context.getCacheDir() | /data/data/com.xxx.app/cache |
<external-path> | 代表 Environment.getExternalStorageDirectory() | /storage/emulated |
<external-files-path> | 代表 context.getExternalFilesDirs() | /storage/emulated/0/Android/data/com.xxx.app/files |
<external-cache-path> | 代表 getExternalCacheDirs() | /storage/emulated/0/Android/data/com.xxx.app/cache |
<external-path name="external" path="pics"/>
代表的目录即为:Environment.getExternalStorageDirectory()/pics
3、DownloadManager 进行下载并注册BroadcastReceiver监听
/**
* @param downloadUrl 下载url
* @param desc 更新描述
* @param latestVersion 最新版本号
*/
private void initUpDate(final String downloadUrl, String desc, final int latestVersion) {
apkPath = Tools.getExternalStoragePublicDirectory() + File.separator + getString(R.string.apk_name);
mUpdateDialog = new UpdateDialog(getContext());
mUpdateDialog.setUpdateDialogCallBack(() -> downloadApk(downloadUrl, latestVersion));
mUpdateDialog.showDialog(desc);
}
/**
* @param downloadUrl 下载url
* @param latestVersion 最新版本号
*/
public void downloadApk(String downloadUrl, int latestVersion) {
final DownloadManager dManager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
File apkFile = new File(apkPath);
if (apkFile.exists()) {
PackageManager pm = getContext().getPackageManager();
PackageInfo packageInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
if (packageInfo != null) {
int versionCode = packageInfo.versionCode;
//已下载最新安装包直接跳转安装界面
if (versionCode == latestVersion) {
startInstall();
return;
}
}
apkFile.delete();
}
Uri uri = Uri.parse(downloadUrl);
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setDestinationUri(Uri.fromFile(new File(apkPath)));
request.setDescription(getString(R.string.app_name) + "版本更新");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setMimeType("application/vnd.android.package-archive");
// 设置为可被媒体扫描器找到
request.allowScanningByMediaScanner();
// 设置为可见和可管理
request.setVisibleInDownloadsUi(true);
// 获取此次下载的ID
final long refernece = dManager.enqueue(request);
// 注册广播接收器,当下载完成时自动安装
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
long myDwonloadID = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (refernece == myDwonloadID) {
startInstall();
}
}
};
getActivity().registerReceiver(mReceiver, filter);
}
4、下载完成后跳转安装界面
private void startInstall() {
Intent localIntent = new Intent(Intent.ACTION_VIEW);
localIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri uri;
/**
* Android7.0+禁止应用对外暴露file://uri,改为content://uri;具体参考FileProvider
*/
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID+".fileprovider",
new File(apkPath));
localIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
uri = Uri.fromFile(new File(apkPath));
}
Log.d("BroadcastReceiver", "uri =" + uri);
localIntent.setDataAndType(uri, "application/vnd.android.package-archive"); //打开apk文件
try {
startActivity(localIntent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
}
FileProvider映射图示
image.png
另外需要注意android6.0动态权限以及
android8.0要多加一条权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
判断一下8.0特殊处理 然后正常走就可以了,坑!!!!
Demo
网友评论