安卓(Android)應(yīng)用版本更新方法
開發(fā)中對版本進(jìn)行檢查并更新的需求基本是所有應(yīng)用必須有的功能,可是在實際開發(fā)中有些朋友就容易忽略一些細(xì)節(jié)。
版本更新的基本流程:
一般是將本地版本告訴服務(wù)器,服務(wù)器經(jīng)過相關(guān)處理會返回客戶端相關(guān)信息,告訴客戶端需不需要更新,如果需要更新是強制更新還是非強制更新??蛻舳说玫椒?wù)器返回的相關(guān)信息后再進(jìn)一步做邏輯處理。
強制更新:
一般的處理就是進(jìn)入應(yīng)用就彈窗通知用戶有版本更新,彈窗可以沒有取消按鈕并不能取消。這樣用戶就只能選擇更新或者關(guān)閉應(yīng)用了,當(dāng)然也可以添加取消按鈕,但是如果用戶選擇取消則直接退出應(yīng)用。
非強制更新
一般的處理是在應(yīng)用的設(shè)置中添加版本檢查的操作,如果用戶主動檢查版本則彈窗告知用戶有版本更新。這時用戶可以取消或者更新。
功能實際是比較簡單清晰的,但之所以寫這篇文章,是因為在我們公司的一個項目中,我把這個模塊分給了一個有著4年工作經(jīng)驗的哥們編寫,最后這哥們花了2個小時做完了。我還想這哥們寫得挺快,效率很高嘛,結(jié)果一測試發(fā)現(xiàn)問題不少:
1. 進(jìn)入首頁前關(guān)閉網(wǎng)絡(luò),進(jìn)入后刷新界面發(fā)現(xiàn)強制更新提醒沒有彈窗
2. 再進(jìn)入其它界面也沒有任何更新提醒
3. 在正常更新時點擊確定更新,沒有判斷網(wǎng)絡(luò)狀態(tài)(wifi,移動網(wǎng)絡(luò))直接下載apk文件,如果用戶在移動網(wǎng)絡(luò)下將耗費非常多的流量,直接影響用戶體驗
4. 下載過程在應(yīng)用內(nèi)沒有進(jìn)度條提醒,通知欄也沒有進(jìn)度提醒
5. apk文件下載過程中,如果強制結(jié)束應(yīng)用,下載被中斷
6. apk如果正常下載下來,彈出了安裝界面,這時如果用戶取消了安裝回到應(yīng)用,在需要強制更新的情況下并沒有再次彈窗阻止用戶進(jìn)行任何其它操作,失去了強制更新的意義
首先聲明下,我這絲毫沒有吐槽的意思喲,只是想說作為一個合格的程序員大家最起碼需要做到思維嚴(yán)謹(jǐn)這點,在有能力的情況下對用戶體驗?zāi)芴狳c建議最好。自己寫的代碼一定要經(jīng)過嚴(yán)格測試再交付,不要指望測試人員幫你測試再去修改,你要知道現(xiàn)在很多公司是沒有專業(yè)的測試人員甚至是沒有測試人員的喲。
針對以上問題出現(xiàn)的原因分析及解決方案如下:
對于1,2問題
很明顯他把檢查更新的工作只寫在了應(yīng)用的首頁(比如MainActivity)中了,在其它任何界面并沒有檢查更新的操作
解決方案
每個界面都需要檢查更新,當(dāng)然咱們不能在每個Activity中都復(fù)制粘貼一樣的代碼。這時定義一個BaseActivity,所有其它Activity都從它繼承就顯得很有價值了??梢园褭z查更新的操作放到BaseActivity的相關(guān)方法中,比如放在onResume中,這樣每當(dāng)顯示一個界面時都將執(zhí)行檢查更新的操作
對于5問題,如果把下載的操作放在了Activity中進(jìn)行,如果應(yīng)用意外終止或者強制退出應(yīng)用,則下載線程也將被終止
解決方案
可以將下載任務(wù)放到Service中執(zhí)行,這樣即使應(yīng)用被終止Service一樣有?;顧C制(startForeground)讓Service的任務(wù)有很大的機會繼續(xù)得以執(zhí)行
對于6問題,如果檢查更新的操作沒有在Activity的resume時再次執(zhí)行,則回到Activity自然也就沒有檢查更新并彈窗了
解決方案
在Activity的onResume中繼續(xù)檢查更新,如果是強制更新則彈窗阻止用戶進(jìn)行其它操作
對于3,4問題,我倒是覺得不是程序問題而是態(tài)度問題,實際加入非wifi和進(jìn)度顯示的功能非常簡單
整體解決方案
定義Service類,比如VersionUpdateService.java。主要提供版本檢查及文件下載操作
定義VersionUpdateHelper類,用來使用Service并提供和前臺Activity的交互
如果大家對Service的使用還有問題(需要頻繁更新前臺ui等),建議大家閱讀android圖片壓縮上傳系列-service篇這篇文章先做了解。
核心代碼如下:
public class VersionUpdateService extends Service {
private LocalBinder binder = new LocalBinder();
private DownLoadListener downLoadListener;//下載任務(wù)監(jiān)聽回調(diào)接口
private boolean downLoading;
private int progress;
private NotificationManager mNotificationManager;
private NotificationUpdaterThread notificationUpdaterThread;
private Notification.Builder notificationBuilder;
private final int NOTIFICATION_ID = 100;
private VersionUpdateModel versionUpdateModel;
private CheckVersionCallBack checkVersionCallBack;//檢查結(jié)果監(jiān)聽回調(diào)接口
public interface DownLoadListener {
void begain();
void inProgress(float progress, long total);
void downLoadLatestSuccess(File file);
void downLoadLatestFailed();
}
public interface CheckVersionCallBack {
void onSuccess();
void onError();
}
...
private class NotificationUpdaterThread extends Thread {
@Override
public void run() {
while (true) {
notificationBuilder.setContentTitle("正在下載更新" + progress + "%"); // the label of the entry
notificationBuilder.setProgress(100, progress, false);
...
}
}
}
private void starDownLoadForground() {
//創(chuàng)建通知欄
notificationBuilder = new Notification.Builder(this);
...
Notification notification = notificationBuilder.getNotification();
startForeground(NOTIFICATION_ID, notification);
}
private void stopDownLoadForground() {
stopForeground(true);
}
//執(zhí)行版本檢查任務(wù)
public void doCheckUpdateTask() {
//獲取本定版本號
final int currentBuild = AppUtil.getVersionCode(this);
//調(diào)用版本檢查接口
ApiManager.getInstance().versionApi.upgradeRecords(currentBuild, new RequestCallBack() {
@Override
public void onSuccess(Headers headers, String response) {
versionUpdateModel = JSON.parseObject(response, VersionUpdateModel.class);
...
if (checkVersionCallBack != null)
checkVersionCallBack.onSuccess();
}
@Override
public void onError(int code, String response) {
...
}
});
}
public void doDownLoadTask() {
starDownLoadForground();
//啟動通知欄進(jìn)度更新線程
notificationUpdaterThread = new NotificationUpdaterThread();
notificationUpdaterThread.start();
//文件下載存放路徑
final File fileDir = FolderUtil.getDownloadCacheFolder();
...
downLoading = true;
if (downLoadListener != null) {
downLoadListener.begain();
}
NetManager.getInstance().download(url, fileDir.getAbsolutePath(), new DownloadCallBack() {
@Override
public void inProgress(float progress_, long total) {
...
//執(zhí)行進(jìn)度更新
if (downLoadListener != null)
downLoadListener.inProgress(progress_, total);
}
@Override
public void onSuccess(Headers headers, String response) {
//執(zhí)行成功回調(diào)
...
installApk(destFile, VersionUpdateService.this);
}
@Override
public void onError(int code, String response) {
...
//執(zhí)行失敗回調(diào)
}
});
}
//安裝apk
public void installApk(File file, Context context) {
...
}
}
public class VersionUpdateHelper implements ServiceConnection {
private Context context;
private VersionUpdateService service;
private AlertDialog waitForUpdateDialog;
private ProgressDialog progressDialog;
private static boolean isCanceled;
private boolean showDialogOnStart;
public static final int NEED_UPDATE = 2;
public static final int DONOT_NEED_UPDATE = 1;
public static final int CHECK_FAILD = -1;
public static final int USER_CANCELED = 0;
private CheckCallBack checkCallBack;
public interface CheckCallBack{
void callBack(int code);
}
public VersionUpdateHelper(Context context) {
this.context = context;
}
public void startUpdateVersion() {
if (isCanceled)
return;
if (isWaitForUpdate() || isWaitForDownload()) {
return;
}
if (service == null && context != null) {
context.bindService(new Intent(context, VersionUpdateService.class), this, Context.BIND_AUTO_CREATE);
}
}
public void stopUpdateVersion() {
unBindService();
}
private void cancel() {
isCanceled = true;
unBindService();
}
private void unBindService() {
if (isWaitForUpdate() || isWaitForDownload()) {
return;
}
if (service != null && !service.isDownLoading()) {
context.unbindService(this);
service = null;
}
}
...
private void showNotWifiDownloadDialog() {
final AlertDialog.Builder builer = new AlertDialog.Builder(context);
builer.setTitle("下載新版本");
builer.setMessage("檢查到您的網(wǎng)絡(luò)處于非wifi狀態(tài),下載新版本將消耗一定的流量,是否繼續(xù)下載?");
builer.setNegativeButton("以后再說", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
...
//如果是強制更新 exit app
if (mustUpdate) {
MainApplication.getInstance().exitApp();
}
}
});
builer.setPositiveButton("繼續(xù)下載", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
service.doDownLoadTask();
}
});
...
}
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
service = ((VersionUpdateService.LocalBinder) binder).getService();
service.setCheckVersionCallBack(new VersionUpdateService.CheckVersionCallBack() {
@Override
public void onSuccess() {
VersionUpdateModel versionUpdateModel = service.getVersionUpdateModel();
//EventBus控制更新紅點提示
EventBus.getDefault().postSticky(versionUpdateEvent);
if (!versionUpdateModel.isNeedUpgrade()) {
if(checkCallBack != null){
checkCallBack.callBack(DONOT_NEED_UPDATE);
}
cancel();
return;
}
if (!versionUpdateModel.isMustUpgrade() && !showDialogOnStart) {
cancel();
return;
}
if(checkCallBack != null){
checkCallBack.callBack(NEED_UPDATE);
}
final AlertDialog.Builder builer = ...//更新提示對話框
builer.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
if (NetUtil.isWifi(context)) {
service.doDownLoadTask();
} else {
showNotWifiDownloadDialog();
}
}
});
//當(dāng)點取消按鈕時進(jìn)行登錄
if (!versionUpdateModel.isMustUpgrade()) {
builer.setNegativeButton("稍后更新", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
cancel();
if(checkCallBack != null){
checkCallBack.callBack(USER_CANCELED);
}
}
});
}
builer.setCancelable(false);
waitForUpdateDialog = builer.create();
waitForUpdateDialog.show();
}
@Override
public void onError() {
unBindService();
...
}
});
service.setDownLoadListener(new VersionUpdateService.DownLoadListener() {
@Override
public void begain() {
VersionUpdateModel versionUpdateModel = service.getVersionUpdateModel();
if (versionUpdateModel.isMustUpgrade()) {
progressDialog = ...//生成進(jìn)度條對話框
}
}
@Override
public void inProgress(float progress, long total) {
...//更新進(jìn)度條
}
@Override
public void downLoadLatestSuccess(File file) {
...//執(zhí)行成功處理
unBindService();
}
@Override
public void downLoadLatestFailed() {
...//執(zhí)行失敗處理
unBindService();
}
});
service.doCheckUpdateTask();
}
...
}
最后,使用方式還是非常簡單的。在BaseActivity中使用:
private VersionUpdateHelper versionUpdateHelper;
@Override
protected void onResume() {
super.onResume();
if(versionUpdateHelper == null)
versionUpdateHelper = new VersionUpdateHelper(this);
versionUpdateHelper.startUpdateVersion();
}
@Override
protected void onPause() {
super.onPause();
if(versionUpdateHelper != null)
versionUpdateHelper.stopUpdateVersion();
}
保證在每進(jìn)入一個界面和離開界面時都將檢查更新(bindService)和取消檢查(unBindService)。這時有些朋友可能認(rèn)為這樣做會不會浪費資源呢?沒有!
1,如果應(yīng)用是強制更新,那么在網(wǎng)絡(luò)正常情況下進(jìn)入應(yīng)用就能檢查出有新版本,這時彈窗后用戶不能進(jìn)入任何操作,沒有機會進(jìn)入別的界面,所有沒有進(jìn)行重復(fù)檢查;如果進(jìn)入應(yīng)用主頁由于網(wǎng)絡(luò)問題,檢查失敗,這時雖然不會彈窗提示更新,但是如果用戶的網(wǎng)絡(luò)恢復(fù)后進(jìn)入任何其它界面都將得到正常的版本更新檢查并彈窗提示
2,如果應(yīng)用是非強制更新時,在Helper代碼里進(jìn)行了如下的判斷:
SettingActivity.java
private VersionUpdateHelper versionUpdateHelper;
@OnClick(R.id.rl_version_update)
public void onClickVersionUpdate(View view) {
if(updateTips.getVisibility() == View.VISIBLE){
return;
}
VersionUpdateHelper.resetCancelFlag();//重置cancel標(biāo)記
if (versionUpdateHelper == null) {
versionUpdateHelper = new VersionUpdateHelper(this);
versionUpdateHelper.setShowDialogOnStart(true);
versionUpdateHelper.setCheckCallBack(new VersionUpdateHelper.CheckCallBack() {
@Override
public void callBack(int code) {
//EventBus發(fā)送消息通知紅點消失
VersionUpdateEvent versionUpdateEvent = new VersionUpdateEvent();
versionUpdateEvent.setShowTips(false);
EventBus.getDefault().postSticky(versionUpdateEvent);
}
});
}
versionUpdateHelper.startUpdateVersion();
}
寫在最后
由于代碼較多,且多數(shù)代碼和ui相關(guān),所以在文章中很多ui相關(guān)或者getter和setter方法等非核心代碼并沒有列出。演示代碼中用了EventBus和OkHttp開源控件,具體使用方法望大家自己找相關(guān)資料學(xué)習(xí)。本人打算有空的時候?qū)憘€EventBus系列文章,望大家多多關(guān)注。
文件下載也是使用的okHttp實現(xiàn)的,大家可以換成任何你熟悉的下載框架。VersionUpdateService.java和VersionUpdateHelper.java的完整代碼可以到我的github上下載,由于時間關(guān)系并沒有相關(guān)用法的完整案例還望見諒,等有時間一定奉上。
- Android編程實現(xiàn)自動檢測版本及自動升級的方法
- android實現(xiàn)程序自動升級到安裝示例分享(下載android程序安裝包)
- Android編程實現(xiàn)應(yīng)用自動更新、下載、安裝的方法
- Android App實現(xiàn)應(yīng)用內(nèi)部自動更新的最基本方法示例
- Android應(yīng)用自動更新功能實現(xiàn)的方法
- Android應(yīng)用APP自動更新功能的代碼實現(xiàn)
- Android應(yīng)用強制更新APP的示例代碼
- Android應(yīng)用App更新實例詳解
- 非常實用的小功能 Android應(yīng)用版本的更新實例
- Android應(yīng)用更新之自動檢測版本及自動升級
相關(guān)文章
Android實現(xiàn)自動變換大小的ViewPager
ViewPager使用適配器類將數(shù)據(jù)和view的處理分離,ViewPager的適配器叫PagerAdapter,這是一個抽象類,不能實例化,所以它有兩個子類:FragmentPagerAdapter 和 FragmentStatePagerAdapter,這兩個都是處理頁面為Fragment的情況2022-11-11
Android使用Realm數(shù)據(jù)庫實現(xiàn)App中的收藏功能(代碼詳解)
這篇文章主要介紹了Android使用Realm數(shù)據(jù)庫實現(xiàn)App中的收藏功能,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
Android數(shù)據(jù)轉(zhuǎn)移之Launcher導(dǎo)出數(shù)據(jù)庫給另一臺機器加載
這篇文章主要介紹了Android系統(tǒng)中Launcher導(dǎo)出數(shù)據(jù)庫給另一臺機器加載的詳細(xì)流程,數(shù)據(jù)轉(zhuǎn)移是少見但早晚要用到的功能,感興趣的朋友快來提前掌握吧2021-11-11
Android checkbox的listView具體操作方法
這篇文章主要介紹了Android checkbox的listView具體操作方法,重點就是存儲每個checkbox的狀態(tài)值,感興趣的小伙伴們可以參考一下2015-12-12
如何在Android中實現(xiàn)漸顯按鈕的左右滑動效果
本篇文章是對在Android中實現(xiàn)漸顯按鈕的左右滑動效果進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
Android Studio和Gradle使用不同位置JDK的問題解決
這篇文章主要介紹了Android Studio和Gradle使用不同位置JDK的問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
在Android中創(chuàng)建菜單項Menu以及獲取手機分辨率的解決方法
本篇文章小編為大家介紹,在Android中創(chuàng)建菜單項Menu以及獲取手機分辨率的解決方法。需要的朋友參考下2013-04-04

