开发中对版本进行检查并更新的需求基本是所有应用必须有的功能,可是在实际开发中有些朋友就容易忽略一些细节。
版本更新的基本流程:
一般是将本地版本告诉服务器,服务器经过相关处理会返回客户端相关信息,告诉客户端需不需要更新,如果需要更新是强制更新还是非强制更新。客户端得到服务器返回的相关信息后再进一步做逻辑处理。
强制更新:
一般的处理就是进入应用就弹窗通知用户有版本更新,弹窗可以没有取消按钮并不能取消。这样用户就只能选择更新或者关闭应用了,当然也可以添加取消按钮,但是如果用户选择取消则直接退出应用。
非强制更新
一般的处理是在应用的设置中添加版本检查的操作,如果用户主动检查版本则弹窗告知用户有版本更新。这时用户可以取消或者更新。
功能实际是比较简单清晰的,但之所以写这篇文章,是因为在我们公司的一个项目中,我把这个模块分给了一个有着4年工作经验的哥们编写,最后这哥们花了2个小时做完了。我还想这哥们写得挺快,效率很高嘛,结果一测试发现问题不少:
1. 进入首页前关闭网络,进入后刷新界面发现强制更新提醒没有弹窗
2. 再进入其它界面也没有任何更新提醒
3. 在正常更新时点击确定更新,没有判断网络状态(wifi,移动网络)直接下载apk文件,如果用户在移动网络下将耗费非常多的流量,直接影响用户体验
4. 下载过程在应用内没有进度条提醒,通知栏也没有进度提醒
5. apk文件下载过程中,如果强制结束应用,下载被中断
6. apk如果正常下载下来,弹出了安装界面,这时如果用户取消了安装回到应用,在需要强制更新的情况下并没有再次弹窗阻止用户进行任何其它操作,失去了强制更新的意义
首先声明下,我这丝毫没有吐槽的意思哟,只是想说作为一个合格的程序员大家最起码需要做到思维严谨这点,在有能力的情况下对用户体验能提点建议最好。自己写的代码一定要经过严格测试再交付,不要指望测试人员帮你测试再去修改,你要知道现在很多公司是没有专业的测试人员甚至是没有测试人员的哟。
针对以上问题出现的原因分析及解决方案如下:
对于1,2问题
很明显他把检查更新的工作只写在了应用的首页(比如MainActivity)中了,在其它任何界面并没有检查更新的操作
解决方案
每个界面都需要检查更新,当然咱们不能在每个Activity中都复制粘贴一样的代码。这时定义一个BaseActivity,所有其它Activity都从它继承就显得很有价值了。可以把检查更新的操作放到BaseActivity的相关方法中,比如放在onResume中,这样每当显示一个界面时都将执行检查更新的操作
对于5问题,如果把下载的操作放在了Activity中进行,如果应用意外终止或者强制退出应用,则下载线程也将被终止
解决方案
可以将下载任务放到Service中执行,这样即使应用被终止Service一样有保活机制(startForeground)让Service的任务有很大的机会继续得以执行
对于6问题,如果检查更新的操作没有在Activity的resume时再次执行,则回到Activity自然也就没有检查更新并弹窗了
解决方案
在Activity的onResume中继续检查更新,如果是强制更新则弹窗阻止用户进行其它操作
对于3,4问题,我倒是觉得不是程序问题而是态度问题,实际加入非wifi和进度显示的功能非常简单
整体解决方案
定义Service类,比如VersionUpdateService.java。主要提供版本检查及文件下载操作
定义VersionUpdateHelper类,用来使用Service并提供和前台Activity的交互
如果大家对Service的使用还有问题(需要频繁更新前台ui等),建议大家阅读android图片压缩上传系列-service篇这篇文章先做了解。
核心代码如下:
public class VersionUpdateService extends Service { private LocalBinder binder = new LocalBinder(); private DownLoadListener downLoadListener;//下载任务监听回调接口 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;//检查结果监听回调接口 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() { //创建通知栏 notificationBuilder = new Notification.Builder(this); ... Notification notification = notificationBuilder.getNotification(); startForeground(NOTIFICATION_ID, notification); } private void stopDownLoadForground() { stopForeground(true); } //执行版本检查任务 public void doCheckUpdateTask() { //获取本定版本号 final int currentBuild = AppUtil.getVersionCode(this); //调用版本检查接口 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(); //启动通知栏进度更新线程 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) { ... //执行进度更新 if (downLoadListener != null) downLoadListener.inProgress(progress_, total); } @Override public void onSuccess(Headers headers, String response) { //执行成功回调 ... installApk(destFile, VersionUpdateService.this); } @Override public void onError(int code, String response) { ... //执行失败回调 } }); } //安装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("检查到您的网络处于非wifi状态,下载新版本将消耗一定的流量,是否继续下载?"); builer.setNegativeButton("以后再说", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ... //如果是强制更新 exit app if (mustUpdate) { MainApplication.getInstance().exitApp(); } } }); builer.setPositiveButton("继续下载", 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(); } } }); //当点取消按钮时进行登录 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 = ...//生成进度条对话框 } } @Override public void inProgress(float progress, long total) { ...//更新进度条 } @Override public void downLoadLatestSuccess(File file) { ...//执行成功处理 unBindService(); } @Override public void downLoadLatestFailed() { ...//执行失败处理 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(); }
保证在每进入一个界面和离开界面时都将检查更新(bindService)和取消检查(unBindService)。这时有些朋友可能认为这样做会不会浪费资源呢?没有!
1,如果应用是强制更新,那么在网络正常情况下进入应用就能检查出有新版本,这时弹窗后用户不能进入任何操作,没有机会进入别的界面,所有没有进行重复检查;如果进入应用主页由于网络问题,检查失败,这时虽然不会弹窗提示更新,但是如果用户的网络恢复后进入任何其它界面都将得到正常的版本更新检查并弹窗提示
2,如果应用是非强制更新时,在Helper代码里进行了如下的判断:
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标记 if (versionUpdateHelper == null) { versionUpdateHelper = new VersionUpdateHelper(this); versionUpdateHelper.setShowDialogOnStart(true); versionUpdateHelper.setCheckCallBack(new VersionUpdateHelper.CheckCallBack() { @Override public void callBack(int code) { //EventBus发送消息通知红点消失 VersionUpdateEvent versionUpdateEvent = new VersionUpdateEvent(); versionUpdateEvent.setShowTips(false); EventBus.getDefault().postSticky(versionUpdateEvent); } }); } versionUpdateHelper.startUpdateVersion(); }
写在最后
由于代码较多,且多数代码和ui相关,所以在文章中很多ui相关或者getter和setter方法等非核心代码并没有列出。演示代码中用了EventBus和OkHttp开源控件,具体使用方法望大家自己找相关资料学习。本人打算有空的时候写个EventBus系列文章,望大家多多关注。
文件下载也是使用的okHttp实现的,大家可以换成任何你熟悉的下载框架。VersionUpdateService.java和VersionUpdateHelper.java的完整代码可以到我的github上下载,由于时间关系并没有相关用法的完整案例还望见谅,等有时间一定奉上。
我试图弄清楚如何从Play商店强制更新最新版本。 由于我的android应用程序的旧版本已经在play store中,我还没有执行检查,以获取应用程序的当前版本,并与链接中指定的play store应用程序版本进行比较,请强制更新android应用程序 当我想上传新版本的代码时,他才可以使用新版本。 你能帮帮我吗?
我有一个应用在谷歌Play商店。当一个更新版本可用时,旧版本将变得不可用--也就是说,如果用户不更新app,他们就不在app中进入。当新版本可用时,我如何强制用户更新应用程序?
在我的应用程序内,我想检查是否有任何更新的版本,我的应用程序是在app Store。如果有,那么必须通过警告消息通知用户,如果他/她选择升级,我想更新新版本。我想通过我的应用程序完成所有这些。这可能吗?
今天早上,我从谷歌的开发者控制台收到一封邮件,说我正在使用一个存在安全漏洞的OpenSSL版本。 实际上我只是在维护代码,我还没有开发它。但是,我应该解决这个问题。 我想知道如何在我的Android应用程序中将打开的SSL更新到最新的支持版本。 尝试了很多搜索,但没有如何在Android应用程序中执行此操作。 对于今天早上收到相同邮件的所有开发人员来说,这将是一个解决方案。 提前谢谢。
安卓应用可以拍照但是不能录像,点击录像会有调用摄像头的提醒,和照相一样,但是无法切换到摄像界面。拍照倒是可以直接切换到手机的拍照界面,正常使用。查看了权限都是设置了的 AndroidManifest.xml文件权限列表如下 在模拟器上的表现是可以跳转到摄像界面,但是一点录像就崩溃,只能原生录像然后上传 在实机上是点击录像无法跳转过去,甚至有些机型直接应用闪退
我读到,如果我们想在google play中更新应用程序,版本代码应该高于之前的apk文件。我有一个版本代码为20,版本名为1.0的应用程序。所以要更新应用程序,我应该如何增加版本代码?应该增加10吗?还是一个就够了?也就是说,版本代码从20到30还是版本代码从20到21?
鸿蒙开发app,实现-提示用户打开app之后检查版本如果当前版本小于最新版本提示更新版本-功能。 通过getBundleInfo()获取到的是用户安装的版本还是最新版本呢? 如何获取用户安装的app版本?如何获取应用市场的最新版本? 通过getBundleInfo()获取app.json文件中的version字段
本文向大家介绍Android程序版本更新之通知栏更新下载安装,包括了Android程序版本更新之通知栏更新下载安装的使用技巧和注意事项,需要的朋友参考一下 Android应用检查版本更新后,在通知栏下载,更新下载进度,下载完成自动安装,效果图如下: •检查当前版本号 AndroidManifest文件中的versionCode用来标识版本,在服务器放一个新版本的apk,versioncode大于当