文章目录

  • 背景
  • 目标
  • 实现
    • 获取系统应用权限
      • 原理
      • 实现
    • 静默安装
      • 普通升级安装
      • shell命令安装
      • 模拟系统安装程序流程

背景

基于定制平板系统开发应用,能获得安卓源码层的支持。
Android8.1系统。

目标

  • 默认获取所需全部权限,不再弹窗询问授权。
  • 静默升级安装新版本。

实现

获取系统应用权限

通过sharedUserId这个配置,将当前应用进程配置为系统应用进程,可获取所有权限。

原理

通过Shared User id,拥有同一个UserId的多个APK可以配置成运行在同一个进程中,所以默认就是可以互相访问任意数据,同时可以访问其他APK的数据目录下的数据库和文件,就像访问本程序的数据一样。

为了防止其他应用拿到sharedUserId并滥用,Android系统限制了相同的userId情况下,必须要使用同样的签名才能安装在同一设备上。故,如果要设置为系统共享进程,必须要和系统应用签名一致。

系统给应用签名的逻辑:

系统源码中build/target/product/security目录中有四组默认签名供Android.mk在编译APK使用,通过指定LOCAL_CERTIFICATE字段来实现,分别是:

  • testkey:普通APK,默认情况下使用。
  • platform:该APK完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的APK所在进程的UID为system。
  • shared:该APK需要和home/contacts进程共享数据。
  • media:该APK是media/download系统中的一环

普通应用则使用testkey,若要为系统应用,可指定为platform

原理及介绍参考该文章:Android的权限机制之—— “沙箱”机制sharedUserId和签名

实现

参考该文章实现:Android apk 系统签名

1. 前提:
应用签名不能是自定义签名,需要是系统签名。签名文件需要找系统提供厂商提供,包含platform.pk8platform.x509.pemsignapk.jar等几个文件,如果这一步不能提供,那通过配置sharedUserId的方式无望。

2、配置sharedUserId
AndroidManifest.xml中的manifest节点增加:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.moore.systemapk"android:sharedUserId="android.uid.system">
</manifest>

3、签名
方式一:
在系统源码层,将应用Android.mk中的LOCAL_CERTIFICATE配置为platform,即对应原理中的四个默认类型中的system应用类型

方式二:
打好未签名的apk包,通过命令方式,对应用进行签名。
java -jar signapk.jar platform.x509.pem platform.pk8 unsigned.apk signed.apk
把你对apk名和路径,替换unsigned.apksigned.apk为输出路径。

方式三:
根据系统方提供的签名文件,生成jks或者keystone文件,后续可直接在AS中打包签名,无需额外再签名。

  1. 生成shared.priv.pem文件

openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out shared.priv.pem -nocrypt

  1. 生成shared.pk12文件

openssl pkcs12 -export -in platform.x509.pem -inkey shared.priv.pem -out shared.pk12 -name moore

  1. 生成jks或者keystone文件

keytool -importkeystore -deststorepass android -destkeypass android -destkeystore moore.jks -srckeystore shared.pk12 -srcstoretype PKCS12 -srcstorepass android -alias moore

生成后则可配置到gradle中,或者打包时选择签名所在路径

4、问题
在实际操作过程中,使用命令方式签名,很大几率会出现no conscrypt_openjdk_jni in java.library.path:的错误,拿到厂商提供的libconscrypt_openjdk_jni.so,重新签名,报另外一个错误:FATAL ERROR in native method: RegisterNatives failed for 'org/conscrypt/NativeCrypto',尝试换电脑、换系统(windows、mac、linux)都无解,厂商那边电脑执行该命令是正常的,遂放弃,采用生成jks签名的方式执行。

5、安装
因更换了签名,若在原安装了该应用的设备上安装,务必提前卸载,同包名不同签名,显然是安装不成功的。

静默安装

应用实现自动更新功能,有几种种安装方式:

  • 调用系统安装程序,传入apk路径(需授权,允许安装应用)
  • 执行shell命令静默安装apk(需root权限)
  • 使用PackageInstaller代码安装,模拟系统安装程序流程(需为系统应用)
普通升级安装

首先来说,这种方式是会出现安装页面的,并不符合我们的需求,但在安装过程中发现了一些问题,所以还是罗列一下可以拓展参考。

1、方式
下载好升级包apk后,直接使用Intent调起系统安装意图,即可。需注意8.0以上需使用自定义FileProvider,否则无法拿到path路径并且会抛出异常FileUriExposedException

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri fileUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//自定义的FileProvider,需要在清单文件中声明String authority = XUpdate.getContext().getPackageName() + ".updateFileProvider";fileUri = FileProvider.getUriForFile(XUpdate.getContext(), authority, file);
} else {fileUri = Uri.fromFile(file);
}
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
context.startActivity(intent)

2、问题
问题来了,按照普通应用流程,这个方式是没问题的,一切正常,但我们的应用,是设置过**android:sharedUserId="android.uid.system"**的,那就有点麻烦了。

调起安装的时候,报错:解析软件包时出现问题

确认过下载的安装包无问题,通过文件管理、其他应用等都能调起安装,去除systemUid后也能正常安装,那问题就是这个sharedUserId了。

原因:
通过追踪系统安装流程源码程序,发现如下代码:

final int callingAppId = UserHandle.getAppId(callingUid);
if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) {// Exempted authority for cropping user photos in Settings app} else {Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"+ " grant to " + grantUri + "; use startActivityAsCaller() instead");return -1;}
}

ok,破案,因为我们是设置为SystemUid的应用,但我们的provider并不是com.android.settings.files,所以直接被return -1了,而com.android.settings.files这个provider,是在Settings这个系统应用中定义的,也就是说,只有Settings这个SystemUid的应用能够调起普通的安装流程。

解决方案:

  • 我们并不能够将我们自己的android:authorities改成上面可用的com.android.settings.files,因为个是整个系统唯一的一个值,改成一致是无法安装到设备上的。卒。
  • 去除systemUid,能够正常安装,能去除就不会花心思去研究这了。卒。
  • 修改源码!在如上判断中,增加一个或条件,加上我们自己的authorities,但要修改源码,有条件可以使用

该问题详细剖析可参考文章:Android android.uid.system的应用调用安装apk失败

shell命令安装

顾名思义,是直接通过输入命令执行的方式进行安装。众所周知Android内核是linux,所以其原理就是使用root账户直接命令安装。
需要先判断是否有root权限:

public static boolean isDeviceRooted() {String su = "su";String[] locations = {"/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/","/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/"};for (String location : locations) {if (new File(location + su).exists()) {return true;}}return false;
}

如果有权限的话,拼接命令,filePath即为apk路径

String command = "pm install -i " + packageName + " --user 0 " + filePath;

然后通过ShellUtils的execCommand方法执行该命令。

如果有Root权限的话,该方式是可行的,不会有界面,但我们对外放出的设备是不会开放root权限的,所以这条路子只能开发测试的时候用,实际上线操作不了。

模拟系统安装程序流程

介绍前可以先参考这两篇文章,了解系统安装应用的流程和代码逻辑
Android包管理机制(一)PackageInstaller的初始化
Android包管理机制(二)PackageInstaller安装APK

我们要做的,就是分析调起系统安装页面,点击安装之后,逻辑层做了些什么操作,然后用直接在我们应用中去实现这个流程。

大致流程

  1. 定义一个监听回调,接收安装开始、进度、结束的通知
  2. PackageManager获取PackageInstaller对象,获取apk文件File对象
  3. 创建一个安装会话Session,得到会话id
  4. 读取apk File,将文件以流的方式写入session
  5. 创建一个PendingIntent,用于调起PMS安装逻辑
  6. commit这个session,进入PMS安装

PMS安装简介:

  1. 接收到会话commit后,会组装成一个PackageInstallObserverAdapter对象,通过Handler发送该消息交由PMS处理
  2. PackageManagerService的install逻辑和PMSHandlerIMediaContainerService几个类相关
  3. PMSHandler处理绑定消息、新安装消息等逻辑,只有绑定成功才能进行下一步的安装操作
  4. 通过binder绑定服务,对apk进行解析和复制(耗时操作,安卓系统新开一个service进程)
  5. InstallParamsInstallArgs具体执行安装前检查、安装位置确认、copy和move等操作,
  6. 安装成功后,通过PMS分发消息,通知应用所属用户,更新Settings,为新安装的应用程序准备数据。安装失败则删除apk。
  7. 断开binder

参考文章::Android包管理机制(三)PMS处理APK的安装

关键代码

int count;
int sessionId;
byte[] buffer = new byte[65536];
InputStream inputStream;
OutputStream outputStream;
//拿到apkFile,拿到packageInstaller对象
File apkFile = new File(apkFilePath);
long apkFileLength = apkFile.length();
PackageManager pm = context.getPackageManager();
PackageInstaller packageInstaller = pm.getPackageInstaller();
packageInstaller.registerSessionCallback(忽略回调实现);
//创建会话
PackageInstaller.Session session = null;
PackageInstaller.SessionParams sessionParams;
try {sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);sessionId = packageInstaller.createSession(sessionParams);session = packageInstaller.openSession(sessionId);inputStream = new FileInputStream(apkFile);outputStream = session.openWrite(apkName, 0, apkFileLength);//写入while ((count = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, count);float progress = ((float) count / (float) apkFileLength);session.setStagingProgress(progress);}session.fsync(outputStream);inputStream.close();outputStream.flush();outputStream.close();//创建PendingIntentIntent intent = new Intent();PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);//绑定并提交session.commit(pendingIntent.getIntentSender());
} catch (Exception e) {e.printStackTrace();
} finally {if (session != null) {session.abandon();}
}

至此,就能实现静默安装了,但,还有一个问题,正常来说,我们这种系统升级,是需要在升级完成后,重启应用,但是,因为安装过程我们的应用是被Kill掉的,所以,在创建会话时设置的回调,是接收不到安装完成的回调的!!

安装完成重启

思路:
在开始安装的时候,创建一个AlarmManager任务,在10s(视apk大小自己定)后重新打开该应用。

Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
PendingIntent restartIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上mgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, restartIntent);
}

如此,就能实现静默安装,安装完成后重启应用。

Android构建系统级应用静默授权,静默安装相关推荐

  1. android悬浮功能实现,Android实现系统级悬浮按钮

    本文实例为大家分享了Android系统级悬浮按钮的具体代码,供大家参考,具体内容如下 具体的需求 1.就是做一个系统级的悬浮按钮,就像iPhone 桌面的那个悬浮按钮效果一样,能随意拖动,并且手一放开 ...

  2. 打造一个全命令行的Android构建系统

    IDE都是给小白程序员的,大牛级别的程序员一定是命令行控,终端控,你看大牛都是使用vim,emacs 就一切搞定" 这话说的虽然有些绝对,但是也不无道理,做开发这行要想效率高,自动化还真是缺 ...

  3. Android添加系统级顶层窗口 和 WindowManager添加view的动画问题

    当Dialog有编辑框时如果选择会弹菜单窗口就不要用 Context applicationContext = mainActivity.getApplicationContext(); AlertD ...

  4. win8.1 android 双系统,图文详解Win8.1安装双系统的步骤

    有人会在Windows7系统下安装一个WindowsXP系统,有的会在Windows7系统下再安装一个Windows7,一个电脑两个系统的使用现在已经是非常常见的一种现象,那么在最新的Win8.1系统 ...

  5. 微信h5静默、非静默授权获取用户openId的方法和步骤

    微信h5静默.非静默授权获取用户openId的方法和步骤: 一.openId是什么? openId是用户在当前公众号下的唯一标识('身份证'),就是说通过这个openId,就能区分在这个公众号下具体是 ...

  6. 关于Android 构建

    在简书上面有系列关于Android 的文章,还不错,部分同学可以在开发过程中阅读和学习:www.jianshu.com/collection/3fde3b545a35 关于Android 构建,看到这 ...

  7. 百度广告产品系统级测试技术演进

    背景 根据典型的测试金字塔结构,一个产品的测试可分为三个层级.第一层是单元测试,主要对程序函数进行测试.第二层是集成测试,在百度内部是大家常理解的模块测试.第三层是系统级测试,对产品整体进行的测试.这 ...

  8. 优酷 Android 构建速度优化实践

    作者:苏彦郊(木磊) Android 项目一般使用 gradle 作为构建打包工具,gradle 简洁.动态的功能特性为人津津乐道,同样,构建执行速度缓慢的缺陷也一直为人诟病. 近年来,随着优酷功能特 ...

  9. ROS2学习(十六).ROS概念 - 构建系统

    关于ROS 2的构建系统 `ament_package`包 **package.xml** **ament package** `ament_cmake`代码库 `ament_lint`代码库 构建工 ...

最新文章

  1. [企业化NET]Window Server 2008 R2[3]-SVN 服务端 和 客户端 基本使用
  2. 分享一个C#读取计算机信息的类
  3. linux下echo /dev/ttys* 到字符设备文件,linux之tty pty pts
  4. Yik-Chung Wu ---Time synchronization for wireless sensor networks
  5. JMeter接口测试通过企业微信API记录(二)创建审批接口并测试
  6. python opencv cv2.cvtColor()方法(将图像从一种颜色空间转换为另一种颜色空间)(转换成灰度图)
  7. 签约 计算机英语,签约协议时的英文表达
  8. WCF Rest Service
  9. IDEA 入门:安装使用详解(创建项目/包/类、运行/关闭/导入项目、字体/字号设置、常用快捷键等)
  10. 月度开销(信息学奥赛一本通-T1243)
  11. 文档转换乱码异常解决:unoconv openoffice libreoffice
  12. 大数据可视化的方法和价值
  13. 设备密码的设置以及遗忘重设置
  14. mybatisplus activerecord之mybatisplus entity XXX Not Found TableInfoCache.错误
  15. 如何在点击a标签下载文件的时候通过JavaScript动态的修改文件的名称?
  16. php动态网时间实时刷新,php刷新页面_php与ajax实现页面自动刷新
  17. 计算机无线网络怎么连接打印机共享打印机,电脑如何连接无线打印机?网络打印机连接方法!,又快又好...
  18. java api管理工具_api 接口管理工具
  19. firefox插件下载失败
  20. redis五种类型的经典使用场景

热门文章

  1. android APK 调用G—sensor驱动的过程
  2. java识别二维码-zxing
  3. [转载]打工辛酸路:我是一朵飘零的花之73
  4. 全卷积网络(FCN)
  5. 震惊!Spring Aop不起作用可能是这个原因导致的
  6. 儿童编程什么语言需求
  7. 多级标题的使用心得-出现定义多级标题后标题格式杂乱
  8. DGNN:Skeleton-Based Action Recognition with Directed Graph Neural Networks
  9. DC LAB8 SDC约束 四种时序路径分析
  10. DM8达梦数据库tpch测试步骤