目录
Android7.0安装apk导致的FileUriExposedException异常
问题描述
解决方案
Android8.0安装apk无法跳转到正常的APP安装页面
问题描述
解决方案
安装apk时弹出“选择打开方式”让用户选择而不是直接跳转到APP安装界面
问题描述
解决方案
安装apk代码示例
最近在做一款APP,做自动更新的时候,安装apk遇到了一些问题:
FileUriExposedException异常;
无法跳转到APP安装页面,无法进行版本更新升级;
在下载完成,进行安装的时候,总是弹出“请选择以下打开方式”让用户选择一个打开方式进行安装,一旦选择不对则无法安装。
下面我们逐一分析解决。
在Android7.0中为了提高私有文件的安全性,Google做了私有目录限制访问,面向Android-N或者更高版本的私有目录被限制访问,有点类似于IOS的沙盒机制,此设置可防止私有文件的元数据泄漏。禁止像你的应用外公开file://uri,如果某一项包含file://uri类型的Intent被外部执行的时候,比如说安装App(从file://uri获取apk文件进行安装)、拍照(向file://uri存取照片)就会导致安全异常FileUriExposedException,下面是我的错误代码以及报错日志:
安装App的代码
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + apkfile.toString()),
"application/vnd.android.package-archive");
mContext.startActivity(intent);
方案一,使用严格模式(在Application中添加以下代码)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
}
方案二,使用FileProvider
首先在配置清单文件(AndroidManifest.xm)中测注册一个provider
注意:
然后资源目录(res)下创建xml目录,并在xml目录下创建一个xml文件,名字要跟AndroidMaifest.xml文件中注册的provider引用的resource保持一致。
创建file_paths.xml文件:
表示路径为:Environment.getExternalStorageDirectory()+"Android/data/com.houbin.test/",起名字为files_root。
最后要在代码中做Android7.0兼容,使用FileProvider代替file://url的Uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri contentUri = FileProvider.getUriForFile(mContext,BuildConfig.APPLICATION_ID+ ".fileprovider", apkfile);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
mContext.startActivity(intent);
}
其中BuildConfig.APPLICATION_ID+".fileprovider",表示“APP包名.fileprovider”,即前面AndroidManifest.xml注册的provider的authorities节点的值com.houbin.test.fileprovider。
注意,在FileProvider.getUriForFile()方法的源码已经明确指出,需要Intent设置flag添加权限FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION权限,如果这里缺少intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);这句代码时,还是会抛出FileUriExposedException异常的。
当用户要从除了官方应用商店之外的来源安装App时(安装位置来源的APP):
Android8.0(Android-O)之前:可以被安装,或者需要打开系统设置当中“安装未知应用”权限,或者会有弹窗给用户一个提示。;Android8.0之后,Google进一步加强了权限管理,“安装未知应用”权限的永久开关被移除掉,每次当用户安装位置来源的App时,都需要单独授权并且对软件权限进行手动确认,这样的设计避免了被引诱安装带来的危害。当我们在Android8.0上安装位置来源Apk时,如果不进行设置或者代码控制,是不会跳转到系统App安装界面,就会导致App无法安装。
方案一,用户自己设置
例如在华为8.0系统手机上,用户可以通过打开 “设置->安全和隐私->更多安全设置->安装未知应用”找到要更新的应用,然后点击打开操作界面,设置“安装未知应用”状态为“允许”即可,如下图所示:
是不是很不方便?如果我们的软件以这样的方式给用户升级更新,那就体验太差了。请看另一种解决办法。
方案二,代码兼容Android8.0
首先在AndroidManifest.xml清单文件中添加安装未知来源的权限
然后在代码中兼容Android8.0
if (android.os.Build.VERSION.SDK_INT >= 26) {
boolean hasInstallPermission = mContext.getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
//请求安装未知应用来源的权限
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, 6666);
}
}
这样在系统安装App页面的时候,会弹出选择框,让用户手动选择是否同意安装位置来源的应用,这个是无法避免的。如果到这一步,用户还要选择“禁止”,那在下实在没办法了~ ~ ~
当Android系统版本比较高的时候,在有些机型上,会出现这样的情况,每次安装下载好的APK文件时候,不会直接跳转到系统的安装界面,而是先弹出一个“打开方式的弹窗”让用户选择打开,如果选择的是打包安装程序之类的应用来打开APK,那么会成功跳转到系统安装Apk界面,否则不会安装成功。
遇到这种情况,请检查一下安装apk的代码,是不是缺少打开相应Activity的Action?为了保险起见,在写安装apk代码的时候,要有如下代码,否则有可能就会出现上述问题
intent.setAction(Intent.ACTION_VIEW);
/**
* 安装APK文件
*/
private void installApk() {
File apkfile = new File(mSavePath, apkName);
if (!apkfile.exists()) {
return;
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri contentUri = FileProvider.getUriForFile(mContext, BuildConfig.APPLICATION_ID + ".fileprovider", apkfile);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
//兼容8.0
if (android.os.Build.VERSION.SDK_INT >= 26) {
boolean hasInstallPermission = mContext.getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
//请求安装未知应用来源的权限
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, 6666);
}
}
} else {
// 通过Intent安装APK文件
intent.setDataAndType(Uri.parse("file://" + apkfile.toString()),
"application/vnd.android.package-archive");
}
if (mContext.getPackageManager().queryIntentActivities(intent, 0).size() > 0) {
mContext.startActivity(intent);
}
}
个人总结的一点小东西,欢迎各位大神批评指正