乐趣区

关于android:史上最好用的Android全量版本更新库XUpdate使用指南

我的项目简介

XUpdate 是一个轻量级、高可用性的 Android 全量版本更新框架。

XUpdate 是为了解决在不同项目组、不同平台之间进行对立的 Android 全量版本更新的库。它具备轻量、灵便、低耦合、高可用等特点,能够很不便地定制属于本人的版本更新。

设计原由

在没有 XUpdate 之前的版本更新,Android 版本更新根本都是靠写各种版本更新工具类来实现版本更新,更可怕的是有时在不同项目组或者平台之间,它们的版本更新齐全是不一样的,这样的后果就是会写有数的版本更新工具类,并且每次更换一个项目组或者平台就须要从头重写再写一遍,十分得麻烦。过后我就在想,版本更新作为一个 Android 利用根本都有,且内容绝对稳固的性能,有没有可能设计出一个通用的、不为业务或者平台所影响的根底库呢?

设计思路

在着手写 XUpdate 之前,我特地去 Github 上搜了一圈无关 Android 版本更新的内容,发现 AppUpdate 这个我的项目 star 数量最多。然而当我翻阅它的源码之后发现,它设计得并不柔美,外部耦合十分重大,不过长处就是 Android 版本更新的性能根本都涵盖了。于是我就照着它所领有的性能,联合了我对版本更新的了解进行了从新设计,感兴趣的可点击查看框架 UML 设计图。

解决痛点

  • 应用简略,只需一行代码即可实现版本更新性能。
  • 功能强大,兼容 Android6.0、7.0、8.0、9.0 和 10.0,反对静默更新和自动更新,反对国际化。
  • 扩展性强,可自定义申请 API 接口、提醒弹窗、下载服务、文件加密器等。
  • 搭建简略,只需提供 json 内容即可反对版本更新。
  • 配套齐全,默认提供了后盾服务、治理界面以及各类插件。

我的项目地址

为了不便大家应用, XUpdate 提供了一整套的全量版本更新解决方案.

  • Android 根底库: https://github.com/xuexiangjys/XUpdate
  • 版本更新后盾服务: https://github.com/xuexiangjys/XUpdateService
  • 版本更新管理系统: https://github.com/xuexiangjys/xupdate-management
  • Flutter 插件: https://github.com/xuexiangjys/flutter_xupdate
  • React-Native 插件: https://github.com/xuexiangjys/react-native-xupdate

我的项目演示

客户端成果

  • 默认版本更新

  • 后盾更新

  • 强制版本更新

  • 可疏忽版本更新

  • 自定义提醒弹窗主题

  • 应用零碎弹窗提醒

后盾治理界面

  • 登录页面

  • 后盾治理主页

  • 利用版本增加

  • 利用版本批改


集成指南

增加 Gradle 依赖

1. 先在我的项目根目录的 build.gradle 的 repositories 增加:

allprojects {
     repositories {
        ...
        maven {url "https://jitpack.io"}
    }
}

2. 而后在 dependencies 增加:

以下是版本阐明,抉择一个即可。

  • androidx 版本:2.0.0 及以上
dependencies {
  ...
  // androidx 版本
  implementation 'com.github.xuexiangjys:XUpdate:2.0.2'
}
  • support 版本:1.1.6 及以下
dependencies {
  ...
  // support 版本
  implementation 'com.github.xuexiangjys:XUpdate:1.1.6'
}

初始化 SDK

在 Application 进行初始化配置:

【留神】这里须要留神的是,IUpdateHttpService必须设置,否则框架将无奈失常应用!IUpdateHttpService的实现可参照 Demo 中的实现

XUpdate.get()
    .debug(true)
    .isWifiOnly(true)    // 默认设置只在 wifi 下查看版本更新
    .isGet(true)         // 默认设置应用 get 申请查看版本
    .isAutoMode(false)   // 默认设置非主动模式,可依据具体应用配置
    .param("versionCode", UpdateUtils.getVersionCode(this)) // 设置默认公共申请参数
    .param("appKey", getPackageName())
    .setOnUpdateFailureListener(new OnUpdateFailureListener() { // 设置版本更新出错的监听
        @Override
        public void onFailure(UpdateError error) {if (error.getCode() != CHECK_NO_NEW_VERSION) { // 对不同谬误进行解决
                ToastUtils.toast(error.toString());
            }
        }
    })
    .supportSilentInstall(true)  // 设置是否反对静默装置,默认是 true
    .setIUpdateHttpService(new OKHttpUpdateHttpService()) // 这个必须设置!实现网络申请性能。.init(this); 

【留神】:如果呈现任何问题,可开启 debug 模式来追踪问题。如果你还须要将日志记录在磁盘上,可实现以下接口

XUpdate.get().setILogger(new ILogger() {
    @Override
    public void log(int priority, String tag, String message, Throwable t) {// 实现日志记录性能}
});

混同配置

-keep class com.xuexiang.xupdate.entity.** {*;}

// 留神,如果你应用的是自定义 Api 解析器解析,还须要给你自定义 Api 实体配上混同,如下是本 demo 中配置的自定义 Api 实体混同规定:-keep class com.xuexiang.xupdatedemo.entity.** {*;}

根底应用

默认版本更新

间接调用如下代码即可实现版本更新操作:

XUpdate.newBuild(getActivity())
        .updateUrl(mUpdateUrl)
        .update();

须要留神的是,应用默认版本更新,申请服务器返回的 json 格局应包含如下内容:

{
  "Code": 0, // 0 代表申请胜利,非 0 代表失败
  "Msg": "", // 申请出错的信息"UpdateStatus": 1, // 0 代表不更新,1 代表有版本更新,不须要强制降级,2 代表有版本更新,须要强制降级"VersionCode": 3,"VersionName":"1.0.2","ModifyContent":"1、优化 api 接口。\r\n2、增加应用 demo 演示。\r\n3、新增自定义更新服务 API 接口。\r\n4、优化更新提醒界面。","DownloadUrl":"https://raw.githubusercontent.com/xuexiangjys/XUpdate/master/apk/xupdate_demo_1.0.2.apk","ApkSize": 2048"ApkMd5":"..."  //md5 值没有的话,就无奈保障 apk 是否残缺,每次都会从新下载。}

主动版本更新

主动版本更新:主动查看版本 + 主动下载 apk + 主动装置 apk(静默装置)。
只须要设置isAutoMode(true), 不过如果设施没有 root 权限的话,是无奈做到齐全的自动更新(因为静默装置须要 root 权限)。除此之外, 对于某些非凡设施可能须要自定义装置监听能力实现静默装置。

XUpdate.newBuild(getActivity())
        .updateUrl(mUpdateUrl)
        .isAutoMode(true) // 如果须要齐全无人干涉,自动更新,须要 root 权限【静默装置须要】.update();

反对后盾更新

开启反对后盾更新后, 用户点击“后盾更新”按钮后, 就能够进入到后盾更新, 不必始终在更新界面期待.

XUpdate.newBuild(getActivity())
        .updateUrl(mUpdateUrl)
        .supportBackgroundUpdate(true)
        .update();

自定义版本更新主题款式

通过设置更新顶部图片、主题色、按钮文字色彩、宽高比率等来实现自定义主题款式.

  • promptThemeColor: 设置主题色彩
  • promptButtonTextColor: 设置按钮的文字色彩
  • promptTopResId: 设置顶部背景图片
  • promptWidthRatio: 设置版本更新提醒器宽度占屏幕的比例,默认是 -1,不做束缚
  • promptHeightRatio: 设置版本更新提醒器高度占屏幕的比例,默认是 -1,不做束缚
XUpdate.newBuild(getActivity())
        .updateUrl(mUpdateUrl)
        .promptThemeColor(ResUtils.getColor(R.color.update_theme_color))
        .promptButtonTextColor(Color.WHITE)
        .promptTopResId(R.mipmap.bg_update_top)
        .promptWidthRatio(0.7F)
        .update();

强制版本更新

就是用户不更新的话,程序将无奈失常应用。

  • 如果你应用的是默认版本更新返回 api 的话, 只须要服务端返回 UpdateStatus 字段为 2 即可。
  • 如果你自定义申请返回 api 的话,只须要设置 UpdateEntitymIsForce字段为 true 即可。

进阶应用

版本更新信息实体

UpdateEntity 作为框架各个环节接口的通信媒介,理解它们的作用对前面接口的自定义十分要害。

  • UpdateEntity 字段属性
字段名 类型 默认值 备注
mHasUpdate boolean false 是否有新版本
mIsForce boolean false 是否强制装置:不装置无奈应用 app
mIsIgnorable boolean false 是否可疏忽该版本
mVersionCode int 0 最新版本 code
mVersionName String unknown_version 最新版本名称
mUpdateContent String “” 更新内容
mDownloadEntity DownloadEntity / 下载信息实体
mIsSilent boolean false 是否静默下载:有新版本时不提醒间接下载
mIsAutoInstall boolean true 是否下载实现后主动装置
  • DownloadEntity 字段属性
字段名 类型 默认值 备注
mDownloadUrl String “” 下载地址
mCacheDir String “” 文件下载的目录
mMd5 String “” 下载文件的 md5 值,用于校验,避免下载的 apk 文件被替换(最新演示 demo 中有计算 md5 值的工具)
mSize long 0 下载文件的大小【单位:KB】
mIsShowNotification boolean false 是否在告诉栏上显示下载进度
  • PromptEntity 字段属性
字段名 类型 默认值 备注
mThemeColor int R.color.xupdate_default_theme_color 主题色(进度条和按钮的背景色)
mTopResId int R.drawable.xupdate_bg_app_top 顶部背景图片资源 id
mButtonTextColor int 0 按钮文字色彩
mSupportBackgroundUpdate boolean false 是否反对后盾更新
mWidthRatio float -1(无约束) 版本更新提醒器宽度占屏幕的比例
mHeightRatio float -1(无约束) 版本更新提醒器高度占屏幕的比例

组成构造

在理解了版本更新的构造和各局部的性能后, 咱们就能够依据咱们理论的需要进行自定义了. 以下是版本更新的组成构造:

  • 版本更新查看器IUpdateChecker:查看是否有最新版本。
  • 版本更新解析器IUpdateParser:解析服务端返回的数据后果。
  • 版本更新提醒器IUpdatePrompter:展现最新的版本信息。
  • 版本更新下载器IUpdateDownloader:下载最新的版本 APK 安装包。
  • 网络申请服务接口IUpdateHttpService:定义了进行网络申请的相干接口。

除此之外,还有两个监听器:

  • 版本更新失败的监听器OnUpdateFailureListener
  • 版本更新 apk 装置的监听器OnInstallListener

更新调度外围:

  • 版本更新业务代理IUpdateProxy:负责版本更新的流程管制,调用 update 开始进行版本更新流程。

实践上, 以上所有组成部分都凋谢了自定义的 api, 咱们只须要依据咱们的需要实现对应的接口即可实现自定义.

自定义版本更新解析器

如果你不想应用默认版本更新返回的接口数据, 那么你能够实现 IUpdateParser 接口即可实现解析器的自定义, 示例如下:

XUpdate.newBuild(getActivity())
        .updateUrl(mUpdateUrl3)
        .updateParser(new CustomUpdateParser()) // 设置自定义的版本更新解析器
        .update();

public class CustomUpdateParser implements IUpdateParser {
    @Override
    public UpdateEntity parseJson(String json) throws Exception {CustomResult result = JsonUtil.fromJson(json, CustomResult.class);
        if (result != null) {return new UpdateEntity()
                    .setHasUpdate(result.hasUpdate)
                    .setIsIgnorable(result.isIgnorable)
                    .setVersionCode(result.versionCode)
                    .setVersionName(result.versionName)
                    .setUpdateContent(result.updateLog)
                    .setDownloadUrl(result.apkUrl)
                    .setSize(result.apkSize);
        }
        return null;
    }
}

自定义版本更新查看器 + 版本更新解析器 + 版本更新提醒器

  • 实现 IUpdateChecker 接口即可实现查看器的自定义。
  • 实现 IUpdateParser 接口即可实现解析器的自定义。
  • 实现 IUpdatePrompter 接口即可实现提醒器的自定义。
XUpdate.newBuild(getActivity())
        .updateUrl(mUpdateUrl3)
        .updateChecker(new DefaultUpdateChecker() {
            @Override
            public void onBeforeCheck() {super.onBeforeCheck();
                CProgressDialogUtils.showProgressDialog(getActivity(), "查问中...");
            }
            @Override
            public void onAfterCheck() {super.onAfterCheck();
                CProgressDialogUtils.cancelProgressDialog(getActivity());
            }
        })
        .updateParser(new CustomUpdateParser())
        .updatePrompter(new CustomUpdatePrompter(getActivity()))
        .update();


public class CustomUpdatePrompter implements IUpdatePrompter {

    private Context mContext;

    public CustomUpdatePrompter(Context context) {mContext = context;}

    @Override
    public void showPrompt(@NonNull UpdateEntity updateEntity, @NonNull IUpdateProxy updateProxy, @NonNull PromptEntity promptEntity) {showUpdatePrompt(updateEntity, updateProxy);
    }

    /**
     * 显示自定义提醒
     *
     * @param updateEntity
     * @param updateProxy
     */
    private void showUpdatePrompt(final @NonNull UpdateEntity updateEntity, final @NonNull IUpdateProxy updateProxy) {String updateInfo = UpdateUtils.getDisplayUpdateInfo(mContext, updateEntity);

        new AlertDialog.Builder(mContext)
                .setTitle(String.format("是否降级到 %s 版本?", updateEntity.getVersionName()))
                .setMessage(updateInfo)
                .setPositiveButton("降级", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {updateProxy.startDownload(updateEntity, new OnFileDownloadListener() {
                            @Override
                            public void onStart() {HProgressDialogUtils.showHorizontalProgressDialog(mContext, "下载进度", false);
                            }

                            @Override
                            public void onProgress(float progress, long total) {HProgressDialogUtils.setProgress(Math.round(progress * 100));
                            }

                            @Override
                            public boolean onCompleted(File file) {HProgressDialogUtils.cancel();
                                return true;
                            }

                            @Override
                            public void onError(Throwable throwable) {HProgressDialogUtils.cancel();
                            }
                        });
                    }
                })
                .setNegativeButton("暂不降级", null)
                .setCancelable(false)
                .create()
                .show();}

自定义文件加密校验器

本框架默认应用的文件加密校验办法是 MD5 加密形式,当然如果你不想应用 MD5 加密,你也能够自定义文件加密器IFileEncryptor, 以下是 MD5 文件加密器的实现供参考:

/**
 * 默认的文件加密计算应用的是 MD5 加密
 *
 * @author xuexiang
 * @since 2019-09-06 14:21
 */
public class DefaultFileEncryptor implements IFileEncryptor {
    /**
     * 加密文件
     *
     * @param file
     * @return
     */
    @Override
    public String encryptFile(File file) {return Md5Utils.getFileMD5(file);
    }

    /**
     * 测验文件是否无效(加密是否统一)*
     * @param encrypt 加密值, 如果 encrypt 为空,间接认为是无效的
     * @param file    须要校验的文件
     * @return 文件是否无效
     */
    @Override
    public boolean isFileValid(String encrypt, File file) {return TextUtils.isEmpty(encrypt) || encrypt.equalsIgnoreCase(encryptFile(file));
    }
}

最初再调用 XUpdate.get().setIFileEncryptor 办法设置即可失效。

只应用 XUpdate 的下载器性能进行 apk 的下载

XUpdate.newBuild(getActivity())
        .apkCacheDir(PathUtils.getExtDownloadsPath()) // 设置下载缓存的根目录
        .build()
        .download(mDownloadUrl, new OnFileDownloadListener() {   // 设置下载的地址和下载的监听
            @Override
            public void onStart() {HProgressDialogUtils.showHorizontalProgressDialog(getContext(), "下载进度", false);
            }

            @Override
            public void onProgress(float progress, long total) {HProgressDialogUtils.setProgress(Math.round(progress * 100));
            }

            @Override
            public boolean onCompleted(File file) {HProgressDialogUtils.cancel();
                ToastUtils.toast("apk 下载结束,文件门路:" + file.getPath());
                return false;
            }

            @Override
            public void onError(Throwable throwable) {HProgressDialogUtils.cancel();
            }
        });

只应用 XUpdate 的 APK 装置的性能

_XUpdate.startInstallApk(getContext(), FileUtils.getFileByPath(PathUtils.getFilePathByUri(getContext(), data.getData()))); // 填写文件所在的门路

如果你的 apk 装置不同凡响,你能够实现本人的 apk 装置器。你只须要实现 OnInstallListener 接口,并通过 XUpdate.setOnInstallListener 进行设置即可失效。


常见问题

接入的问题

1. 问:为什么我刚接入的时候,始终报错updateHttpService == null?

答:你须要仔细阅读接入文档,必须在 Application 中按要求初始化 XUpdate,而其中IUpdateHttpService 必须设置,除非你自定义版本查看器和版本更新下载器,否则框架将无奈失常应用!

2. 问:为什么我在开发调试的时候,可能呈现最新版本的提醒,然而打进去的包却什么反馈也没有?

答:呈现这个问题,个别是少了混同配置。如果你应用了自定义的版本更新解析器,请对你的接口实体进行混同配置。

3. 问:为什么我点击下载后文件是能下载下来的,然而进度条不更新,或者打印出进度条的值是 -1?

答:呈现这种状况能够从两个方面来排查。

  • 如果你打印出进度条的值是 -1,那很有可能是服务端提供的下载服务自身就不反对进度。因为如果你在申请服务端下载文件的时候,服务端在申请头中没有返回数据长度,即contentLength(Content-Length)没有设置,是未知的,那么是不可能有进度的。这个你能够通过抓包来查看响应头中是否设置了“Content-Length”。
  • 如果你应用的服务端自身曾经确认是反对进度的。那么就可能须要思考是不是你的 IUpdateHttpServicedownload接口实现有问题,你务必要保障接口 DownloadCallbackonProgress办法能被失常执行。

4. 问:为什么我执行了版本更新的办法,它却始终提醒无最新版本或者是始终在进行版本更新?

答:呈现这个问题,你首先得明确一点的是,你判断是否有最新版本的根据是什么。到底是根据 VersionCode 还是VersionName,这个取决于你理论应用的场景。明确完这一点,你才能够依据日志去判断到底是前端出了问题还是后端出了问题。

5. 问:这个最新版本我曾经下载过了,只不过没装置,在下一次进行版本更新的查看时,为什么我还要从新下载一次?

答:呈现这个问题,只能证实你的后端在返回版本信息的时候并没有返回最新版本文件的 MD5 值,或者返回了你没有设置。如果你设置了 MD5 值,那么就是你设置的 MD5 值和文件计算出来的 MD5 值不匹配,这种状况下,你的 APK 文件极有可能被篡改了(当然在这种状况下,你也不能失常装置),或者是你们前后端的 MD5 值计算算法不统一(个别不存在这种状况)。

6. 问:为什么我最新的利用下载了,然而点击 装置 按钮后始终提醒更新失败呢?

答:呈现这种问题的状况有很多种。

  • 首先你须要确保是否找到下载下来的最新 APK,如果你设置了 MD5 值的话,还须要判断下载下来的最新 APK 计算出来的 MD5 值和后盾接口返回的 MD5 值是否统一(计算文件的 MD5 值 Demo 中有对应的办法);
  • 其次你须要手动装置一下 APK, 确保 APK 文件没问题(签名统一、文件残缺),能失常装置;
  • 最初你能够在多台设施上尝试一下,确保不是设施本身的问题。
  • 如果以上办法都不能解决问题,很遗憾,那么你只能自定义装置监听器 OnInstallListener 接口,实现可能正确装置 APK 的办法了。

7. 问:在版本更新的过程中呈现了谬误,我该如何进行排查?

答:最好的解决办法当然是打断点一一进行排查啦!当然在打断点前,咱们须要调用 XUpdate.get().debug(true) 开启 debug 模式,打印相干日志,明确出错的地位,这样能力更快地解决问题啦!

8. 问:为什么版本更新弹窗弹不进去,报 System.err: at com.xuexiang.xupdate.widget.BaseDialog.init(BaseDialog.java:72) 谬误?

答:最好的解决办法就是传入的 context 应用的是 AppCompatActivity, 而不是Activity 或者 FragmentActivity!如果你肯定要应用Activity 或者 FragmentActivity,那么请设置其主题为Theme.AppCompat 类型的主题。

自定义的问题

常常有使用者反馈不晓得该如何自定义接口(面对一堆接口,不晓得该如何下手),进行个性化的定制,以满足版本更新实现的需要,上面我将一一列举问题和解决的办法。

1. 问:我应用的是 retrofit 自定义的接口,不想应用 IUpdateHttpService 那套通用申请形式来查问最新版本,我该怎么办?

答:能够自定义版本更新查看器IUpdateChecker,它次要负责的是查问是否存在最新版本。可参考框架默认提供的版本更新查看器来自定义。

2. 问:我不想应用框架默认的申请服务器返回的 json 格局,因为公司的后端有本人的一套数据返回格局,我该怎么办?

答:能够自定义版本更新解析器IUpdateParser,它次要负责的是解析服务端返回的数据后果,并构建更新信息实体UpdateEntity。具体可参考自定义版本更新解析器, 也可参考框架默认提供的版本更新解析器来自定义。

3. 问:我感觉框架提供的一套默认的版本更新提醒界面不合乎咱们公司的 UI 格调,我能自定义一套本人的版本更新提醒界面吗?

答:能够自定义版本更新提醒器IUpdatePrompter,它次要负责的是展现最新的版本信息。具体可参考自定义版本更新提醒器, 也可参考框架默认提供的版本更新提醒器来自定义。

4. 问:我总感觉框架中提供的最新版本 APK 下载服务速度不行,我想实现本人的下载服务,并做相干下载进度的提醒,能够吗?

答:能够自定义版本更新下载器IUpdateDownloader,它次要负责的是下载最新的版本 APK 安装包。可参考框架默认提供的版本更新下载器来自定义。

5. 问:我的利用和一般利用有些特地,并不能应用零碎的装置 api 安装程序,我该怎么办?

答:如果你的 apk 装置不同凡响,你能够实现本人的 apk 装置器。你只须要实现 OnInstallListener 接口,并通过 XUpdate.setOnInstallListener 进行设置即可失效。

【留神】以上实现的自定义接口,都能够通过 XUpdate 进行全局和部分的设置。

错误码

错误码 备注
2000 查问更新失败
2001 没有 wifi
2002 没有网络
2003 正在进行版本更新
2004 无最新版本
2005 版本查看返回空
2006 版本查看返回 json 解析失败
2007 曾经被疏忽的版本
2008 利用下载的缓存目录为空
3000 版本提醒器异样谬误
3001 版本提醒器所在 Activity 页面被销毁
4000 新利用安装包下载失败
4001 读写权限申请失败
5000 apk 装置失败
5100 未知谬误

资源链接

  • Android 根底库: https://github.com/xuexiangjys/XUpdate
  • 版本更新后盾服务: https://github.com/xuexiangjys/XUpdateService
  • 版本更新管理系统: https://github.com/xuexiangjys/xupdate-management
  • Flutter 插件: https://github.com/xuexiangjys/flutter_xupdate
  • React-Native 插件: https://github.com/xuexiangjys/react-native-xupdate

微信公众号

更多资讯内容,欢送扫描关注我的集体微信公众号:【我的 Android 开源之旅】

退出移动版