最近遇到了这个问题,在调试和查找资料的时候,发现了很多问题,网上的解决方案也有的有问题,于是自己写一个记下!
首先,出现这个问题的原因:从Android 7.0开始,不再允许在app中把file:// Uri暴露给其他app,否则应用会抛出FileUriExposedException。原因在于,Google认为使用file:// Uri存在一定的风险。比如,文件是私有的,其他app无法访问该文件,或者其他app没有申请READ_EXTERNAL_STORAGE运行时权限。解决方案是,使用FileProvider生成content:// Uri来替代file:// Uri。
另外,并不是所有的手机都会因为这个异常崩溃,比如华为的不会崩溃,而小米6会,其他机型没有测试过。
解决方案:
在manifests.xml清单文件中添加:
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.xxxxx.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path"/>
</provider>
</application>
其中,authorities属性可自定义,是provider的唯一标识,一般用自己项目的包名,以保证唯一性。exported必须设置成 false,否则运行时会报错java.lang.SecurityException: Provider must not be exported 。grantUriPermissions用来控制共享文件的访问权限。
meta-data节点中的android:resource指定了共享文件的路径。此处的file_paths即是该Provider对外提供文件的目录的配置文件,存放在res/xml/下。内容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="rc_external_path"
path="."/>
</paths>
其中根元素<paths>是固定的,内部元素可以是以下节点:
<files-path name="name" path="path" /> 对应getFilesDir()。
<cache-path name="name" path="path" /> 对应getCacheDir()。
<external-path name="name" path="path" /> 对应Environment.getExternalStorageDirectory()。
<external-files-path name="name" path="path" /> 对应getExternalFilesDir()。
<external-cache-path name="name" path="path" /> 对应getExternalCacheDir()。
“.”代表全路径
注意:有些第三方的module(比如融云即时通讯)会有些需要在你项目的主module清单文件中添加这个,这时候就不需要再添加了,否则会编译不通过,然后只需要在已存在的provider定义好的resource文件中添加就可以了。
之后就是如何使用了:
public void installApk(File file) {
if (file.exists()) {
Intent intent = new Intent();
Uri data;
// 判断版本大于等于7.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
//清单文件中配置的authorities
data = FileProvider.getUriForFile(this, "com.snyj.wsd.kangaibang.FileProvider", file);
// 给目标应用一个临时授权
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//重点!!
} else {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//重点!!!
data = Uri.fromFile(file);
}
intent.setDataAndType(data, "application/vnd.android.package-archive");
startActivity(intent);
} else {
Log.e("ruin", file.getName()+"文件不存在!" );
}
}
这里面标重点的一定要注意!版本大于7.0和小于7.0的setFlags是不一样的!!