美文网首页学习之鸿蒙&Android
应用安装(一) - 三方应用发起apk系统安装

应用安装(一) - 三方应用发起apk系统安装

作者: Stan_Z | 来源:发表于2021-05-06 22:03 被阅读0次

    最近花时间梳理下常规的系统安装apk流程,主要分三大部分:三方应用发起apk安装、PackageInstaller中转apk安装,PakcageManagerService实现apk安装。那么这篇先从三方应用发起apk安装开始。

    一、代码安装apk方案

    < android 7.0

    private void startInstall() {
        Intent intent = new Intent(Intent.ACTION_VIEW);
       intent.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android.package-archive");
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       mAct.startActivity(intent);
    }
    

    = android 7.0
    私有数据保护(使用file scheme会报如下类型错误)

    2021-04-23 17:46:14.605 4133-4133/com.stan.installtest E/AndroidRuntime: FATAL EXCEPTION: main
       Process: com.stan.installtest, PID: 4133
       android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com.stan.installtest/cache/sina_sports.apk exposed beyond app through Intent.getData()
           at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
           at android.net.Uri.checkFileUriExposed(Uri.java:2346)
           at android.content.Intent.prepareToLeaveProcess(Intent.java:8933)
           at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
           at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
    

    官方说明:
    https://developer.android.google.cn/reference/android/os/FileUriExposedException.html
    从Android 7.0开始,不允许app把scheme为file的uri暴露给其他app,该报错即为对此的限制。scheme需要从file切换为content,通过ContentProvider来暴露私有数据给其他程序:

    private void startInstallN() {
        Intent intent = new Intent(Intent.ACTION_VIEW);
       intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
       Uri apkUri = FileProvider.getUriForFile(mAct, Constants.AUTHORITY, new File(filePath));
       intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
       mAct.startActivity(intent);
    }
    

    AndroidMainfest.xml

    <provider
       android:name="androidx.core.content.FileProvider"
       android:authorities="com.stan.installtest.fileprovider"
       android:exported="false"
       android:grantUriPermissions="true">
       <!-- 元数据 -->
       <meta-data
           android:name="android.support.FILE_PROVIDER_PATHS"
           android:resource="@xml/file_paths" />
    </provider>
    

    res/xml/file_paths.xml
    根据实际情况暴露对应apk获取路径

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
       <!--根节点/-->
       <root-path name="root" path="." />
    
       <!--相当于Context.getFilesDir()-->
       <files-path name="files" path="." />
    
       <!--相当于Context. getCacheDir()-->
       <cache-path name="cache" path="." />
    
       <!--相当于Environment.getExternalStorageDirectory()-->
       <external-path name="external" path="." />
    
       <!--相当于Context.getExternalFilesDir(String)-->
       <external-files-path name="external_files" path="." />
    
       <!--相当于Context.getExternalCacheDir()-->
       <external-cache-path name="external_cache" path="." />
    
       <!--相当于Context.getExternalMediaDirs()-->
       <external-media-path name="name" path="." />
    </paths>
    

    >= android 8.0
    安装未知来源动态手动授权

    private void startInstallO() {
        boolean isGranted = mAct.getPackageManager().canRequestPackageInstalls();
    
       if (isGranted) {
            startInstallN();
           return;
       }
    
        new AlertDialog.Builder(mAct)
                .setCancelable(false)
                .setTitle("安装应用需要打开未知来源权限,请去设置中开启权限")
                .setPositiveButton("确定", (d, w) -> {
                    Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
                   mAct.startActivityForResult(intent, UNKNOWN_CODE);
               })
                .show();
    }
    

    二、android.os.FileUriExposedException限制研究

    Android不再允许在app中把file://Uri暴露给其他app,包括但不局限于通过Intent或ClipData 等方法。
    原因在于使用file://Uri会有一些风险,比如:

    • 文件是私有的,接收file://Uri的app无法访问该文件。
    • 在Android6.0之后引入运行时权限,如果接收file://Uri的app没有申请READ_EXTERNAL_STORAGE权限,在读取文件时会引发崩溃。
      因此,google提供了FileProvider,使用它可以生成content://Uri来替代file://Uri

    这里简单研究下限制策略,通过限制报错来反推:

    2021-04-23 17:46:14.605 4133-4133/com.stan.installtest E/AndroidRuntime: FATAL EXCEPTION: main
       Process: com.stan.installtest, PID: 4133
       android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com.stan.installtest/cache/sina_sports.apk exposed beyond app through Intent.getData()
           at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
           at android.net.Uri.checkFileUriExposed(Uri.java:2346)
           at android.content.Intent.prepareToLeaveProcess(Intent.java:8933)
           at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
           at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
    

    从报错信息获取异常抛出的调用栈,得到核心检测方法:
    frameworks/base/core/java/android/net/Uri.java

    public void checkFileUriExposed(String location) {
        if ("file".equals(getScheme())
                && (getPath() != null) && !getPath().startsWith("/system/")) {
            StrictMode.onFileUriExposed(this, location);
       }
    }
    

    frameworks/base/core/java/android/os/StrictMode.java

    /** @hide */
    public static void onFileUriExposed(Uri uri, String location) {
        final String message = uri + " exposed beyond app through " + location;
       if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
            throw new FileUriExposedException(message);//异常抛出点
       } else {
            onVmPolicyViolation(new FileUriExposedViolation(message));
       }
    }
    

    简单测试发现,这里有两个方式可以绕过file uri检查:
    1)反射调用StrictMode的disableDeathOnFileUriExposure方法,禁用运行时检查

        public static boolean disableDeathOnFileUriExposure() {
            try {
                Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
                m.invoke(null);
            } catch (Exception e) {
                return false;
            }
            return true;
        }
    

    2)Uri的checkFileUriExposed 中有个判断条件是!getPath().startsWith("/system/“) 进入检查
    那么我可以构造以/system/开头的路径来绕过:
    String fpath = "/system/.." + filePath;
    intent.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android.package-archive”);

    至于说file和content的选择,和应用场景这就不铺开分析了。

    相关文章

      网友评论

        本文标题:应用安装(一) - 三方应用发起apk系统安装

        本文链接:https://www.haomeiwen.com/subject/zzhjdltx.html