Android实现App版本检测、下载与安装新版本apk
背景
很多Android应用都内置了新版本检测与在线更新功能,这个简单的功能主要包括检测、下载、安装三个环节,演示效果如下:
下载完成以后,自动打开apk,跳到安装界面,交由用户操作:
思路
想要实现上述功能,主要是分三个步骤来进行:
- App端向服务端发送网络请求,获取App的最新版本号信息,进行比较,如果服务端返回的版本号大于当前App的版本号,则开启第二步,下载新版本App;
- 有新版本App时,开启下载,并在界面上给出下载进度提示,增加交互性;
- 在下载达到100%的进度时,通过代码打开apk实现安装。
实现
1. 版本检测
版本检测就是通过发送网络请求至App的服务端,从服务端查询到最新版App的版本号是多少,一般来说,可以通过请求静态资源(手动配置文件等)或动态接口的方式来获取最新的版本号。
- 静态资源的话主要就是在服务端放置一个可以被访问的配置文件,其中写明了最新的版本号是多少;
动态接口的话就是服务端维护一个接口,可以返回版本号,好处就是可以与数据库结合,做一些更加复杂的操作,例如维护版本更新记录等。
在本篇文章里面,为了简单表达,我们使用第一种静态资源的方式,在服务端放置一个文本文件version,内容为JSON格式。其访问地址为http://host/app/version,访问后得到的内容形如:
{"versionCode": 1,"fileName" : "abc-20210806.apk"
}
其中,versionCode是最新版本App的versionCode(Android应用的配置属性),fileName是最新版App的文件名称,用来配合着做文件下载。
App端检测版本的代码:
RetrofitRequest.sendGetRequest(Constant.URL_APP_VERSION, new RetrofitRequest.ResultHandler(context) {...@Overridepublic void onResult(String response) {if (response == null || response.trim().length() == 0) {Toast.makeText(context, R.string.layout_version_no_new, Toast.LENGTH_SHORT).show();LoadingDialog.close();return;}try {JSONObject jsonObject = new JSONObject(response);if (!jsonObject.has("versionCode") || !jsonObject.has("fileName")) {Toast.makeText(context, R.string.layout_version_no_new, Toast.LENGTH_SHORT).show();LoadingDialog.close();return;}newVersionCode = jsonObject.getInt("versionCode");newFileName = jsonObject.getString("fileName");int versionCode = VersionUtil.getVersionCode(context);LoadingDialog.close();if (newVersionCode > versionCode) {showUpdateDialog(newFileName);} else {if (!isAutoCheck) {Toast.makeText(context, R.string.layout_version_no_new, Toast.LENGTH_SHORT).show();}}} catch (JSONException e) {Toast.makeText(context, R.string.layout_version_no_new, Toast.LENGTH_SHORT).show();LoadingDialog.close();}}
...
});
其中newVersionCode > versionCode
就是最服务器端的版本号与本App的版本进行比较的代码,根据比较结果,如果当前不是最新版本,则显示更新提醒对话框showUpdateDialog(newFileName)
。
private void showUpdateDialog(final String fileName) {ConfirmDialog dialog = new ConfirmDialog(context, new ConfirmDialog.OnClickListener() {@Overridepublic void onConfirm() {showDownloadDialog(fileName);}});dialog.setTitle(R.string.note_confirm_title);dialog.setContent(R.string.layout_version_new);dialog.setConfirmText(R.string.layout_yes);dialog.setCancelText(R.string.layout_no);dialog.show();
}
2. 下载新版本apk
用户在更新对话框中点击“是”时,表示需要下载最新版apk,此时显示下载进度对话框,并启动下载,实时刷新下载进度:
private void showDownloadDialog(String fileName) {Builder builder = new Builder(context);View view = LayoutInflater.from(context).inflate(R.layout.dialog_download, null);proDownload = (ProgressBar) view.findViewById(R.id.pro_download);tvPercent = (TextView) view.findViewById(R.id.txt_percent);tvKbNow = (TextView) view.findViewById(R.id.txt_kb_now);tvKbAll = (TextView) view.findViewById(R.id.txt_kb_all);Button btnCancel = (Button) view.findViewById(R.id.btn_cancel);btnCancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (downloadDialog != null) {downloadDialog.dismiss();}cancelUpdate = true;}});downloadDialog = builder.create();downloadDialog.setCanceledOnTouchOutside(false);downloadDialog.show();downloadDialog.getWindow().setContentView(view);downloadApk(fileName);
}
下载的后台线程和前端百分比更新动作:
private void downloadApk(String fileName) {ExecutorService executorService = Executors.newFixedThreadPool(1);Retrofit retrofit = new Retrofit.Builder().baseUrl(Constant.URL_CONTRACT_BASE).callbackExecutor(executorService).build();String url = String.format(Constant.URL_APP_DOWNLOAD, fileName);FileRequest fileRequest = retrofit.create(FileRequest.class);Call<ResponseBody> call = fileRequest.download(url);call.enqueue(new Callback<ResponseBody>() {@Overridepublic void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {if (response.isSuccessful()) {if (writeResponseBodyToDisk(response.body())) {downloadDialog.dismiss();} else {mHandler.sendEmptyMessage(DOWNLOAD_ERROR);}} else {mHandler.sendEmptyMessage(DOWNLOAD_ERROR);}}@Overridepublic void onFailure(Call<ResponseBody> call, Throwable t) {mHandler.sendEmptyMessage(DOWNLOAD_ERROR);}});
}private boolean writeResponseBodyToDisk(ResponseBody body) {savePath = StorageUtil.getDownloadPath(context);File apkFile = new File(savePath, newFileName);InputStream inputStream = null;OutputStream outputStream = null;try {byte[] fileReader = new byte[4096];long fileSize = body.contentLength();long fileSizeDownloaded = 0;inputStream = body.byteStream();outputStream = new FileOutputStream(apkFile);BigDecimal bd1024 = new BigDecimal(1024);totalByte = new BigDecimal(fileSize).divide(bd1024, BigDecimal.ROUND_HALF_UP).setScale(0).intValue();while (!cancelUpdate) {int read = inputStream.read(fileReader);if (read == -1) {mHandler.sendEmptyMessage(DOWNLOAD_FINISH);break;}outputStream.write(fileReader, 0, read);fileSizeDownloaded += read;progress = (int) (((float) (fileSizeDownloaded * 100.0 / fileSize)));downByte = new BigDecimal(fileSizeDownloaded).divide(bd1024, BigDecimal.ROUND_HALF_UP).setScale(0).intValue();mHandler.sendEmptyMessage(DOWNLOAD_ING);}outputStream.flush();return true;} catch (Exception e) {e.printStackTrace();return false;} finally {if (outputStream != null) {try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}
}private void showProgress() {proDownload.setProgress(progress);tvPercent.setText(progress + "%");tvKbAll.setText(totalByte + "Kb");tvKbNow.setText(downByte + "Kb");
}
3. 安装apk
最新版本的apk下载完成后,调用安装代码执行安装动作。新旧版Android SDK安装方式略有区别,详见代码:
private void installApk() {File apkFile = new File(savePath, newFileName);if (!apkFile.exists()) {return;}Intent intent = new Intent(Intent.ACTION_VIEW);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile);intent.setDataAndType(contentUri, "application/vnd.android.package-archive");} else {intent.setDataAndType(Uri.parse("file://" + apkFile.toString()), "application/vnd.android.package-archive");}context.startActivity(intent);
}
总结
Android端的版本更新相对比较自由,不受应用商店的限制。实现起来思路清晰,各环节一步步走下来还算简单,只是这其中有几点需要开发者注意:
- 这个的版本号
versionCode
对应的是build.gradle中的versionCode不是versionName,Android系统也是根据versionCode来确定安装的应用是否为新版本; - 想要在进度条中准确显示下载进度的话,App在下载时应能够读取到apk的大小,如果apk是以静态资源形式提供的,还比较方便,一般从web服务器上都能够读到,如上述的代码
body.contentLength()
。如果是通过从服务端的文件流接口返回的话,一定要让文件流接口正确返回Http请求的Content-Length属性,否则无法读取到apk的大小,就无法准确的表达进度了。 - 上述的演示操作是用户主动更新,如果想要做后台无交互的自动更新,则只需要修改一个构造参数,使用
new UpdateManager(this, UpdateManager.CHECK_AUTO).checkUpdate()
即可,检测过程不会有loading效果。 - 动态权限申请、Dialog定制等不是本文的重点,但源码完整可用,包含这部分内容。
- 服务端版本配置文件和下载程序代码,都放置在源码的versionConfig文件夹内,仅供参考。
源码下载
见:http://github.com/ahuyangdong/VersionDownload
Android实现App版本检测、下载与安装新版本apk相关推荐
- Android SDK 2.1 下载与安装教程
Android SDK 2.1 下载与安装教程 一.Google已经发布了Android SDK 2.1版本.下面给朋友们介绍一下安装2.1的 Android 模拟器 Emulator模拟器的方法: ...
- Android Studio和SDK下载、安装和环境变量配置(全网最详细步骤)
序言:工欲善其事,必先利其器,无论你是搞Android开发的.测试App软件的.App爬虫等都需要建立Android环境 目录 一.安装Java JDK 1.1 首先必须安装Java JDK 1.2 ...
- Android的app inventor下载地址
Android的app inventor出来了,传说中的"所见即所得",不过听说业界对这款软件批评不绝, 说这会导致大量的垃圾应用产生.说的也有道理,不过先看看效果吧,正在下呢,先 ...
- 探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法
前言 相信这样一个问题,大家都不会陌生, "有什么的方法可以使Android的程序APK不用安装,而能够直接启动". 发现最后的结局都是不能实现这个美好的愿望,而腾讯Android ...
- 【边做项目边学Android】手机安全卫士04_02:从服务器下载并安装新版本安装包
文件下载 1. 下载文件业务类 下载文件的操作也属于业务方法,所以在com.liuhao.mobilesafe.engine中创建一个DownloadFileTask下载文件的类. 其中的getFil ...
- Android 自己实现更新下载自动安装
1.一些公司开发完一款App之后可能并不会去上架App商店,但事后期也需要定时进行维护更新,所以会选择把打包好的apk 发布到自己的服务器,然后在数据库建一个版本号的表,然后剩下的就交给你androi ...
- android自定义app图标下载,安卓换图标
安卓换图标app又叫换图标app,是一款可以快速为安卓手机用户替换手机图标的软件,换图标变得更简单哦!平台上有更多的手机app图标可以选择,也可以自己设置喜欢的图标图片,超级实用,打造个性化桌面,快来 ...
- android command line tools 下载,Mac 安装 Android commandlinetools 各种报错的问题
解压后直接运行 sdkmanager 各种报错: $ ./sdkmanager --update Warning: Could not create settings java.lang.Illega ...
- android下载并安装最新apk
package com.hbk.layoutdemo;import java.io.File; import java.io.FileOutputStream; import java.io.Inpu ...
- android 模拟器 启动,android开发之启动模拟器并安装游戏apk
本文不讲环境设置,也不讲程序代码,咱们想讲如何把一个游戏APK文件,在模拟器上跑起来! 首先到网上下几个ANDROID的游戏到本地保存, 然后启动模拟器! 启动模拟器用命令行 CMD-> CD ...
最新文章
- express给html设置缓存,webpack + express 实现文件精确缓存
- 复旦大学邱锡鹏教授等「Transformers全面综述」论文
- 还是自己写的东西比较放心
- 理解 Android 的 Binder 机制
- 搞机器学习需要数学基础吗?
- 改革开放成就我 感谢祖国
- tolua打包Android后路径出错,unity+tolua 64位android崩溃排查过程记录
- java 实现数据结构之队列
- 大家一起做训练 第一场 A Next Test
- vue写js代码_vue.js弹出式音乐播放器特效代码
- 《java程序设计》第十一周学习小结 201671010130
- 乐高科技系列搭建指南 pdf_近30年十辆乐高科技系列摩托车回顾_积木
- C++ explicit禁止单参构造函数隐式调用
- 第三章 数据链路层[课后习题+练习题]
- 使用javamail通过代理发送邮件
- python调用window dll和linux so例子
- QT使用tableWidget显示双排列表 并且选中用红框圈出来
- 电信行业大数据应用的四个方向
- [笔记]Open3D基础知识及例程demo
- ORB-SLAM2 ORBmatcher.cc读代码一