最近项目中有一个很简单的需求:就是用系统自带图库打开在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 的基本使用就完成了。