问题描述:
android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
报错场景:
最近在做应用内安装apk ,在7.0系统的Android手机上运行会报FileUriExposedException ,报错代码如下:
val intent = Intent()
intent.action = "android.intent.action.VIEW"
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addCategory("android.intent.category.DEFAULT")
startActivity(intent)
产生原因:
Google在Android 7.0 系统中进行的行为变更,对于面向 Android 7.0 的应用,禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。
简单来说,就是7.0以上的应用,禁止向外部应用共享文件。
解决应用间共享文件的办法:
Google官方解释:要想在应用间共享文件,您应该在发送 content:// URI时授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。
网上有的方案是直接使用官方提供的FileProvider( android:name=”android.support.v4.content.FileProvider”),但是会有一个问题,如果我们导入的三方依赖已经使用FileProvider,这时会出现FileProvider冲突,以致于项目编译失败。
正确的解决办法是我们创建自己的GenericFileProvider 类来继承FileProvider,以确保我们的FileProvider不会与项目中导入依赖中声明的FileProvider发生冲突。
解决步骤如下:
- 新建一个类继承FileProvider
public class GenericFileProvider extends FileProvider {}
- 在清单文件中添加FileProvider标签,name设置为自己新建的GenericFileProvider,并且设置一个独一无二的android:authorities属性以避免冲突。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<provider
android:name=".GenericFileProvider"
android:authorities="${applicationId}.my.package.name.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
注意:
authorities:app的包名.fileProvider
grantUriPermissions:必须是true,表示授予 URI 临时访问权限
exported:必须是false
resource:中的@xml/file_paths是我们接下来要添加的文件
- 然后在res / xml文件夹中创建一个provider_paths.xml文件,如果xml文件夹不存在需要创建,该文件的内容如下:它描述我们想要在根文件夹(path =“.”)上共享对外部存储的访问权限,名称为external_files。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
path:需要临时授权访问的路径(.代表所有路径)
name:就是你给这个访问路径起个名字
files-path/>代表的根目录:Context.getFilesDir()
external-path/>代表的根目录:Environment.getExternalStorageDirectory()
cache-path/>代表的根目录:getCacheDir()
- 最后一步就是修改安装apk部分的代码。
val intent = Intent()
intent.action = "android.intent.action.VIEW"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
val contentUri = FileProvider.getUriForFile(this, application.packageName +".my.package.name.provider",file)
intent.setDataAndType(contentUri, "application/vnd.android.package-archive")
}else{
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addCategory("android.intent.category.DEFAULT")
}
startActivity(intent)
到此,问题已经全面解决。
写在最后:
该篇文章只是举出应用内安装apk的例子去解决文件共享的问题,在其他的应用场景同样适用(例如剪裁图片),只需要把URL的获取方式改一下就行了。
Uri photoURI = Uri.fromFile(createImageFile());
改为
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());