场景:正在开发的应用中有分享这样一个功能,这个分享是需要先把一篇pdf格式的文章下载到本地,然后再去进行分享的操作,在Android7.0之前都没有问题,但是7.0及以后的版本就会报这个异常,其实不止这一个场景,在其他需要访问目录文件的场景,也是会报这个异常
原因:从Android7.0为了提高私有文件的安全性,面向Android N或更高版本的应用私有目录将被限制访问,也就是说从Android 7.0开始,一个应用提供私有文件给其它应用使用时,如果传递一个file://格式的Uri的话,由于谷歌认为目标应用可能不具有文件权限,就会抛出FileUriExposedException
谷歌官方推荐解决方案:通过FileProvider生成content:// Uri来替代file:// Uri,并授予其临时访问权限
首先来看下谷歌的官方文档,大概意思上面已经介绍了,我(实)就(际)不(上)翻(是)译(英)一(语)遍(太)了(差)
public class FileUriExposedException
extends RuntimeException
java.lang.Object
↳ java.lang.Throwable
↳ java.lang.Exception
↳ java.lang.RuntimeException
↳ android.os.FileUriExposedException
The exception that is thrown when an application exposes a file:// Uri to another app.
This exposure is discouraged since the receiving app may not have access to the shared path. For example, the receiving app may not have
requested the READ_EXTERNAL_STORAGE runtime permission, or the platform may be sharing the Uri across user profile boundaries.
Instead, apps should use content:// Uris so the platform can extend temporary permission for the receiving app to access the resource.
This is only thrown for applications targeting N or higher. Applications targeting earlier SDK versions are allowed to share file:// Uri, but it's strongly discouraged.
See also:
FileProvider
FLAG_GRANT_READ_URI_PERMISSION
具体步骤如下:
1、在AndroidManifest.xml中添加如下代码,声明一个FileProvider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="最好是app的包名.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
注:
authorities:该provider的唯一标识,可自定义,为了避免与其他应用冲突,最好是app的包名.fileProvider
grantUriPermissions:用来控制共享文件的访问权限,必须是true,表示授予URI 临时访问权限
exported:必须是false,否则会报异常java.lang.SecurityException: Provider must not be exported
resource:共享文件的路径,file_paths是我们接下来要添加的文件即该provider对外提供文件的目录的配置文件,存放在res/xml/目录下,名称可自定义
2、在res目录下新建一个xml文件夹,并且新建一个file_paths的xml文件,内容如下
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="gomejrj_download"
path="gomejrj/" />
</paths>
注:
path:需要临时授权访问的路径(可以是完整路径,也可以是路径中的关键字,比如我下载的pdf存放在Android/data/包名/gomejrj/cache路径下,所以我的path=”gomejrj/”就可以了)
name:访问路径的名称(其实可以随便取)
external-path:这个是重点,这个不是固定的,是跟你代码中设置路径的方法对应的,具体对应关系如下表
节点 | 对应方法 |
---|---|
files-path | getFilesDir() |
cache-path | getCacheDir() |
external-path | Environment.getExternalStorageDirectory() |
external-files-path | getExternalFilesDir() |
external-cache-path | getExternalCacheDir() |
3、在java代码中使用FileProvider(以文章开始的分享pdf文件为例)
String mSharePath = Environment.getExternalStorageDirectory() + File.separator + "gomejrj" + File.separator + "下载的文件名称" + ".pdf";//下载的pdf存放的路径
Uri imageUri = FileProvider.getUriForFile(this, "最好是app的包名.fileprovider", new File(mSharePath));//第二个参数是manifest中定义的'authorities'
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("*/*");
shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//这一步很重要,给目标应用一个临时的授权
startActivity(Intent.createChooser(shareIntent, "分享到"));