Android--静默升级

静默升级通常指的是在用户不知情的情况下,应用程序能够自动下载并安装更新。这种升级过程涉及到几个步骤:

  1. 获取版本信息:应用在启动时检查当前版本号,这通常是通过访问服务器以获取最新的版本信息。
  2. 开始下载更新:一旦检测到新版本可用,应用会开始从服务器上下载更新包。
  3. 保存更新包:更新包被下载后,通常会保存在设备的指定文件夹中。
  4. 安装更新:在更新包下载完成后,应用需要使用特定的命令来安装更新。这些命令可能包括pm install或其他相关的Shell命令。

需要注意的是:
1、静默升级需要添加系统权限,并且apk得是系统应用;
2、 静默升级并不是所有情况下都可行或安全的。它通常需要设备具有较高的权限,如ROOT权限,以及可能需要开启辅助功能。此外,某些设备或制造商可能会禁用这些功能,因此非官方的静默升级方法可能会有安全风险。

在实际操作中,可以使用一些开源库或工具来帮助实现静默升级的功能。例如,Aria是一个可以用于静默升级的开源框架,它可以处理应用的版本更新流程。另一个例子是AutoInstaller项目,它提供了更高级的方法来自动化应用的静默安装和升级。

要实现静默升级,首先要准备的

一般需要该应用是系统级别的应用,经过了平台下发的对应签名apk,即:

1.内置到ROM,即APK包的安装位置是/system/app下。(制成一个系统刷机包)

2.使用APK的目标安装系统同样的签名。(系统签名)

静默升级步骤

  1. 首先导入安卓工具库依赖:
    implementation 'com.blankj:utilcode:1.26.0'

  1. 将SilentInstallUtils工具类复制到项目中:
/**
 * 静默安装工具类
 */
public class SilentInstallUtils {

    private static final String SP_NAME_PACKAGE_INSTALL_RESULT = "package_install_result";
    private static SharedPreferences sPreferences;


    public static synchronized void silenceInstall(@NonNull Context context, @NonNull String path) {
        Log.d("","InstallByPackageInstaller, silenceInstall starting...");
        try {
            boolean installSuccess = install(context, path);
            if (installSuccess) {
                Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
                if (intent != null) {
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                }
                ContextCompat.startActivity(context, intent, null);
                Log.d("","InstallByPackageInstaller, silenceInstall success");
            } else {
                Log.d("","InstallByPackageInstaller, silenceInstall fail");
            }
        } catch (InterruptedException e) {
            Log.d("","InstallByPackageInstaller, silenceInstall fail : " + e);
        }
    }

    @Synchronized
    public static boolean install(@NonNull Context context, @NonNull String apkFile) throws InterruptedException {
        File file = new File(apkFile);
        if (apkFile.isEmpty() || !file.exists()) {
            return false;
        }
        Context mContext = context.getApplicationContext();
        //apk合法性判断
        AppUtils.AppInfo apkInfo = AppUtils.getApkInfo(file);
        if (apkInfo == null || apkInfo.getPackageName().isEmpty()) {
            Log.d("","InstallByPackageInstaller,apk info is null, the file maybe damaged:  " + file.getAbsolutePath());
            return false;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //由于调用PackageInstaller安装失败的情况下, 重复安装会导致内存占用无限增长的问题.
            //所以在安装之前需要判断当前包名是否有过失败记录, 如果以前有过失败记录, 则不能再使用该方法进行安装
            if (sPreferences == null) {
                sPreferences = mContext.getSharedPreferences(SP_NAME_PACKAGE_INSTALL_RESULT, Context.MODE_PRIVATE);
            }
            String packageName = apkInfo.getPackageName();
            boolean success = installByPackageInstaller(mContext, file, apkInfo);
            sPreferences.edit().putBoolean(packageName, success).apply();
            if (success) {
                Log.d("","InstallByPackageInstaller,Install Success[PackageInstaller]::  " + file.getAbsolutePath());
                return true;
            }
        }
        return false;
    }


    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private static boolean installByPackageInstaller(@NonNull Context context, @NonNull File file, @NonNull AppUtils.AppInfo apkInfo) {
        Log.d("","InstallByPackageInstaller,filePath: " + file.getPath());
        OutputStream outStream = null;
        InputStream inStream = null;
        PackageInstaller.Session session = null;
        int sessionId = -1;
        boolean success = false;
        PackageManager pm = context.getPackageManager();
        PackageInstaller installer = pm.getPackageInstaller();
        try {
            //初始化安装参数
            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            params.setSize(file.length());
            params.setAppIcon(ImageUtils.drawable2Bitmap(apkInfo.getIcon()));
            params.setAppLabel(apkInfo.getName());
            params.setAppPackageName(apkInfo.getPackageName());
            sessionId = installer.createSession(params);
            //sessionId返回一个正数非零的值,若小于0,则会话开启失败
            if (sessionId > 0) {
                InstallReceiver callBack = new InstallReceiver(context, true, file.getAbsolutePath());
                session = installer.openSession(sessionId);
                outStream = session.openWrite(file.getName(), 0, file.length());
                inStream = new FileInputStream(file);
                int len;
                byte[] buffer = new byte[8192];
                while ((len = inStream.read(buffer)) != -1) {
                    outStream.write(buffer, 0, len);
                }
                session.fsync(outStream);
                inStream.close();
                outStream.close();
                session.commit(callBack.getIntentSender());
                success = callBack.isSuccess();
            }
        } catch (Throwable e) {
            Log.d("","InstallByPackageInstaller, fail:" + e.toString());
        } finally {
            try {
                //若会话开启,但未成功,需将其销毁
                if (sessionId > 0 && !success) {
                    if (session != null) {
                        session.abandon();
                    }
                    installer.abandonSession(sessionId);
                }
            } catch (Throwable e) {
                Log.d("","InstallByPackageInstaller, fail:" + e.toString());
            }
            CloseUtils.closeIOQuietly(inStream, outStream, session);
        }
        return success;
    }


    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private static boolean uninstallByPackageInstaller(@NonNull Context context, @NonNull String packageName) {
        Log.d("","InstallByPackageInstaller, uninstall packageName:" + packageName);
        try {
            InstallReceiver callBack = new InstallReceiver(context, false, packageName);
            PackageInstaller installer = Utils.getApp().getPackageManager().getPackageInstaller();
            installer.uninstall(packageName, callBack.getIntentSender());
            return callBack.isSuccess();
        } catch (Throwable e) {
            Log.d("","InstallByPackageInstaller, uninstall fail:" + e.toString());
        }
        return false;
    }


    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private static class InstallReceiver extends BroadcastReceiver {
        private String ACTION = InstallReceiver.class.getName() + SystemClock.elapsedRealtimeNanos();
        private Context mContext;
        private String mOperate;
        private String mParam;
        /**
         * 异步转同步
         */
        private CountDownLatch mCountDownLatch = new CountDownLatch(1);
        private boolean mSuccess;

        public InstallReceiver(Context context, boolean isInstall, String param) {
            this.mContext = context.getApplicationContext();
            this.mOperate = isInstall ? "Install" : "Uninstall";
            this.mParam = param;
            context.getApplicationContext().registerReceiver(this, new IntentFilter(ACTION));
        }

        public boolean isSuccess() {
            try {
                //安装最长等待2分钟
                mCountDownLatch.await(2, TimeUnit.MINUTES);
                return mSuccess;
            } catch (InterruptedException e) {
                Log.d("","InstallByPackageInstaller, fail:" + e.toString());
            } finally {
                mContext.unregisterReceiver(this);
            }
            return false;
        }

        public IntentSender getIntentSender() {
            return PendingIntent
                    .getBroadcast(mContext, ACTION.hashCode(), new Intent(ACTION).setPackage(mContext.getPackageName()),
                            PendingIntent.FLAG_UPDATE_CURRENT)
                    .getIntentSender();
        }

        @Override

        public void onReceive(Context context, Intent intent) {
            try {
                int status = -200;
                if (intent == null) {
                    mSuccess = false;
                } else {
                    status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
                    mSuccess = status == PackageInstaller.STATUS_SUCCESS;
                }
                Log.d("","InstallByPackageInstaller, receive: " + mOperate + " Result: " + mSuccess + " [" + status + "]");
            } finally {
                mCountDownLatch.countDown();

  1. 在ViewModel中调用工具类中的方法:
class MainActivityViewModel : ViewModel(){

    fun silentInstall(context : Context){
        SilentInstallUtils.silenceInstall(context, "/home/config/app-release.apk")
    }
}

  1. 点击按钮的时候调用ViewModel中silentInstall方法实现静默升级:
class MainActivity : AppCompatActivity() {

    private lateinit var mainViewModel : MainActivityViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mainViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

        // 静默升级按钮
        val updateButton = findViewById<Button>(R.id.upgrade_button)
        updateButton.setOnClickListener {
            mainViewModel.silentInstall(this)
            Toast.makeText(this, "UpGrade Apk Successful!", Toast.LENGTH_LONG, ).show()
        }
    }
}

注意:

  1. 需要在androidManifest中增加系统权限;
  2. 导入依赖后需要先运行下,否则有可能会报错
    报错内容:Didn’t find class “com.blankj.utilcode.util.Utils$FileProvider4UtilCode” on path……

解决办法:更换依赖

implementation 'com.blankj:utilcode:1.29.0'
 
// if u use AndroidX, use the following
implementation 'com.blankj:utilcodex:1.29.0'

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值