Android 通过代码安装 APK的方法详解

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

  • 但随着 Android系统版本的迭代,其对于权限的把控越来越严格,或者说是变得越来越注重安全性。这就导致了以前可以通过很简单的几行代码就能实现的功能,现在要复杂很多。
  • 对于通过代码打开系统安装程序这一功能的限制,其分水岭在 Android7.0,即 Android N 上。通常在 Android N以上的系统使用一种做法,以下则使用另一种做法。
  • 传统的通过代码安装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 了,这一方法几乎已经没有用了

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

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

说到权限问题,在Android版本不断提高的趋势下,系统得安全性也越来越高,很多权限不只是在清单文件里面注册那么简单,内存卡得读写权限属于危险权限,需要我们使用代码动态添加,这里我使用了RxPermiision框架,遇到9.0或者更高版本的系统时获取权限的方法可能会不同。

private void rxPermission() {
    RxPermissions rxPermissions = new RxPermissions(this);
    rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe(new Consumer<Boolean>() {
      @Override
      public void accept(Boolean granted) throws Exception {
        if (granted) {
          //权限允许
          //在这里可以添加自己的操作
        } else {
          // 权限被拒绝
        }
      }
    });
  }

Android更多权限得查询:https://www.jianshu.com/p/24f79a70025b

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

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

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

什么是APK的私有数据?

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

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

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

具体的步骤大致如下:

  • 配置 AndroidManifest.xml 中的 ContentProvider 信息;
  • 配置要开放的 paths 信息;
  • 在 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这个属性表示的是:其他app能否访问这个provider
  • android:grantUriPermissions 这个属性用于给内容提供器的数据子集授权
  • 如果内容提供器的grantUriPermissions属性被设置为true,那么权限能够被授予内容提供器范围内的任何数据。但是,如果grantUriPermission属性被设置为false,那么权限就只能授予这个元素所指定的数据子集。一个内容提供器能够包含任意多个元素。每个都只能指定一个路径(三个可能属性中的一个)。
  • meta-data 标签中的内容需要关注的是 android:resource 属性中的内容。这个属性的值引向一个自行配置的 xml 文件,这份 xml 文件记载的是设备中的路径信息,简单理解就是你想开放哪些目录中的文件资源给第三方使用的意思。关于这个 xml 的配置请看第 2 步的记载。

2、paths 配置

  • 通常的做法是在工程 res 目录下新建一个 xml 目录,并在该 xml 目录下新建一个 xml 文件。文件的名称必须与第 1 步中 @xml/ 属性值中配置的一致。
  • 根据第 1 步中的示例代码,我们需要新建一个 file_paths.xml 文件。这里我的apk是保留在程序的file文件加下得,该文件的内容如下所示:
<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path path="apk/" name="apk" />
</paths>

其他路径的的配置方式请参考:https://editor.csdn.net/md?articleId=106670247

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

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path path="apk/" name="apk" />
</paths>

这表示我们想开放 程序内存里面的files目录,然后在 files 目录下的子路径是 /apk,组合成绝对路径就是 /data/con.xxx.xxx/files/apk 。至于 name 标签则是用于 ContentProvider 标识使用的,一般来讲按需要设置成不同的值就可以了,这里我有一个子目录。

3、Java 代码配置

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

Uri uri = FileProvider.getUriForFile(context, authority, file);

这里的三个参数分别为:

  • context:这里表示需要传一个上下文过来
  • authority:可以通代码在AndroidManifest.xml里面获得
  • file:是你需要的安装的文件
String authority = new StringBuilder(packageName).append(".provider").toString();
//这里的strFile文件的路径+名称;例如:/data/file/apk/xxx.apk
File f=new File(strFile);
Uri uri = FileProvider.getUriForFile(context, authority, file);

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

public void install(){
	try{//这里有文件流的读写,需要处理一下异常
		Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
			//如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24
		  String packageName = context.getApplicationContext().getPackageName();
		  String authority = new StringBuilder(packageName).append(".provider").toString();
		  uri = FileProvider.getUriForFile(context, authority, file);
		  intent.setDataAndType(uri, "application/vnd.android.package-archive");
		 } else{
		  uri = Uri.fromFile(file);
		  intent.setDataAndType(uri, "application/vnd.android.package-archive");
		}
		context.startActivity(intent);
	}catch (Exception e) {
      e.printStackTrace();
  }
}

总结

到此这篇关于Android 通过代码安装 APK的方法详解的文章就介绍到这了,更多相关android 代码安装apk内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android无需root实现apk的静默安装

    Android的静默安装似乎是一个很有趣很诱人的东西,但是,用普通做法,如果手机没有root权限的话,似乎很难实现静默安装,因为Android并不提供显示的Intent调用,一般是通过以下方式安装apk: Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); startAct

  • Android 8.0不能自动安装APK问题的解决方法(完美适配)

    Android8.0之前 未知应用安装权限默认开启,如下图所示 8.0以下手机未知应用安装权限.png Android8.0之后 未知应用安装权限默认关闭,且权限入口隐藏. 如何开启未知应用安装权限的入口,并设置允许安装? 在清单文件中添加权限 <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> 判断当前是否为8.0以上系统(在这之前也可以对7.0系统进行判断,这里代码没

  • Android安装apk文件并适配Android 7.0详解

    Android安装apk文件并适配Android 7.0详解 首先在AndroidManifest.xml文件,activity同级节点注册provider: <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.file_provider" android:exported="false"

  • Android简单判断某个APK是否已经安装的方法

    本文实例讲述了Android简单判断某个APK是否已经安装的方法.分享给大家供大家参考,具体如下: privateboolean isAppInstalled(String uri){ PackageManager pm = getPackageManager(); boolean installed =false; try{ pm.getPackageInfo(uri,PackageManager.GET_ACTIVITIES); installed =true; }catch(Package

  • Android中获取apk安装包信息的方法

    一.获取安装包信息 复制代码 代码如下: /** * 获取apk包的信息:版本号,名称,图标等 * @param absPath apk包的绝对路径 * @param context  */  public void apkInfo(String absPath,Context context) { PackageManager pm = context.getPackageManager();      PackageInfo pkgInfo = pm.getPackageArchiveInf

  • Android实现用代码简单安装和卸载APK的方法

    本文实例讲述了Android实现用代码简单安装和卸载APK的方法.分享给大家供大家参考,具体如下: public class TestInstallAPK extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); // this.unInstallFi

  • Android 监听apk安装替换卸载广播的实现代码

    首先是要获取应用的安装状态,通过广播的形式以下是和应用程序相关的Broadcast ActionACTION_PACKAGE_ADDED 一个新应用包已经安装在设备上,数据包括包名(最新安装的包程序不能接收到这个广播)ACTION_PACKAGE_REPLACED 一个新版本的应用安装到设备,替换之前已经存在的版本ACTION_PACKAGE_CHANGED 一个已存在的应用程序包已经改变,包括包名ACTION_PACKAGE_REMOVED 一个已存在的应用程序包已经从设备上移除,包括包名(正

  • Android 通过代码安装 APK的方法详解

    在 APK 开发中,通过 Java 代码来打开系统的安装程序以安装 APK 并不是什么难事,一般的 Android 系统都有开放这一功能. 但随着 Android系统版本的迭代,其对于权限的把控越来越严格,或者说是变得越来越注重安全性.这就导致了以前可以通过很简单的几行代码就能实现的功能,现在要复杂很多. 对于通过代码打开系统安装程序这一功能的限制,其分水岭在 Android7.0,即 Android N 上.通常在 Android N以上的系统使用一种做法,以下则使用另一种做法. 传统的通过代

  • Android程序打包为APK的方法详解

    Andriod安装包文件(Android Package),简称APK,后缀名为.apk. 1.生成未签名的安装包 Build -> Build Bundle(s)/APK(s) -> Build APK(s)    会生成一个未签名的apk文件,默认为debug版,可以正常安装使用. 可以 Build -> Select Build Variant -> 选择生成的apk版本(debug.release),再 Build -> Build Bundle(s)/APK(s)

  • Android模拟器中安装apk的方法

    本文讲述了Android模拟器中安装apk的方法.分享给大家供大家参考,具体如下: 第一步: 在Eclipse中启动模拟器. 第二步: 打开doc命名窗口,转到你android sdk安装目录中的platform-tools下, 如我的是 E:/Program Files/andriod_sdk/platform-tools 第三步: 利用adb来对android进行安装操作.建议第三步使用方案一 方案一: 为了方便,我们应将要安装的apk直接拷到platform-tools所在的目录下,然后在

  • Android适配底部虚拟按键的方法详解

    最近项目进行适配的时候发现部分(如华为手机)存在底部虚拟按键的手机会因为虚拟按键的存在导致挡住部分界面,因为需要全屏显示,故调用虚拟按键隐藏方法使之隐藏,然而发现出现如下问题: 手动操作隐藏虚拟按键后出现长白条区域 不自动隐藏 滑出状态栏后虚拟按键也出来,状态栏隐藏后虚拟却不跟着隐藏 在没有虚拟按键的设备上影响了SurfaceView全屏显示图传(原本全屏显示的图传在切出去再进来时变成了小屏显示) 通过google了很多方法并尝试终于解决了这个问题,达到如下效果: 每次进入界面时虚拟按键自动隐藏

  • Android学习之Span的使用方法详解

    目录 Span集合 段落类Span 其他Span 展示效果 小试牛刀 小结 Span集合 段落类Span BulletSpan 为段落开头增加项目符号并支持大小.颜色.弧度 span.append(SpannableString("BulletSpan").also { it.setSpan(BulletSpan(40, Color.RED), 0, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) }) QuoteSpan 为段落开头增加垂直引用线 sp

  • 利用Android实现光影流动特效的方法详解

    目录 前言 MaskFilter 类简介 MaskFilter 的几种效果对比 光影流动 光影流动效果1 光影流动效果2 光影流动效果3 光影流动效果4:光影沿贝塞尔曲线流动 总结 前言 Flutter 的画笔类 Paint 提供了很多图形绘制的配置属性,来供我们绘制更丰富多彩的图形.前面几篇我们介绍了 shader 属性来绘制全屏渐变的聊天气泡背景.渐变流动的边框和毛玻璃效果的背景图片,具体可以参考下面几篇文章. 让你的聊天气泡丰富多彩! 手把手教你实现一个流动的渐变色边框 利用光影变化构建立

  • Python 3.6 性能测试框架Locust安装及使用方法(详解)

    背景 Python3.6 性能测试框架Locust的搭建与使用 基础 python版本:python3.6 开发工具:pycharm Locust的安装与配置 点击"File"→"setting" 点击"setting",进入设置窗口,选择"Project Interpreter" 点击"+" 输入需要"Locust",点击"Install Package" 安装完成

  • mysql 5.6.13 免安装版配置方法详解

    本文给大家记录在上个项目中涉及到免安装版的mysql的配置问题,今天小编把配置方法分享到我们平台供大家学习. 1. 下载mysql Community Server 5.6.13 2. 解压MySQL压缩包 将以下载的MySQL压缩包解压到自定义目录下,我的解压目录是: "D:\Program Files\MySQL\mysql-5.6.13-win32" 将解压目录下默认文件 my-default.ini 拷贝一份,改名 my.ini 复制下面的配置信息到 my.ini 保存 #如果

  • Linux Docker安装wordpress的方法详解教程

    安装mysql服务 下载mysql镜像: docker pull mysql 创建mysql容器并后台运行,指定数据库密码是123456.-e指定环境变量. docker run --name mysql_db -e MYSQL_ROOT_PASSWORD=123456 -d mysql 使用官方的wordpress wordpress镜像daocloud.io: docker pull daocloud.io/daocloud/dao-wordpress:latest 拉取镜像前请先登录: d

  • CentOS 7.0下使用yum安装mysql的方法详解

    CentOS7默认数据库是mariadb,配置等用着不习惯,因此决定改成mysql,但是CentOS7的yum源中默认好像是没有mysql的.为了解决这个问题,我们要先下载mysql的repo源. 1.下载mysql的repo源 $ wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm 2.安装mysql-community-release-el7-5.noarch.rpm包 $ sudo rpm -ivh mys

随机推荐