• 如何实现 APK 的升级功能?


    2019-09-18

    关键字:APK后台升级、APK自动升级


    最近在磕一个APK,要做一个热更新的功能。笔者以前从来没有做过类似的功能,但没吃过猪肉总是见过猪跑步的,想了一下,要实现一个纯粹的升级功能似乎也不难。

    1、升级原理

    一个纯粹的升级功能只需要三个模块。

    1、服务器;

    2、升级包与升级信息;

    3、具备升级功能的应用程序。

    1、服务器

    如果仅是想实现一个纯粹的升级功能的话,那服务器这边没啥特殊的要求。搭建一个最普通的 HTTP 访问型的站点即可。

    2、升级包与升级信息

    升级包就是新版本的 APK 程序安装包。但由于升级包通常来讲不具备信息描述能力,所以我们要再额外准备一个升级描述信息。升级策略就是将当前升级包的相关信息记录在升级信息中,客户端通过解析、判断升级信息来决定升级与否。换句话说,服务器端对外只暴露出一个升级信息出来。真实的升级程序地址,是需要解析升级信息来来获取的。

    3、具备升级功能的应用程序

    至于客户端,职责就清晰且单一了。程序启动后,在后台开一个线程定时访问升级服务器,拿到升级信息以后再做解析判断。当有更新版本时,下载然后提示升级。仅此而已。

    2、实现

    1、服务器搭建

    服务器端很简单,使用 Ngix 搭建即可。然后开辟一个目录专门用于存放应用升级的文件。

    2、升级包与升级信息准备

    升级包就不说了,直接编译生成 APK 文件即可。

    至于升级信息,看实际需求咯。比如笔者这边的升级需求就非常纯粹。仅有一个版本号与升级文件名。

    [root@localhost umlocator]# cat upgradeinfo.txt 
    version=0
    app_name=umlocator_alpha3_ver3.apk
    [root@localhost umlocator]#

    客户端在拿到这个升级信息时,首先检查 version 字段记载的版本号。如果该版本号大于应用当前的版本号,则表明有新版本,需要下载新版本的程序。

    app_name 字段记载的就是该新版本的安装包的名称,同时也是路径,因为笔者设定的是升级包与升级信息于同一目录下,所以客户端在拿到新版本的程序名以后,将自行将网址替换一下即可。

    显然这份升级描述信息是极其简陋的。连个 md5 检验都没有,但笔者前面说了,只是想实现一个纯粹的升级功能而已。业务上的合理性不重要。

    3、客户端升级处理逻辑

    笔者这边应用程序的升级总体逻辑很简单:程序在登录成功以后通过一个后台子线程,定时向服务器查询升级信息。当检查到有新版本时,立即开始下载,下载完成以后即弹出提示进行应用升级。

    后台线程通过一个 Service 来创建。

    <service
        android:name=".UpgradeService"
        android:enabled="true"
        android:exported="false" />

    因为后台线程服务是不可能开放给外部应用调用的,所以将 exported 设置为 false。同时,创建升级检测子线程不一定非得通过 Serivce 完成,大家可以按自己的实际业务需求来抉择。

    子线程的实现如下所示:

        private Thread checkThread = new Thread(){
    
            @Override
            public void run() {
                while(isContinueCheck) {
                    Logger.v(TAG, "Upgrade:" + counter);
    
                    if(isDownloading) {
                        Logger.i(TAG, "Downloading new version apk:" + progress + "%");
                    }else{
                        // 180s.
                        if(counter++ >= UPGRADE_CHECK_INTERVAL) {
                            counter = 0;// reset.
                            checkUpdate();
                        }
                    }
    
                    try {
                        Thread.sleep(10000); // 10s.
                    } catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Logger.i(TAG, "The upgrade check thread was stopped.");
            }
    
        };

    这个检测线程是通过一个标志位 isContinueCheck 来判断是否持续检测的。这个 while 语句每 10 秒执行一次,但是仍然有一个计数器 counter 来决定实际连接服务器检测升级信息的时间间隔。笔者这里设置成 counter 达到 18 ,即每 180 秒连接一次服务器查询升级信息。这个检测频率算是很高的了,检测频率高不是说会担心消耗手机端的资源,而是要担心服务器的资源消耗情况。当应用的用户量足够大时,这种检测频率,对服务提供方来讲是很烧钱的。但还是那句话,笔者仅用于演示一个纯粹的升级功能,所以这些都不重要。

    那我们要如何来启动这个子线程呢?

    笔者是将升级功能封装在 Serivce 中的。Service 有个特性:在应用运行期间,通过 start 的方式启动 Service 仅会实例化一份 Service 类。

    所以笔者将控制子线程循环运行的标志位 isContinueCheck 设为实例变量。然后通过外部的 Intent 来携带当前是要“启动”还是“停止”线程的检测。在 MainActivity 的 onResume() 方法中,发送启动线程的命令。在 onPause() 方法中发送停止的命令。

    @Override
    protected void onResume() {
        super.onResume();
        //Start the upgrade check daemon.
        Intent intent = new Intent(this, UpgradeService.class);
        intent.putExtra("isContinueCheck", true);
        startService(intent);
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        //stop the upgrade check thread.
        Intent intent = new Intent(this, UpgradeService.class);
        intent.putExtra("isContinueCheck", false);
        startService(intent);
    }

    同时,为了提升体验,应该在程序启动之初就检查一次升级信息。因此要在启动线程前将计数器 counter 的值 设为 17。

    private void startThread() {
        Logger.d(TAG, "checkThread is alive:" + checkThread.isAlive());
        if(checkThread.isAlive()) {
            if(isContinueCheck) {
                Logger.d(TAG, "upgrade check thread already running...");
            }else{
                Logger.d(TAG, "stopping the upgrade check thread...");
            }
        }else{
            /*
             * 升级策略:每 180 秒检查一次升级包,线程每 10 秒执行一次循环。
             * 刚打开程序时立即检查是否有新升级包。
             * 所以 counter 的默认值设为 17。
             * */
            counter = UPGRADE_CHECK_INTERVAL - 1; // counter = 17;
            checkThread.start();
            Logger.d(TAG, "started the upgrade check thread.");
        }
    }

    关于 HTTP 通信,笔者就直接使用 OkHttpUtils 开源库来实现了。当成功得到服务器的返回信息以后即开始解析了,解析算法根据自己的升级信息来写。笔者的实现如下图所示:

    有个地方要注意的就是查询应用当前的版本号。Android SDK 有接口可以直接查

    getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;

    然后就是下载文件了。下载仍然是使用 OkHttpUtils 开源库来实现。这里强烈建议在 sdcard 中创建一个专用目录用于保存下载文件。至于本地文件的管理逻辑还不那么简单,就不再赘述了。OkHttpUtils 有一个回调类是专用于下载文件的(FileCallBack 类),照着下图所示的代码来写即可:

    如果想监听下载进度,FileCallBack 类也有一个方法,重写该方法即可:

    在这个 inProgress() 方法中,progress 是百分比形式的当前下载进度。文件下载完成时它的值为 1.000000

    在应用中可以通过 startActivity 的方式来实现打开某 APK 安装引导程序的界面,它的实现代码为:

    Uri uri = Uri.fromFile(apk);
    Intent intent = new Intent();
    intent.setClassName("com.android.packageinstaller", "com.android.packageinstaller.PackageInstallerActivity");
    intent.setData(uri);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

    上面加红标粗的是 File 类的实例,它就是你下载好的 APK 文件。

    一个纯粹的 APK 升级功能的主要思路就是这样。至于更详情的逻辑处理,就要大家自行根据业务需求来实现了。


  • 相关阅读:
    安卓高级6 拍照或者从相册获取图片 并检测旋转角度或者更新画册扫描
    安卓高级6 玩转AppBarLayout,更酷炫的顶部栏 Toolbar
    安卓高级6 CoordinatorLayout
    安卓高级6 SnackBar
    android addCategory()等说明
    Eclipse如何解决启动慢?
    Java基础知识总结(绝对经典)
    java反射详解 三
    java反射详解 二
    java反射详解 一
  • 原文地址:https://www.cnblogs.com/chorm590/p/11537817.html
Copyright © 2020-2023  润新知