在Android7.0的系统上调用系统相机拍照或者进相册选择图片时,会报如下错误: android.os.FileUriExposedException: ******** exposed beyond app through Intent.getData()

产生原因

其实不仅是调用相机和相册,只要是访问文件,都会出现这个错误,其原因是Android 7.0 做了一些系统权限更改,为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问,此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。而此权限更改有多重副作用,其中之一就是当传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。在应用间共享文件 对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。要在应用间共享文件,应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。原文在这里Android 7.0 行为变更。

解决办法

首先在AndroidManifest中配置:

package="com.example.myapp">
...>
android:name="android.support.v4.content.FileProvider"
android:authorities="app包名.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
...

也就是在application内加了一个provider,其中,name是固定的,android:authorities是你的应用报名+“.fileprovider”,其实这里不一定要写fileprovider,可以随便写,只是要与后面FileProvider.getUriForFile()这个方法中的第二个参数authority对应起来即可。android:grantUriPermissions固定true,表示uri访问授权,android:exported固定的false,android:resource表示我们app要共享文件的路径的资源文件。

接着就要在res目录下新建一个xml文件夹,然后新建文件filepaths.xml,其内容为:

path表示要共享文件的路径,“.”表示所有路径,name是开发者自己起的,容易识别就好。

最后在代码中修改,在需要访问文件获取其Uri时根据SDK_INT判断系统版本是否高于Android N:

File mTempFile = new File(path);
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", mTempFile);
intent.setDataAndType(contentUri, "image/*");
} else {
intent.setDataAndType(Uri.fromFile(mTempFile), "image/*");
}

getUriForFile()的第二个参数getPackageName() + ".fileprovider"也就是第一步在AndroidManifest中provider中的属性android:authorities的值,只要前后两个fileprovider对应上,这个String可以随便写,但是包名必须是你app的包名,但是如果你的app中有import module,在别的module中用这种方法的话,写包名时仍然需要写你的app的包名,而不是那个module的AndroidManifest中配置的packagename。

如果出现下面这个错误,解决办法同上。

Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData