• Android 如何通过代码安装 APK?


    2019-10-21

    关键字:Java代码安装程序


    在 APK 开发中,通过 Java 代码来打开系统的安装程序以安装 APK 并不是什么难事,一般的 Android 系统都有开放这一功能。

    但随着 Android 系统版本的迭代,其对于权限的把控越来越严格,或者说是变得越来越注重安全性。这就导致了以前可以通过很简单的几行代码就能实现的功能,现在要复杂很多。

    对于通过代码打开系统安装程序这一功能的限制,其分水岭在 Android7.0,即 Android N 上。通常在 Android N 以上的系统使用一种做法,以下则使用另一种做法。

    这里简述一下这两种做法。

    1、传统的通过代码安装APK的方式

    传统的方式就是简单,寥寥几行代码就可以实现需求:

    File apk = new File(...);
    Uri uri = Uri.fromFile(apk);
    Intent intent = new Intent();
    intent.setClassName("com.android.packageinstaller", "com.android.packageinstaller.PackageInstallerActivity");
    intent.setData(uri);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

    这种方法简单粗暴且实用,只要知晓要安装的 APK 的位置,并拥有访问权限即可。

    但现在市面上主流的 Android 手机系统版本都已经要高于 7.0 了,这一方法几乎已经没有用了。

    2、高版本系统上的通过代码安装APK的方式

    直接上Java代码:

    File apk = new File(...);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    Uri uri = FileProvider.getUriForFile(this, "com.apk.demo.fileprovider", apk);
    intent.setDataAndType(uri, "application/vnd.android.package-archive");
    
    startActivity(intent);

    上面这段代码看起来似乎和传统的方式并没有太大的区别是吗?

    确实是,但它真正的区别并没有在 Java 代码上体现出来。

    在高版本系统中,APK 已经不能直接访问其它 APK 的私有数据了。

    什么是APK的私有数据?

    APK在安装过程中于 data 目录下创建的专属目录自然是其私有数据无疑。另外,只要是在应用程序中封装的 File 对象,不管这个文件本身是不是由该程序创建的,那这个文件都属于该程序的“私有数据”。举个例子来说,假设我们将手机连接到电脑,通过 adb push 的方式往 sdcard 目录下推了一个 APK 文件进去。然后我们自行编写了一段代码,将这个 sdcard 中的安装包传到系统的 PackageInstaller 中去安装,都会报安全错误,因为这个位于 sdcard 目录下文件对我们这段代码来说是“私有数据”,不允许直接暴露给 PackageInstaller。

    我知道这有点“蛮横”,但系统这么做也确实是为了数据的安全性着想,无可厚非,毕竟是有解决的办法的。

    下面就来看看在高版本系统中暴露“私有数据”给其它程序的方法。

    在高版本中,Android7.0 及以上,开放(暴露)私有数据的唯一方式是通过 ContentProvider 来实现。

    具体的步骤大致如下:

    1、配置 AndroidManifest.xml 中的 ContentProvider 信息;

    2、配置要开放的 paths 信息;

    3、在 Java 代码中通过 FileProvider 封装文件信息。

    1、AndroidManifest.xml 配置

    前面说过,高版本系统中其实就是将以前的直接开放变成通过 ContentProvider 来间接开放。因此我们需要在 AndroidManifest.xml 中添加一个 provider 标签,示例如下:

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.your.app.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

    android:name 属性填写的是 FileProvider 类的完整名称。这个类可以填写两个值,一个是位于 support(android.support.v4.content.FileProvider) 包下的,另一个是位于 androidx(androidx.core.content.FileProvider) 包下的。这两种都可以填写,本质上没有区别。但是要根据实际情况来决定用哪个,即要看你的工程引的是 androidx 支援包还是 support 支援包。关于 support 与 androidx 的关系本文就不再赘述了。

    android:authorities 属性就是和普通的 ContentProvider 一样的用于访问文件资源的 uri 标签头。值内容根据实际需要来填写即可。

    android:exported 与 android:grantUriPermissions 两个属性的值照着填就好。它们的含义在这里不太重要,大致是指允许其它应用单次使用自己的 Provider 资源。

    meta-data 标签中的内容需要关注的是 android:resource 属性中的内容。这个属性的值引向一个自行配置的 xml 文件,这份 xml 文件记载的是设备中的路径信息,简单理解就是你想开放哪些目录中的文件资源给第三方使用的意思。关于这个 xml 的配置请看第 2 步的记载。

    2、paths 配置

    通常的做法是在工程 res 目录下新建一个 xml 目录,并在该 xml 目录下新建一个 xml 文件。文件的名称必须与第 1 步中 @xml/ 属性值中配置的一致。

    根据第 1 步中的示例代码,我们需要新建一个 file_paths.xml 文件。该文件的内容如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
    
        <external-path
            name="p1"
            path="/data/dir1/" />
    
        <external-path
            name="p2"
            path="/" />
    
        <external-files-path
            name="p3"
            path="/data/dir2" />
    
        <external-cache-path
            name="p4"
            path="/data/dir3" />
    
        <cache-path
            name="p5"
            path="/data" />
    
        <files-path
            name="p6"
            path="/ff" />
    
    </paths>

    在解释上面的配置信息之前,我们先来看看一个 APK 在运行过程中可以使用的存储位置,或者说常用的存储路径。它们通常有以下五种:

    1、程序安装目录下的 cache 目录,即 /data 目录对应包名下的 cache 目录;

    2、程序安装目录下的 files 目录,同样是 /data 目录对应包名下的 files 目录;

    3、sdcard 目录;

    4、sdcard 目录下对应包名的专属目录下的 cache 目录;

    5、sdcard 目录下对应包名的专属目录下的 files 目录;

    上述第 3、第 4 、第 5 理应归为同一种。但我们理解成 Android 为了方便我们的使用特意封装出了后面两种也可以。

    在了解了一个 APK 在设备上可以使用的几种存储位置后再来理解上面 xml 中记载的配置信息就很好理解了。上面示例 xml 配置信息中共有 6 个配置项信息,其中前面两项是同价的,因此其实就相当于只有五种配置信息。

    我们首先假设我们的 APK 的包名为: com.apk.demo

    其中,xml 中第 1 、第 2 个配置项信息对应于 APK 存储位置的第 3 条,即 sdcard 目录。两个配置项信息分别代表着不同子路径位置。其对应的文件绝对路径为 /storage/emulated/0/data/dir1 或者 /sdcard/data/dir1

    xml 中第 3 个配置项信息对应于 APK 存储位置的第 5 条,即 sdcard 下对应包名的 files 目录。其对应的文件绝对路径为 /sdcard/Android/data/com.apk.demo/files/data/dir2

    xml 中第 4 个配置项信息对应于 APK 存储位置的第 4 条,即 sdcard 下对应包名的 cache 目录。其对应的文件绝对路径为 /sdcard/Android/data/com.apk.demo/cache/data/dir3

    xml 中第 5 个配置项信息对应于 APK 存储位置的第 1 条,即 /data 下应用包名下的 cache 目录。其对应的文件绝对路径为 /data/user/0/com.apk.demo/cache/data 或者 /data/data/com.apk.demo/cache/data

    xml 中最后一个配置项信息对应于 APK 存储位置的第 2 条,即 /data 下应用包名下的 files 目录。其对应的文件绝对路径为 /data/user/0/com.apk.demo/files/ff 或者 /data/data/com.apk.demo/files/ff

    简单来说,就是将你要开放出去的路径的类型选好,然后填上该类型下的相对路径即可。

    我们以第 1 个标签再详细说说:

    这表示我们想开放 sdcard 目录,然后在 sdcard 目录下的子路径是 /data/dir1,组合成绝对路径就是 /sdcard/data/dir1 。至于 name 标签则是用于 ContentProvider 标识使用的,一般来讲按需设计成不同的值就可以了。

    哦对了,还是以上图为例,如果你刚好要访问 sdcard 根目录,那么在 path 属性下直接填一个 '.' 值或者 '/' 值即可,就如上面 xml 示例中的第 2 个配置项那样。

    3、Java 代码配置

    Java 代码的配置就没什么特别的了,直接以章节首部的代码来用就可以了。关键的代码其实只有一行:

    Uri uri = FileProvider.getUriForFile(this, "com.apk.demo.fileprovider", apk);

    通常我们都会兼顾 Android 高低版本的系统,因此会使用如下所示的“混合型”代码:

    File apk = new File(...);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri uri = FileProvider.getUriForFile(this, "com.apk.demo.fileprovider", apk);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
    }else{
        intent.setDataAndType(Uri.fromFile(apk),"application/vnd.android.package-archive");
    }
    try {
        startActivity(intent);
    }catch(Exception e){
        e.printStackTrace();
    }

  • 相关阅读:
    redis缓存和mysql数据库同步
    msf生成shellcode
    转 如何用mt7620方案的rt2860v2驱动实现wifi探针功能,网上能搜到一些方法,但是讲的好模糊?
    解决 “不支持尝试执行的操作”错误
    解决win7资源监视器不能开启
    mp3文件 ID3v2 帧标识的含义
    LoadImage函数问题
    AutoCAD ObjectARX(VC)开发基础与实例教程2014版光盘镜像
    Python计算文件MD5值
    objectARX 获取指定图层上所有实体ID
  • 原文地址:https://www.cnblogs.com/chorm590/p/11696547.html
Copyright © 2020-2023  润新知