android静默安装+自动打开只需要看这一篇就够了

随着物联网的兴起,无人值守的android设备越来越多,我目前手里刚好在接相关的售票机器,需要实现更新、下载、静默安装、自动打开,网上相关文章太多了,很容易眼花缭乱,我这边整理出来留作记录,亲测有效

前提:root!

非root设备就需要rom厂商的签名了

第一步

增加权限

    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED" />
    <!--启动系统的广播监听-->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Manifest文件 的appliacation节点下增加
        android:sharedUserId="android.uid.shell"
        我还试了android.uid.system,也是可以正常静默安装的,其他几个uid没有测试,猜测也是可用的。
        

    <application
        android:name=".MyApplication"
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="false"
        android:sharedUserId="android.uid.shell"
        android:theme="@style/AppTheme">

第二步

注册广播监听重启或安装包卸载

        <receiver android:name=".receiver.AppBootReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <category android:name="android.intent.category.HOME" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_REPLACED" />
                <!--scheme就是填写package这个单词,而不是要替换为packName之类的-->
                <data android:scheme="package" />
            </intent-filter>
        </receiver>

这个地方有点意思:为什么scheme的值是package,追到源码可以发现 这个package其实是服务的名字(PMS的main方法中)
在这里插入图片描述

第三步

这里我用的是第三方的下载库,实际上只需要关心静默安装那一行代码

    /**
     * 下载apk,并调用安装
     */
    private fun downloadApk(context: Context, apkUrl: String?) {
        FileDownloader.setup(context)
        if (!apkUrl.isNullOrEmpty()) {
            val apkPath = "${PathUtils.getExternalAppDownloadPath()}/upgrade.apk"

            FileDownloader.getImpl().create(apkUrl)
                .setPath(apkPath)
                .setForceReDownload(true)
                .setListener(object :
                    FileDownloadSampleListener() {
                    override fun completed(task: BaseDownloadTask?) {
                        super.completed(task)
                        LogUtils.i("下载完成,${task?.targetFilePath}")

                        if (task?.targetFilePath == null) {
                            doRetry()
                        } else {
                            //下载完,安装
                            if (AppUtils.isAppRoot()) {
                                val installAppSilent =
                                    DangerousUtils.installAppSilent(task.targetFilePath, "-r")
                                LogUtils.i("静默安装apk 结果 $installAppSilent")
                            } else {
                                AppUtils.installApp(task.targetFilePath)
                                LogUtils.i("普通安装apk")
                            }
                        }
                    }

                    override fun progress(
                        task: BaseDownloadTask?,
                        soFarBytes: Int,
                        totalBytes: Int
                    ) {
                        super.progress(task, soFarBytes, totalBytes)
                        val process = (soFarBytes.toDouble() / totalBytes * 100).toInt()
                        viewModel.currentProcess.value = "$process%..."
                    }

                    override fun error(task: BaseDownloadTask?, e: Throwable?) {
                        super.error(task, e)
                        LogUtils.e(e?.message)
                        doRetry()
                    }
                }).start()
        }
    }

        fun installAppSilent(filePath: String, params: String?): Boolean {
            return installAppSilent(getFileByPath(filePath), params)
        }


      
        @JvmOverloads
        fun installAppSilent(
            file: File?,
            params: String? = null,
            isRooted: Boolean = isDeviceRooted
        ): Boolean {
            if (!isFileExists(file)) return false
            val filePath = '"'.toString() + file!!.absolutePath + '"'
            val command = ("LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install " +
                    (if (params == null) "" else "$params ")
                    + filePath)
            val commandResult = ShellUtils.execCmd(command, isRooted)
            return if (commandResult.successMsg != null
                && commandResult.successMsg.toLowerCase().contains("success")
            ) {
                true
            } else {
                Log.e(
                    "AppUtils", "installAppSilent successMsg: " + commandResult.successMsg +
                            ", errorMsg: " + commandResult.errorMsg
                )
                false
            }
        }

具体实现如上:DangerousUtils文件在这里,可以下载

第四步

监听,延迟5秒后启动,看需要也可以不延迟。

class AppBootReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        LogUtils.e("检测到系统启动广播: ${intent.action}")
        if (intent.action == Intent.ACTION_BOOT_COMPLETED || intent.action == Intent.ACTION_PACKAGE_REPLACED) {
            LogUtils.i("检测到系统启动广播,启动App")
            //同事沟通,业务需求场景处理,延迟5s后再启动
            GlobalScope.launch {
                delay(5 * 1000)
                ActivityUtils.startActivity(MainActivity::class.java)
            }
        }
    }
}

startActvity也有点道道
两种方式:一种是

   val intent2 = Intent(context, MainActivity::class.java)
   //必须加,否则报错 android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
   intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
   context.startActivity(intent2)

另外一种隐式启动:
我这边因为项目里引用了https://github.com/Blankj/AndroidUtilCode 这个工具库,所以可以一行代码,
ActivityUtils.startActivity(MainActivity::class.java)
其实际上是隐式调用:

  Intent intent = new Intent();
  if (extras != null) intent.putExtras(extras);
  intent.setComponent(new ComponentName(pkg, cls));
  startActivity(intent, context, options);

拓展

如果想实现,静默安装,就需要设置好UID,只有设置这个UID后,才有安装的权限
但是这个UID必须要求有系统的[签名], 而这个系统的[签名]是属于各大手机厂商的机密,除非root,刚好我手里的设备就是root过的,所以才有上面的步骤。
PSM里面定义的UID我都找出来了:
mSettings.addSharedUserLPw(“android.uid.system”, Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw(“android.uid.phone”, RADIO_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw(“android.uid.log”, LOG_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw(“android.uid.nfc”, NFC_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw(“android.uid.bluetooth”, BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw(“android.uid.shell”, SHELL_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw(“android.uid.se”, SE_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw(“android.uid.networkstack”, NETWORKSTACK_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex_ChuTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值