最近项目中有一个很简单的需求:就是用系统自带图库打开在APP中下载的图片,结果就遇到了
android.os.FileUriExposedException: file:///storage/emulated/0/Pictures/xxx.jpg exposed beyond app through Intent.getData()
的异常。
原来从Android 7.0开始,谷歌收回了访问文件的权限,即一个应用提供自身资源文件给其它应用使用时,如果给出 file://xxx 这样格式的URI的话,谷歌会认为目标应用不具备访问此文件的权限,便会抛出 FileUriExposedException 的异常。我这里的解决方法是使用 FileProvider,来生成一个content://xxx 格式的URI,并授予此 URI 临时访问权限,ok,异常解决了。下面我们来一起看看实现方式吧:
1.在 Manifest中声明FileProvider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.xxx.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"
>
</meta-data>
</provider>
name为 FileProvider的完整类名:android.support.v4.content.FileProvider ,是固定的;
authorities为自定义的权限名称,大家可以自定义,通常都会带上包名来避免与其他应用冲突;
exported为 false,代表只有同一个应用程序的组件或带有相同用户ID的应用程序才能使用,在 FileProvider的使用中一定要填false,否则会报错;
grantUriPermissions为true,代表允许通过接受传递的权限方式进行访问,拥有共享文件的临时权限。即如果应用A具有读取 ContentProvider 的权限,它去调用另一个应用B的activity,但是B没有读取此 ContentProvider 的权限,那么应用A可以将自己的权限通过intent传递给应用B,让其也具有访问此ContentProvider 的权限。(因此后面代码中会对Intent 进行设置: intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2.在res下的xml文件夹中新建一个provider_paths.xml文件(名字可自定义,和上一步中resource的value一致即可)
<resources>
<paths>
<external-path
name="gallery_photos"
path=""/>
</paths>
</resources>
内部的element可以是 files-path,cache-path,external-path等,代表的根目录分别对应为:Context.getFilesDir(),Context.getCacheDir(),Environment.getExternalStorageDirectory()等。
name是一个标识,作为根目录的名称,可以自定义;
path代表根目录下要共享的目标。
这里大家也许看的会有些困惑,我举个简单的例子:
大家都知道 ContentProvider 使用 URI 标识要操作的数据,这里的内容 URI 主要包括两部分:authority:整个提供程序的符号名称;path:指向表的名称/路径,所以内容 URI 统一的形式就是:content://authority/path。
那么当我们以上面为例,通过 FileProvider 获取到的某资源文件(比如根目录下的xxx.jpg)的uri链接即为:content://com.xxx.fileprovider/gallery_photos/xxx.jpg
3.在代码中调用(我这里就以实现用系统自带图库打开下载的一张图片为例了)
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri photoURI = FileProvider.getUriForFile(getApplicationContext(),
"com.xxx.fileprovider",
file);//file即为所要共享的文件的file
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//授予临时权限别忘了
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(photoURI, "image/*");
startActivity(intent);
ok,至此 FileProvider 的基本使用就完成了。