史上最好用的Android全量版本更新庫XUpdate使用指南

在這裏插入圖片描述

項目簡介

XUpdate是一個輕量級、高可用性的Android全量版本更新框架。前端

XUpdate是爲了解決在不一樣項目組、不一樣平臺之間進行統一的Android全量版本更新的庫。它具備輕量、靈活、低耦合、高可用等特色,能夠很方便地定製屬於本身的版本更新。java

設計起因

在沒有XUpdate以前的版本更新,Android版本更新基本都是靠寫各類版本更新工具類來實現版本更新,更可怕的是有時在不一樣項目組或者平臺之間,它們的版本更新徹底是不同的,這樣的結果就是會寫無數的版本更新工具類,而且每次更換一個項目組或者平臺就須要從頭重寫再寫一遍,很是得麻煩。當時我就在想,版本更新做爲一個Android應用基本都有,且內容相對穩定的功能,有沒有可能設計出一個通用的、不爲業務或者平臺所影響的基礎庫呢?react

設計思路

在着手寫XUpdate以前,我特意去Github上搜了一圈有關Android版本更新的內容,發現AppUpdate這個項目star數量最多。可是當我翻閱它的源碼以後發現,它設計得並不優美,內部耦合很是嚴重,不過優勢就是Android版本更新的功能基本都涵蓋了。因而我就照着它所擁有的功能,結合了我對版本更新的理解進行了從新設計,感興趣的可點擊查看框架UML設計圖android

解決痛點

  • 使用簡單,只需一行代碼便可完成版本更新功能。
  • 功能強大,兼容Android6.0、7.0、8.0、9.0和10.0,支持靜默更新和自動更新,支持國際化。
  • 擴展性強,可自定義請求API接口、提示彈窗、下載服務、文件加密器等。
  • 搭建簡單,只需提供json內容便可支持版本更新。
  • 配套齊全,默認提供了後臺服務、管理界面以及各種插件。

項目地址

爲了方便你們使用, XUpdate提供了一整套的全量版本更新解決方案.git


項目演示

客戶端效果

  • 默認版本更新

  • 後臺更新

xupdate_background.png

  • 強制版本更新

xupdate_force.png

  • 可忽略版本更新

xupdate_ignore.png

  • 自定義提示彈窗主題

  • 使用系統彈窗提示

xupdate_system.png

後臺管理界面

  • 登陸頁面

在這裏插入圖片描述

  • 後臺管理主頁

xupdate_management_2.png

  • 應用版本添加

xupdate_management_3.png

  • 應用版本修改


集成指南

添加Gradle依賴

1.先在項目根目錄的 build.gradle 的 repositories 添加:github

allprojects {
     repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}
複製代碼

2.而後在dependencies添加:算法

如下是版本說明,選擇一個便可。json

  • 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中的實現react-native

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": "一、優化api接口。\r\n二、添加使用demo演示。\r\n三、新增自定義更新服務API接口。\r\n四、優化更新提示界面。",
  "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開源之旅】

gzh_weixin.jpg

相關文章
相關標籤/搜索