Android7.0、8.0安装apk以及安装apk弹出“选择打开方式”的解决方案

目录

Android7.0安装apk导致的FileUriExposedException异常

问题描述

解决方案

Android8.0安装apk无法跳转到正常的APP安装页面

问题描述

解决方案

安装apk时弹出“选择打开方式”让用户选择而不是直接跳转到APP安装界面

问题描述

解决方案

安装apk代码示例


最近在做一款APP,做自动更新的时候,安装apk遇到了一些问题:

  • FileUriExposedException异常;

  • 无法跳转到APP安装页面,无法进行版本更新升级;

  • 在下载完成,进行安装的时候,总是弹出“请选择以下打开方式”让用户选择一个打开方式进行安装,一旦选择不对则无法安装。

下面我们逐一分析解决。

Android7.0安装apk导致的FileUriExposedException异常

问题描述

在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);

Android7.0、8.0安装apk以及安装apk弹出“选择打开方式”的解决方案_第1张图片

 

解决方案

方案一,使用严格模式(在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

    
            
    

注意:

  • exported:要求必须为false,为true则会报安全异常。
  • grantUriPermissions:true,表示授予 URI 临时访问权限。
  • authorities 组件标识,按照江湖规矩,都以包名开头,避免和其它应用发生冲突。

然后资源目录(res)下创建xml目录,并在xml目录下创建一个xml文件,名字要跟AndroidMaifest.xml文件中注册的provider引用的resource保持一致。

创建file_paths.xml文件:

Android7.0、8.0安装apk以及安装apk弹出“选择打开方式”的解决方案_第2张图片



    
    

代表的根目录: Environment.getExternalStorageDirectory()

表示路径为: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异常的。

 

Android8.0安装apk无法跳转到正常的APP安装页面

问题描述

当用户要从除了官方应用商店之外的来源安装App时(安装位置来源的APP):

Android8.0(Android-O)之前:可以被安装,或者需要打开系统设置当中“安装未知应用”权限,或者会有弹窗给用户一个提示。;Android8.0之后,Google进一步加强了权限管理,“安装未知应用”权限的永久开关被移除掉,每次当用户安装位置来源的App时,都需要单独授权并且对软件权限进行手动确认,这样的设计避免了被引诱安装带来的危害。当我们在Android8.0上安装位置来源Apk时,如果不进行设置或者代码控制,是不会跳转到系统App安装界面,就会导致App无法安装。

解决方案

方案一,用户自己设置

例如在华为8.0系统手机上,用户可以通过打开 “设置->安全和隐私->更多安全设置->安装未知应用”找到要更新的应用,然后点击打开操作界面,设置“安装未知应用”状态为“允许”即可,如下图所示:

Android7.0、8.0安装apk以及安装apk弹出“选择打开方式”的解决方案_第3张图片          Android7.0、8.0安装apk以及安装apk弹出“选择打开方式”的解决方案_第4张图片

是不是很不方便?如果我们的软件以这样的方式给用户升级更新,那就体验太差了。请看另一种解决办法。

方案二,代码兼容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页面的时候,会弹出选择框,让用户手动选择是否同意安装位置来源的应用,这个是无法避免的。如果到这一步,用户还要选择“禁止”,那在下实在没办法了~ ~ ~

Android7.0、8.0安装apk以及安装apk弹出“选择打开方式”的解决方案_第5张图片

安装apk时弹出“选择打开方式”让用户选择而不是直接跳转到APP安装界面

问题描述

当Android系统版本比较高的时候,在有些机型上,会出现这样的情况,每次安装下载好的APK文件时候,不会直接跳转到系统的安装界面,而是先弹出一个“打开方式的弹窗”让用户选择打开,如果选择的是打包安装程序之类的应用来打开APK,那么会成功跳转到系统安装Apk界面,否则不会安装成功。

解决方案

遇到这种情况,请检查一下安装apk的代码,是不是缺少打开相应Activity的Action?为了保险起见,在写安装apk代码的时候,要有如下代码,否则有可能就会出现上述问题

intent.setAction(Intent.ACTION_VIEW);

安装apk代码示例

   /**
     * 安装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);
        }
    }

个人总结的一点小东西,欢迎各位大神批评指正

你可能感兴趣的:(未分类系列)