Android 7.0使用FileProvider解决file:// URI引起的FileUriExposedException异常

现象描述

Android 7.0以前的版本

Uri photoUri = Uri.fromFile(tempFile);
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA);

File文件直接转换成"file://xxx/xxx/xxx"的uri格式

Android 7.0及以后的版本

当把targetSdkVersion指定成24及以上并且在API>=24的设备上运行时,这种方式则会出现FileUriExposedException异常

android.os.FileUriExposedException: file:///storage/emulated/0/europa/DCIM/Camera/0_42_20180908_123018_review.jpg exposed beyond app through ClipData.Item.getUri()

产生原因

参考:https://developer.android.com/reference/android/os/FileUriExposedException.html

Android不再允许在App中把file://Uri暴露给其他App,包括但不局限于通过Intent或ClipData等方法。

原因在于使用file://Uri会有一些风险,比如:

  • 文件是私有的,接收file://Uri的App无法访问该文件。
  • 在Android 6.0之后引入运行时权限,如果接收file://Uri的App没有申请READ_EXTERNAL_STORAGE权限,在读取文件时会引发崩溃。

因此,Google提供了FileProvider,使用它可以生成content://Uri来替代file://Uri。

解决方案

首先在AndroidManifest.xml中添加provider

  • android:authorities 用来标识provider的唯一标识,在同一部手机上一个"authority"串只能被一个App使用,冲突的话会导致App无法安装。
  • android:exported 必须设置成false
  • android:grantUriPermissions 用来控制共享文件的访问权限,也可以在Java代码中设置
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="me.yezhou.lib.photo" >

    <application>
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>

</manifest>

res/xml/provider_paths.xml 是指定路径和转换规则,如

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <paths>
        <external-cache-path path="photos/camera" name="camera_photos" />
        <cache-path path="photos/camera" name="camera_photos" />
    </paths>
</resources>
中可以定义以下子节点

| 子节点 | 对应路径 | 例子 |
| :-- | :-- | :-- |
| files-path | Context.getFilesDir() | |
| cache-path | Context.getCacheDir() | |
| external-path | Environment.getExternalStorageDirectory() | /storage/emulated/0/ |
| external-files-path | Context.getExternalFilesDir(null) | |
| external-cache-path | Context.getExternalCacheDir() | |

假如要将目录 file:///storage/emulated/0/appblog.cn/photo/ 替换为 content://${android:authorities}/photo_files/

那么配置应该写成

```xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path path="appblog.cn/photo" name="photo_files" />
</paths>
```

然后修改代码

```java
//Uri photoUri = Uri.fromFile(tempFile);
Uri photoUri = FileProvider.getUriForFile(
mContext,
mActivity.getPackageName() + ".fileprovider",
tempFile);
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA);
```

## 常见异常处理

```
java.lang.SecurityException: Provider must not be exported
```

解决方案:android:exported必须设置成false

```
Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
```

解决方案:AndroidManifest.xml处的android:authorities必须跟mActivity.getPackageName() + ".fileprovider"一致

## FileProvider

### FileProvider使用content://Uri的优点

- 可以控制共享文件的读写权限,只要调用Intent.setFlags()就可以设置对方App对共享文件的访问权限,并且该权限在对方App退出后自动失效。相比之下,使用file://Uri时只能通过修改文件系统的权限来实现访问控制,访问控制是对所有App都生效的,不能区分App。
- 可以隐藏共享文件的真实路径。

### file://content://的转换规则

- 替换前缀:把file://替换成content://${android:authorities}
- 匹配和替换:遍历的子节点,找到最大能匹配path的子节点,用name替换掉文件路径里所匹配的内容

### 设置文件的访问权限

有两种设置权限的办法:

- 调用Context.grantUriPermission(package, uri, modeFlags)。这样设置的权限只有在手动调用Context.revokeUriPermission(uri, modeFlags)或系统重启后才会失效。
- 调用Intent.setFlags()来设置权限。权限失效的时机:接收Intent的Activity所在的stack销毁时。

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/11/android-7-uses-fileprovider-to-solve-fileuriexposedexception-exceptions-caused-by-file-uri/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Android 7.0使用FileProvider解决file:// URI引起的FileUriExposedException异常
现象描述 Android 7.0以前的版本 Uri photoUri = Uri.fromFile(tempFile); Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); cameraInten……
<<上一篇
下一篇>>
文章目录
关闭
目 录