如何实现android静默安装

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何实现android静默安装相关的知识,希望对你有一定的参考价值。

今天在网上闲逛的时候发现一款名叫文件大师的软件,该软件提供了在root条件下的静默安装功能。因为之前也了解过相关的知识,网上各种博客帖子都说android中无法实现静默安装。这里很想知道这款软件是如何实现静默安装的,有没有高人能给给点指导~我猜想可能是用了#pm install命令,我在模拟器环境中使用该命令总是提示INSTALL_FAILED_INSUFFICIENT_STORAGE错误,我都把手机system内存改到256MB了啊。。

原理:
使用隐藏的系统API——installPackage。该方法在1.5版之后的android SDK中是看不见的,查看源码可以看到它设置了@hide属性,但在实际的运行包framework.jar中是存在的,因此只要能编译通过,安装到系统后是可以正常运行的。
步骤:
1. 从模拟器System\\framework目录下提取framework.jar
2. 将framework.jar后缀名改为zip,解压后提取其中的classes.dex文件
3. 用dex2jar工具将classes.dex转成classes.dex.dex2jar.jar(注意新版本的dex2jar工具无法转换Android2.2的framework,建议使用dex2jar-0.0.7.8-SNAPSHOT,该工具可以从google官方站上下载到)
4. 将classes.dex.dex2jar.jar改名为classes.dex.dex2jar.zip解压取出android/content/pm/目录下的PackageManager.class,IPackageInstallObserver.class,IPackageDeleteObserver.class及相关的几个class文件备用
5. 找到android-sdk目录下的android.jar,改名为android.zip(注意改名前先备份一下),解压后将步骤4中取得的class文件覆盖到android对应的目录下,重新压缩成android.zip,并改名为android.jar
6. 这个时候android.jar已经是一个更新过的SDK了,重新打开eclipse工程,已经可以实现。
调用方法:
void android.content.pm.PackageManager.installPackage(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName)

说明:
1. 由于更改android.jar可能导致重新加载SDK失败,覆盖之前切记备份一下
2. 实际上该过程可以调用到任何hide属性的API,本文为了影响最小,只覆盖了installPackage相关的class
3. 下载android源码重新编译SDK也可以实现调用隐藏API,不过比较麻烦
参考技术A 研究了好久,真的研究不出来不root就实现静默安装的方法。唉…… 参考技术B 你也说了 这个软件需要root权限下才能静默安装,模拟器应用显然没有root权限,当然会失败了 另外pm install确实可以在root权限下进行静默安装 参考技术C 1ROOT 2 用本机的应用市场选择静默安装3使用360手机助手 设置里选择秒装

ROOT存在于Linux系统、UNIX系统(如AIX、BSD等)和类UNIX系统(如Debian、Redhat、Ubuntu等版本的Linux系统以及Android系统)中,是系统中唯一的超级用户,相当于Windows系统中的administrator用户。其具有系统中所有的权限,如启动或停止一个进程,删除或增加用户,增加或者禁用硬件等等。
参考技术D 不能沉。。。

Android构建系统级应用静默授权,静默安装

文章目录

背景

基于定制平板系统开发应用,能获得安卓源码层的支持。
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;

然后通过ShellUtilsexecCommand方法执行该命令。

如果有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();
    //创建PendingIntent
    Intent 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静默安装的主要内容,如果未能解决你的问题,请参考以下文章

android怎么实现apk的静默安装

Android 如何静默安装app

android如何在定制系统上实现静默安装

Android是否可以实现静默安装模式

android实现静默安装demo

Android 静默安装(自动安装)和静默卸载的实现方法