安卓(Android)应用版本更新方法

开发中对版本进行检查并更新的需求基本是所有应用必须有的功能,可是在实际开发中有些朋友就容易忽略一些细节。

版本更新的基本流程:

一般是将本地版本告诉服务器,服务器经过相关处理会返回客户端相关信息,告诉客户端需不需要更新,如果需要更新是强制更新还是非强制更新。客户端得到服务器返回的相关信息后再进一步做逻辑处理。

强制更新:

一般的处理就是进入应用就弹窗通知用户有版本更新,弹窗可以没有取消按钮并不能取消。这样用户就只能选择更新或者关闭应用了,当然也可以添加取消按钮,但是如果用户选择取消则直接退出应用。

非强制更新

一般的处理是在应用的设置中添加版本检查的操作,如果用户主动检查版本则弹窗告知用户有版本更新。这时用户可以取消或者更新。

功能实际是比较简单清晰的,但之所以写这篇文章,是因为在我们公司的一个项目中,我把这个模块分给了一个有着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上下载,由于时间关系并没有相关用法的完整案例还望见谅,等有时间一定奉上。

(0)

相关推荐

  • android中DownloadManager实现版本更新,监听下载进度实例

    DownloadManager简介 DownloadManager是Android 2.3(API level 9)用系统服务(Service)的方式提供了DownloadManager来处理长时间的下载操作.它包含两个静态内部类DownloadManager.Query(用来查询下载信息)和DownloadManager.Request(用来请求一个下载). DownloadManager主要提供了下面几个方法: public long enqueue(Request request)把任务加

  • Android版本更新实例详解

    Android版本更新实例详解 1.导入xutils的jar包 2.在AndroidManifest.xml中添加权限 3.选择下载的路径,和下载apk文件的网址 private String url="http://www.oschina.NET/uploads/osc-Android-v2.6.4-release.apk"; private String sdPath = "/sdcard/" + System.currentTimeMillis() + &qu

  • Android程序版本更新之通知栏更新下载安装

    Android应用检查版本更新后,在通知栏下载,更新下载进度,下载完成自动安装,效果图如下: •检查当前版本号 AndroidManifest文件中的versionCode用来标识版本,在服务器放一个新版本的apk,versioncode大于当前版本,下面代码用来获取versioncode的值 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); int

  • vmware虚拟机安装安卓Android x86的方法步骤

    有时候只是想测试一个app,又不想在手机上做个测试,这个时候我们就可以用虚拟机来完成这件事情.首先到官网上去下载一个安卓系统(https://www.android-x86.org/),我这里用:android-x86-9.0-rc1.iso做演示. 或者选择本地安卓系统下载地址:https://www.jb51.net/softs/203311.html 在提供一个VMware15的下载地址:https://www.jb51.net/softs/638385.html VMware15 for

  • 安卓(Android)开发之统计App启动时间

    前言 作为 Android 开发者,想必多多少少要接触启动速度优化相关的事情,当用户越来越多,产品的功能也随着迭代越来越多,App 逐渐变得臃肿是一件很常见的现象,甚至可以说是不可避免的现象,随之而来的工作就是优化 App 性能,其中最主要的一项就是启动速度优化.但本文的主角并不是启动速度优化,而是启动时间统计. 一.启动类型 工欲善其事,必先利其器.想要优化 App 的启动速度,必须有准确衡量启动时间的方法,否则优化完之后效果怎样,自己都不知道,说出去别人也不信服不是.在做 App 启动时间统

  • 安卓(Android)实现3DTouch效果

    本篇博客要做的效果图: 来个低质量动图: 这个动图效果不是很好,实际上模糊效果应该是像上面第一张图那样的,后面会放出代码,有兴趣的可以试着运行一下看看效果. 先说一下思路,我们要实现这个效果其实只需要掌握几个东西: 1.屏幕截图 2.模糊高斯模糊) 3.添加视图 4.弹出动画 5.处理长按事件 6.优化(模糊速度和强度) 流程:当用户长按一个Item的时候,我们先截取一张当前屏幕的图片,接着将这张图片进行压缩后再进行高斯模糊,再覆盖在整个布局上面(包括覆盖Toolbar),这样界面模糊的效果就出

  • Android HandlerThread使用方法详解

    Android HandlerThread使用方法详解 HandlerThread 继承自Thread,内部封装了Looper. 首先Handler和HandlerThread的主要区别是:Handler与Activity在同一个线程中,HandlerThread与Activity不在同一个线程,而是别外新的线程中(Handler中不能做耗时的操作). 用法: import android.app.Activity; import android.os.Bundle; import androi

  • Android RecyclerView使用方法详解

    本文为大家分享了Android RecyclerView使用方法,供大家参考,具体内容如下 1.RecyclerView 是在Android support - v7 里面提供的 新的列表组件,用来替代传统的ListView. . 要使用RecyclerView 需要给我工程添加 support:recycle-v7 的支持: app 右键 - Open Module Settings - Dependencies(依赖项) - 点 + 号 - 添加一个库 upport:recycle-v7 

  • Android Notification使用方法总结

    Android Notification使用方法总结 一. 基本使用 1.构造notification NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(appContext) .setSmallIcon(appContext.getApplicationInfo().icon) .setWhen(System.currentTimeMillis()) .setAutoCancel(true)//当点击通知的

  • 总结安卓(Android)中常用的跳转工具

    话不多说了,直接上代码,这篇文章包含了一些基本的并且常用的跳转工具,一起来看看吧. 首先,这是需要的对应的权限. <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="

  • Android使用setCustomTitle()方法自定义对话框标题

    Android有自带的对话框标题,但是不太美观,如果要给弹出的对话框设置一个自定义的标题,使用AlertDialog.Builder的setCustomTitle()方法. 运行效果如下,左边是点击第一个按钮,弹出Android系统自带的对话框(直接用setTitle()设置标题):右边是点击第二个按钮,首先inflate一个View,然后用setCustomTitle()方法把该View设置成对话框的标题. 定义一个对话框标题的title.xml文件: <?xml version="1.

  • 详解Android中Drawable方法

    本文为大家分享了Android中Drawable方法的详细使用方法,供大家参考,具体内容如下 1. BitmapDrawable相关方法: 新建在drawable目录下面,示例如下: <bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:antialias="true" android:dither="true" android:filter=&

  • Android发送邮件的方法实例详解

    本文实例讲述了Android发送邮件的方法.分享给大家供大家参考,具体如下: 在android手机中实现发送邮件的功能也是不可缺少的.如何实现它呢?下面以简单的例子进行说明. 程序如下: import java.util.regex.Matcher; import java.util.regex.Pattern; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import

随机推荐