按照我们之前的做法(以下载安装为例):
/*
* 安装apk
*/
public static void installApk(Context context, String fileName) {
File apkfile =
new File(Environment.getExternalStorageDirectory().getAbsolutePath(), fileName);
if (!apkfile.exists()) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");
context.startActivity(intent);
}
但这种做法在Android7.0系统上运行会发生异常:
Caused by: android.os.FileUriExposedException:
file:///storage/emulated/0/Download/xxx.apk exposed beyond app through Intent.getData()
这是因为在Android7.0系统上,Android 框架强制执行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用<b>下载安装</b>、<b>系统相机拍照</b>,<b>裁切照片</b>;
应对策略:
若要在应用间共享文件,可以发送 content:// URI类型的Uri,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider类。
使用FileProvider的大致步骤如下(以下载安装为例):
第一步:
在AndroidManifest.xml清单文件中注册provider。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="包名.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
注意:
- exported:要求必须为false,为true则会报安全异常。
- grantUriPermissions:true,表示授予 URI 临时访问权限。
- authorities 组件标识,一般都以包名开头,避免和其它应用发生冲突。
第二步:指定共享的目录
上面配置文件中 android:resource="@xml/file_paths" 指的是当前组件引用 res/xml/file_paths.xml 这个文件。我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="apk_download" path="" />
</paths>
</resources>
上述代码中path="",是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了,如果你将path设为path="download",
那么它代表着根目录下的download目录(eg:/storage/emulated/0/download),如果你向其它应用分享download目录范围之外的文件是不行的。
第三步:使用FileProvider,并考虑兼容Android7.0以下版本
/**
* 安装apk
*/
public static void installApk(Context context, String fileName) {
File apkfile =
new File(Environment.getExternalStorageDirectory().getAbsolutePath(), fileName);
if (!apkfile.exists()) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
Uri apkUri =
FileProvider.getUriForFile(context, "包名.fileprovider", apkfile);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");
}
context.startActivity(intent);
}