android 7.0 因为file://引起的FileUriExposedException异常

本文探讨了Android 7.0及以上版本中FileUriExposedException的问题,详细解释了为何使用file://Uri会引发此异常,并提供了解决方案,包括如何使用FileProvider生成content://Uri来替代。

最近作者又碰到因为android 7.0 引起的兼容问题了。

在7.0以前的版本:

//创建临时图片
File photoOutputFile = SDPath.getFile("temp.jpg", SDPath.PHOTO_FILE_STR);
Uri photoOutputUri = Uri.fromFile(photoOutputFile);

这个file文件直接非常简单的转换成"file://XXX/XXX/XXX"的uri格式

7.0后的版本:

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

android.os.FileUriExposedException:         file:///XXX exposed beyond app through ClipData.Item.getUri()
    at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
    at android.net.Uri.checkFileUriExposed(Uri.java:2346)
    at android.content.ClipData.prepareToLeaveProcess(ClipData.java:832)
    at android.content.Intent.prepareToLeaveProcess(Intent.java:8909)
    ...

原因

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

解决方案

首先在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"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.zhongjh.phone.ui"
···

    <provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.zhongjh.phone.ui.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths" />
   </provider>

</manifest >

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

<paths>中可以定义以下子节点

子节点对应路径例子
files-pathContext.getFilesDir() 
cache-pathContext.getCacheDir() 
external-pathEnvironment.getExternalStorageDirectory()/storage/emulated/0/
external-files-pathContext.getExternalFilesDir(null) 
external-cache-pathContext.getExternalCacheDir() 

 

 

 

 

 

 

 

加入我要替换的目录是    /storage/emulated/0/diary sdcard/photo/
那么配置应该写成

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="diary sdcard/photo"/>
</paths>

然后修改代码

//创建临时图片
File photoOutputFile = SDPath.getFile("temp.jpg", SDPath.PHOTO_FILE_STR);
//Uri photoOutputUri = Uri.fromFile(photoOutputFile);
Uri photoOutputUri = FileProvider.getUriForFile(
                    mContext,
                    mActivity.getPackageName() + ".fileprovider",
                    photoOutputFile);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, photoOutputUri);

我所碰到的异常处理

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"一样


 

链接:https://www.jianshu.com/p/55b817530fa3
來源:简书
 

Android 7.0 及以上系统中,直接使用 `file://` 格式的 URI 会触发 `FileUriExposedException` 错误,因为这种方式存在安全风险。解决该问题可采用 `FileProvider` 来生成内容 URI。以下是解决此问题的具体步骤和示例代码: ### 1. 在 `AndroidManifest.xml` 中配置 `FileProvider` 在 `<application>` 标签内添加 `FileProvider` 的配置,示例如下: ```xml <provider android:name="androidx.core.content.FileProvider" android:authorities="com.yourpackage.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ``` 其中,`android:authorities` 的值一般为“自己的包名 + fileprovider”,这里假设包名为 `com.yourpackage`。 ### 2. 创建 `file_paths.xml` 文件 在 `res/xml` 目录下创建 `file_paths.xml` 文件,内容如下: ```xml <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="my_images" path="." /> </paths> ``` 这里的 `external-path` 表示外部存储目录,`path="."` 表示允许访问整个外部存储。 ### 3. 修改代码以使用 `FileProvider` 在启动 Activity 时,使用 `FileProvider` 生成内容 URI,示例代码如下: ```java import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.FileProvider; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; public class MainActivity extends AppCompatActivity { private static final int REQUEST_CODE_TAKE_PHOTO = 1; private String mPhotoName; private File mPhoto; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { // 生成照片文件名 mPhotoName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg"; // 照片保存路径 File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); mPhoto = new File(storageDir, mPhotoName); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Uri mImageUri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 7.0及以上版本使用FileProvider生成内容URI intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); mImageUri = FileProvider.getUriForFile(this, "com.yourpackage.fileprovider", mPhoto); } else { // 7.0以下版本使用传统的Uri.fromFile方法 mImageUri = Uri.fromFile(mPhoto); } intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri); startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO); } catch (Exception e) { e.printStackTrace(); } } } ``` 在上述代码中,当系统版本为 Android 7.0 及以上时,使用 `FileProvider.getUriForFile` 方法生成内容 URI,并为 Intent 添加 `Intent.FLAG_GRANT_READ_URI_PERMISSION` 标志,以授予其他应用访问该 URI 的权限。 ### 总结 通过以上步骤,将原本的 `file://` 格式的 URI 替换为 `content://` 格式的内容 URI,从而避免 `FileUriExposedException` 错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值