XUpdate是一個輕量級、高可用性的Android全量版本更新框架。前端
XUpdate是爲了解決在不一樣項目組、不一樣平臺之間進行統一的Android全量版本更新的庫。它具備輕量、靈活、低耦合、高可用等特色,能夠很方便地定製屬於本身的版本更新。java
在沒有XUpdate以前的版本更新,Android版本更新基本都是靠寫各類版本更新工具類來實現版本更新,更可怕的是有時在不一樣項目組或者平臺之間,它們的版本更新徹底是不同的,這樣的結果就是會寫無數的版本更新工具類,而且每次更換一個項目組或者平臺就須要從頭重寫再寫一遍,很是得麻煩。當時我就在想,版本更新做爲一個Android應用基本都有,且內容相對穩定的功能,有沒有可能設計出一個通用的、不爲業務或者平臺所影響的基礎庫呢?react
在着手寫XUpdate以前,我特意去Github上搜了一圈有關Android版本更新的內容,發現AppUpdate這個項目star數量最多。可是當我翻閱它的源碼以後發現,它設計得並不優美,內部耦合很是嚴重,不過優勢就是Android版本更新的功能基本都涵蓋了。因而我就照着它所擁有的功能,結合了我對版本更新的理解進行了從新設計,感興趣的可點擊查看框架UML設計圖。android
爲了方便你們使用, XUpdate提供了一整套的全量版本更新解決方案.git
1.先在項目根目錄的 build.gradle 的 repositories 添加:github
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
複製代碼
2.而後在dependencies添加:算法
如下是版本說明,選擇一個便可。json
dependencies {
...
// androidx版本
implementation 'com.github.xuexiangjys:XUpdate:2.0.2'
}
複製代碼
dependencies {
...
// support版本
implementation 'com.github.xuexiangjys:XUpdate:1.1.6'
}
複製代碼
在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();
複製代碼
經過設置更新頂部圖片、主題色、按鈕文字顏色、寬高比率等來實現自定義主題樣式.
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的話,只須要設置UpdateEntity
的mIsForce
字段爲true便可。
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 | 是否下載完成後自動安裝 |
字段名 | 類型 | 默認值 | 備註 |
---|---|---|---|
mDownloadUrl | String | "" | 下載地址 |
mCacheDir | String | "" | 文件下載的目錄 |
mMd5 | String | "" | 下載文件的md5值,用於校驗,防止下載的apk文件被替換(最新演示demo中有計算md5值的工具) |
mSize | long | 0 | 下載文件的大小【單位:KB】 |
mIsShowNotification | boolean | false | 是否在通知欄上顯示下載進度 |
字段名 | 類型 | 默認值 | 備註 |
---|---|---|---|
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.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.startInstallApk(getContext(), FileUtils.getFileByPath(PathUtils.getFilePathByUri(getContext(), data.getData()))); //填寫文件所在的路徑
複製代碼
若是你的apk安裝不同凡響,你能夠實現本身的apk安裝器。你只須要實現OnInstallListener接口,並經過XUpdate.setOnInstallListener
進行設置便可生效。
updateHttpService == null
?答:你須要仔細閱讀接入文檔,必須在Application中按要求初始化XUpdate
,而其中IUpdateHttpService
必須設置,除非你自定義版本檢查器和版本更新下載器,不然框架將沒法正常使用!
答:出現這個問題,通常是少了混淆配置。若是你使用了自定義的版本更新解析器,請對你的接口實體進行混淆配置。
答:出現這種狀況能夠從兩個方面來排查。
若是你打印出進度條的值是-1,那頗有多是服務端提供的下載服務自己就不支持進度。由於若是你在請求服務端下載文件的時候,服務端在請求頭中沒有返回數據長度,即contentLength
(Content-Length)沒有設置,是未知的,那麼是不可能有進度的。這個你能夠經過抓包來查看響應頭中是否設置了「Content-Length」。
若是你使用的服務端自己已經確認是支持進度的。那麼就可能須要考慮是否是你的IUpdateHttpService
的download
接口實現有問題,你務必要保證接口DownloadCallback
的onProgress
方法能被正常執行。
答:出現這個問題,你首先得明確一點的是,你判斷是否有最新版本的依據是什麼。究竟是依據VersionCode
仍是VersionName
,這個取決於你實際使用的場景。明確完這一點,你才能夠根據日誌去判斷究竟是前端出了問題仍是後端出了問題。
答:出現這個問題,只能證實你的後端在返回版本信息的時候並無返回最新版本文件的MD5值,或者返回了你沒有設置。若是你設置了MD5值,那麼就是你設置的MD5值和文件計算出來的MD5值不匹配,這種狀況下,你的APK文件極有可能被篡改了(固然在這種狀況下,你也不能正常安裝),或者是大家先後端的MD5值計算算法不一致(通常不存在這種狀況)。
安裝
按鈕後一直提示更新失敗呢?答:出現這種問題的狀況有不少種。
OnInstallListener
接口,實現可以正確安裝APK的方法了。答:最好的解決方法固然是打斷點逐個進行排查啦!固然在打斷點前,咱們須要調用XUpdate.get().debug(true)
開啓debug模式,打印相關日誌,明確出錯的位置,這樣才能更快地解決問題啦!
System.err: at com.xuexiang.xupdate.widget.BaseDialog.init(BaseDialog.java:72)
錯誤?答:最好的解決方法就是傳入的context使用的是AppCompatActivity
, 而不是Activity
或者FragmentActivity
!若是你必定要使用Activity
或者FragmentActivity
,那麼請設置其主題爲Theme.AppCompat
類型的主題。
常常有使用者反饋不知道該如何自定義接口(面對一堆接口,不知道該如何下手),進行個性化的定製,以知足版本更新實現的需求,下面我將一一列舉問題和解決的方法。
IUpdateHttpService
那套通用請求方式來查詢最新版本,我該怎麼辦?答:能夠自定義版本更新檢查器IUpdateChecker
,它主要負責的是查詢是否存在最新版本。可參考框架默認提供的版本更新檢查器來自定義。
答:能夠自定義版本更新解析器IUpdateParser
,它主要負責的是解析服務端返回的數據結果,並構建更新信息實體UpdateEntity
。具體可參考自定義版本更新解析器, 也可參考框架默認提供的版本更新解析器來自定義。
答:能夠自定義版本更新提示器IUpdatePrompter
,它主要負責的是展現最新的版本信息。具體可參考自定義版本更新提示器, 也可參考框架默認提供的版本更新提示器來自定義。
答:能夠自定義版本更新下載器IUpdateDownloader
,它主要負責的是下載最新的版本APK安裝包。可參考框架默認提供的版本更新下載器來自定義。
答:若是你的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開源之旅】