安卓实现静默安装

在做医院项目的时候,遇到了医院病人床头屏app需要静默安装的需求。静默安装就是被安装的app在升级或者安装的时候不需要用户手动点击确认安装,让app自动在后台安装或者升级成功。

要想实现app静默安装我们需要下面4个步骤:

1.首先我们需要在配置清单文件里面添加INSTALL_PACKAGE权限
 <uses-permission android:name="android.permission.INSTALL_PACKAGES" />  
2.把该应用的uid设置为系统级别的,在manifest标签下添加以下属性
 android:sharedUserId="android.uid.system"
3.仅仅设置uid,还是没法实现静默安装,因为系统并不认为你这个app是系统级别的应用,所以,还应该对该应用的APK进行系统签名(注意:不是那个静默安装的APK,是这个实现静默安装程序的APK)。签名过程如下:

总共需要三个文件:

  • SignApk.jar %系统源码%/out/host/linux-x86/framework/signapk.jar
  • platform.x509.pem %系统源码%/build/target/product/security/platform.x509.pem
  • platform.pk8 %系统源码%/build/target/product/security/platform.pk8

由于我们的安卓设备是第三方定制的安卓系统,我们只能去找安卓设备供应商要系统签名需要的这三个文件。
利用签名工具signapk.jar修改应用程序签名:命令为:

 java -jar signapk.jar platform.x509.pem platform.pk8 my.apk mysigned.apk

my.apk是我们的应用程序的路径,mysigned.apk是应用系统签名之后生成的新的apk的路径

4.静默安装代码
/**
     * 静默安装
     * 会依次调用Stream-->反射-->Shell
     *
     * @param apkFile APK文件
     * @return 成功或失败
     */
    @SuppressLint("PackageManagerGetSignatures")
    @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
    public static synchronized boolean install(Context context, String apkFile) throws InterruptedException {
        File file;
        if (TextUtils.isEmpty(apkFile) || !(file = new File(apkFile)).exists())
            return false;
        context = context.getApplicationContext();
        //加上apk合法性判断
        AppUtils.AppInfo apkInfo = AppUtils.getApkInfo(file);
        if (apkInfo == null || TextUtils.isEmpty(apkInfo.getPackageName())) {
            LogUtils.iTag(TAG, "apk info is null, the file maybe damaged: " + file.getAbsolutePath());
            return false;
        }

        //加上本地apk版本判断
        AppUtils.AppInfo appInfo = AppUtils.getAppInfo(apkInfo.getPackageName());
        if (appInfo != null) {

            //已安装的版本比apk版本要高, 则不需要安装
            if (appInfo.getVersionCode() >= apkInfo.getVersionCode()) {
                LogUtils.iTag(TAG, "The latest version has been installed locally: " + file.getAbsolutePath(),
                        "app info: packageName: " + appInfo.getPackageName() + "; app name: " + appInfo.getName(),
                        "apk version code: " + apkInfo.getVersionCode(),
                        "app version code: " + appInfo.getVersionCode());
                return true;
            }

            //已安装的版本比apk要低, 则需要进一步校验签名和ShellUID

            PackageManager pm = context.getPackageManager();
            try {
                PackageInfo appPackageInfo, apkPackageInfo;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    appPackageInfo = pm.getPackageInfo(appInfo.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
                    apkPackageInfo = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNING_CERTIFICATES);
                } else {
                    appPackageInfo = pm.getPackageInfo(appInfo.getPackageName(), PackageManager.GET_SIGNATURES);
                    apkPackageInfo = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNATURES);
                }

                if (appPackageInfo != null && apkPackageInfo != null &&
                        !compareSharedUserId(appPackageInfo.sharedUserId, apkPackageInfo.sharedUserId)) {
                    LogUtils.wTag(TAG, "Apk sharedUserId is not match",
                            "app shellUid: " + appPackageInfo.sharedUserId,
                            "apk shellUid: " + apkPackageInfo.sharedUserId);
                    return false;
                }

            } catch (Throwable ignored) {

            }


        }
        //        try {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //由于调用PackageInstaller安装失败的情况下, 重复安装会导致内存占用无限增长的问题.
            //所以在安装之前需要判断当前包名是否有过失败记录, 如果以前有过失败记录, 则不能再使用该方法进行安装
            if (sPreferences == null) {
                sPreferences = context.getSharedPreferences(SP_NAME_PACKAGE_INSTALL_RESULT, Context.MODE_PRIVATE);
            }
            String packageName = apkInfo.getPackageName();
            boolean canInstall = sPreferences.getBoolean(packageName, true);
            if (canInstall) {
                boolean success = installByPackageInstaller(context, file, apkInfo);
                sPreferences.edit().putBoolean(packageName, success).apply();
                if (success) {
                    LogUtils.iTag(TAG, "Install Success[PackageInstaller]: " + file.getAbsolutePath());
                    return true;
                }
            }
        }

        if (installByReflect(context, file)) {
            if (sPreferences != null)
                sPreferences.edit().putBoolean(apkInfo.getPackageName(), true).apply();
            LogUtils.iTag(TAG, "Install Success[Reflect]", file.getPath());
            return true;
        }

        if (installByShell(file, DeviceUtils.isDeviceRooted())) {
            if (sPreferences != null)
                sPreferences.edit().putBoolean(apkInfo.getPackageName(), true).apply();
            LogUtils.iTag(TAG, "Install Success[Shell]", file.getPath());
            return true;
        }
        //        } catch (InterruptedException e) {
        //            throw e;
        //        } catch (Throwable e) {
        //            e.printStackTrace();
        //            LogUtils.wTag(TAG, e);
        //        }
        LogUtils.iTag(TAG, "Install Failure: " + file.getAbsolutePath());
        return false;
    }
附上Demo地址
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容