调用系统相机时,系统会抛出FileUriExposedException
这个错误.具体堆栈信息如下:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: zzidc.com.education_system, PID: 13382
android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/zzidc.com.education_system/files/Pictures/camera1548744838682.jpg exposed beyond app through ClipData.Item.getUri()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1975)
at android.net.Uri.checkFileUriExposed(Uri.java:2363)
at android.content.ClipData.prepareToLeaveProcess(ClipData.java:941)
at android.content.Intent.prepareToLeaveProcess(Intent.java:9944)
at android.content.Intent.prepareToLeaveProcess(Intent.java:9929)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1622)
at android.app.Activity.startActivityForResult(Activity.java:4751)
at android.app.Activity.startActivityForResult(Activity.java:4691)
at zzidc.com.education_system.activity.ImageToVideo.openCamera(ImageToVideo.java:78)
at zzidc.com.education_system.activity.ImageToVideo.onViewClicked(ImageToVideo.java:61)
at zzidc.com.education_system.activity.ImageToVideo_ViewBinding$2.doClick(ImageToVideo_ViewBinding.java:49)
at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java:22)
at android.view.View.performClick(View.java:6291)
at android.view.View$PerformClick.run(View.java:24931)
at android.os.Handler.handleCallback(Handler.java:808)
at android.os.Handler.dispatchMessage(Handler.java:101)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7425)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
7.0之前
Uri pothoUri = Uri.fromFile(getSavePhotoPath());
private File getSavePhotoPath() {
String photoFile = getExternalFilesDir(Environment.DIRECTORY_PICTURES)+ "/camera" + System.currentTimeMillis() + ".jpg";
File file = new File(photoFile);
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
return file;
}
7.0之后
如果继续这样使用就会报 android.os.FileUriExposedException错误,Android不再允许在app中把file://Uri暴露给其他app,包括但不局限于通过Intent或ClipData 等方法。
原因在于使用file://Uri会有一些风险,比如:
- 文件是私有的,接收
file://Uri
的app无法访问该文件。 - 在Android6.0之后引入运行时权限,如果接收file://Uri的app没有申请
READ_EXTERNAL_STORAGE
权限,在读取文件时会引发崩溃。
因此,google提供了FileProvider
,使用它可以生成content://Uri
来替代file://Uri
。
其中education_system 可自己随意,但保证唯一。需要与AndroidManifest.xml
中的provider
authorities保持一样
Uri pothoUri = FileProvider.getUriForFile(this,"education_system",getSavePhotoPath());
在AndroidManifest.xml添加 provider
<provider
android:authorities="education_system"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
- android:authorities:用来标识provider
- android:exported : 是否支持其它应用调用当前组件。 默认为false
- android:grantUriPermissions : 用来控制共享文件的访问权限,也可以在java代码中设置。
需要在res下建立 xml/provider_path.xml
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
<paths>中可以定义以下节点:
子节点 | 对应路径 |
---|---|
| Context.getFilesDir() |
| Context.getCacheDir() |
| Context.getExternalCacheDir() |
| Context.getExternalFilesDir(null) |
| Environment.getExternalStorageDirectory() |
file://
到content://
的转换规则:
- 替换前缀:把
file://
替换成content://${android:authorities}
。 - 匹配和替换
- 遍历<paths>的子节点,找到最大能匹配上文件路径前缀的那个子节点。
- 用path的值替换掉文件路径里所匹配的内容。
- 文件路径剩余的部分保持不变。
转换示意图