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}
- 匹配和替换:遍历
### 设置文件的访问权限
有两种设置权限的办法:
- 调用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全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论