〇、前言
应用版本更新作为Android应用的基础功能是每个应用都必须具有的,这个功能实现起来也有多种方式。前段时间我们项目改版,重新梳理了应用更新的逻辑,功能本身是比较简单的,但是各种可能的异常情况还是挺多的,特此进行记录。
一、使用OkHttp实现应用版本更新
主要有三个方法:首先检测是否有新的版本,然后进行APK文件下载,最后安装新的APK。
检测新版本的方法如下:
/**
* 检测是否有新的版本,如有则进行下载更新:
* 1. 请求服务器, 获取数据;2. 解析json数据;3. 判断是否有更新;4. 弹出升级弹窗或直接进入主页面
*/
private void checkVersion() {
showLaunchInfo("正在检测是否有新版本...", false);
String checkUrl = "http://localhost:8080/AndroidAPK/AndroidUpdate.json";
OkHttpClient okHttpClient = new OkHttpClient();//创建 OkHttpClient 对象
Request request = new Request.Builder().url(checkUrl).build();//创建 Request
okHttpClient.newCall(request).enqueue(new Callback() {//发送请求
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.w(TAG, "onFailure: e = " + e.getMessage());
mProcess = 30;
showLaunchInfo("新版本检测失败,请检查网络!", true);
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
try {
Log.w(TAG, "onResponse: response = " + response);
mProcess = 30;
final ResponseBody responseBody = response.body();
if (response.isSuccessful() && responseBody != null) {
final String responseString = responseBody.string();
Log.w(TAG, "onResponse: responseString = " + responseString);
//解析json
final JSONObject jo = new JSONObject(responseString);
final String versionName = jo.getString("VersionName");
final int versionCode = jo.getInt("VersionCode");
final String versionDes = jo.getString("VersionDes");
final String versionUrl = jo.getString("VersionUrl");
//本地版本号和服务器进行比对, 如果小于服务器, 说明有更新
if (BuildConfig.VERSION_CODE < versionCode) {
//本地版本小于服务器版本,存在新版本
showLaunchInfo("检测到新版本!", false);
progressBar.setProgress(mProcess);
//有更新, 弹出升级对话框
showUpdateDialog(versionDes, versionUrl);
} else {
showLaunchInfo("该版本已是最新版本,正在初始化项目...", true);
}
} else {
showLaunchInfo("新版本检测失败,请检查服务!", true);
}
} catch (Exception e) {
e.printStackTrace();
showLaunchInfo("新版本检测出现异常,请检查服务!", true);
}
}
});
}
下载APK文件的方法如下:
private void downloadNewApk(String apkName) {
showLaunchInfo("检测到新版本,正在下载...", false);
final File downloadDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
if (downloadDir != null && downloadDir.exists() && downloadDir.isDirectory()) {
//删除(/storage/emulated/0/Android/data/包名/files/Download)文件夹下的所有文件,避免一直下载文件堆积
File[] files = downloadDir.listFiles();
if (files != null) {
for (final File file : files) {
if (file != null && file.exists() && file.isFile()) {
boolean delete = file.delete();
}
}
}
}
//显示进度条
final ProgressDialog dialog = new ProgressDialog(this);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);//水平方向进度条, 可以显示进度
dialog.setTitle("正在下载新版本...");
dialog.setCancelable(false);
dialog.show();
//APK文件路径
final String url = "http://localhost:7090/AndroidAPK/" + apkName;
Request request = new Request.Builder().url(url).build();
new OkHttpClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
String strFailure = "新版本APK下载失败";
showLaunchInfo(strFailure, false);
showFailureDialog(strFailure, apkName);
dialog.dismiss();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
try {
final ResponseBody responseBody = response.body();
if (response.isSuccessful() && responseBody != null) {
final long total = responseBody.contentLength();
final InputStream is = responseBody.byteStream();
final File file = new File(downloadDir, apkName);
final FileOutputStream fos = new FileOutputStream(file);
int len;
final byte[] buf = new byte[2048];
long sum = 0L;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
float downloadProgress = (sum * 100F / total);
dialog.setProgress((int) downloadProgress);//下载中,更新进度
progressBar.setProgress(mProcess + (int) (downloadProgress * 0.7));
}
fos.flush();
responseBody.close();
is.close();
fos.close();
installAPKByFile(file);//下载完成,开始安装
} else {
String strFailure = "新版本APK获取失败";
showLaunchInfo(strFailure, false);
showFailureDialog(strFailure, apkName);
}
} catch (Exception e) {
e.printStackTrace();
String strException = "新版本APK下载安装出现异常";
showLaunchInfo(strException, false);
showFailureDialog(strException, apkName);
} finally {
/*正常应该在finally中进行关流操作,以避免异常情况时没有关闭IO流,导致内存泄露
*因为本场景下异常情况可能性较小,为了代码可读性直接在正常下载结束后关流
*/
dialog.dismiss();//dialog消失
}
}
});
}
安装新APK的方法如下:
/**
* 7.0以上系统APK安装
*/
private void installAPKByFile(File file) {
showLaunchInfo("新版本下载成功,正在安装中...", false);
Intent intent = new Intent(Intent.ACTION_VIEW);
//intent.putExtra("pwd", "soft_2694349");//根据密码判断升级文件是否允许更新
intent.addCategory(Intent.CATEGORY_DEFAULT);
Uri uri = FileProvider.getUriForFile(this, "com.lxb.demo0325.fileProvider", file);
//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivityForResult(intent, REQUEST_INSTALL);
}
如上,代码并不难,主要是各种可能的异常判断,为了代码的完整性,我将整个应用版本更新Demo进行了上传:https://github.com/beita08/AppUpdate
其中,服务端配置的更新文件,可以是Json形式的:
{
"VersionName":"3.1.1.2",
"VersionCode":545,
"VersionDes":"发现新版本, 赶紧更新吧!",
"VersionUrl":"ApkName"
}
参考资料:https://www.cnblogs.com/xiaoxiaoqingyi/p/7003241.html
二、使用三方服务进行应用版本更新
应用版本更新作为通用的统一功能也可以使用三方提供的服务进行,最常用的就是腾讯的bugly。
腾讯Bugly是腾讯为开发者提供的一种三方服务,主要包含有:异常上报、运营统计、应用升级等三大功能,对于我们可以使用其应用升级和异常上报功能,官网地址为:https://bugly.qq.com/v2/。首先使用Bugly功能需要在客户端集成其相关SDK:
bugly功能介绍:
1、应用升级:
将我们更新的apk文件上传至bugly后台,设置下发策略(其中可选强制升级和下发上限等,也可填写更新说明),客户端检测到bugly后台有新版本就会进行下载。
应用升级官网介绍连接:https://bugly.qq.com/docs/introduction/app-upgrade-introduction/?v=20180709165613
2、异常上报
客户端集成后,对于线上用户使用过程中的出现的异常问题,会汇总至bugly后台自动进行汇总分类,便于分析用户端出现的问题。(此日志在用户端设备上),bugly的推出初衷就是汇总异常信息。
异常上报官网介绍连接:https://bugly.qq.com/docs/introduction/bugly-introduction/?v=20181014122344#_3
bugly优缺点分析:
优点:
1、应用升级使用第三方服务后不需要占用我们自己的服务器资源,升级并发量也有保障。
2、异常上报可以监测用户端异常情况,这部分我们平时自己较难检测。
缺点:
1、信息安全问题:客户端需集成相关SDK并且需要将我们的APK文件上传至三方服务器上;
2、更新应用时平板终端需要连接bugly后台外网;
3、我们各个地区的apk文件需要进行区分,避免下载时串线(都从bugly后台下载)。