背景

很多Android应用都内置了新版本检测与在线更新功能,这个简单的功能主要包括检测、下载、安装三个环节,演示效果如下:

下载完成以后,自动打开apk,跳到安装界面,交由用户操作:

思路

想要实现上述功能,主要是分三个步骤来进行:

  1. App端向服务端发送网络请求,获取App的最新版本号信息,进行比较,如果服务端返回的版本号大于当前App的版本号,则开启第二步,下载新版本App;
  2. 有新版本App时,开启下载,并在界面上给出下载进度提示,增加交互性;
  3. 在下载达到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端的版本更新相对比较自由,不受应用商店的限制。实现起来思路清晰,各环节一步步走下来还算简单,只是这其中有几点需要开发者注意:

  1. 这个的版本号versionCode对应的是build.gradle中的versionCode不是versionName,Android系统也是根据versionCode来确定安装的应用是否为新版本;
  2. 想要在进度条中准确显示下载进度的话,App在下载时应能够读取到apk的大小,如果apk是以静态资源形式提供的,还比较方便,一般从web服务器上都能够读到,如上述的代码body.contentLength()。如果是通过从服务端的文件流接口返回的话,一定要让文件流接口正确返回Http请求的Content-Length属性,否则无法读取到apk的大小,就无法准确的表达进度了。
  3. 上述的演示操作是用户主动更新,如果想要做后台无交互的自动更新,则只需要修改一个构造参数,使用new UpdateManager(this, UpdateManager.CHECK_AUTO).checkUpdate()即可,检测过程不会有loading效果。
  4. 动态权限申请、Dialog定制等不是本文的重点,但源码完整可用,包含这部分内容。
  5. 服务端版本配置文件和下载程序代码,都放置在源码的versionConfig文件夹内,仅供参考。

源码下载

见:http://github.com/ahuyangdong/VersionDownload

Android实现App版本检测、下载与安装新版本apk相关推荐

  1. Android SDK 2.1 下载与安装教程

    Android SDK 2.1 下载与安装教程 一.Google已经发布了Android SDK 2.1版本.下面给朋友们介绍一下安装2.1的 Android 模拟器 Emulator模拟器的方法: ...

  2. Android Studio和SDK下载、安装和环境变量配置(全网最详细步骤)

    序言:工欲善其事,必先利其器,无论你是搞Android开发的.测试App软件的.App爬虫等都需要建立Android环境 目录 一.安装Java JDK 1.1 首先必须安装Java JDK 1.2 ...

  3. Android的app inventor下载地址

    Android的app inventor出来了,传说中的"所见即所得",不过听说业界对这款软件批评不绝, 说这会导致大量的垃圾应用产生.说的也有道理,不过先看看效果吧,正在下呢,先 ...

  4. 探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法

    前言 相信这样一个问题,大家都不会陌生, "有什么的方法可以使Android的程序APK不用安装,而能够直接启动". 发现最后的结局都是不能实现这个美好的愿望,而腾讯Android ...

  5. 【边做项目边学Android】手机安全卫士04_02:从服务器下载并安装新版本安装包

    文件下载 1. 下载文件业务类 下载文件的操作也属于业务方法,所以在com.liuhao.mobilesafe.engine中创建一个DownloadFileTask下载文件的类. 其中的getFil ...

  6. Android 自己实现更新下载自动安装

    1.一些公司开发完一款App之后可能并不会去上架App商店,但事后期也需要定时进行维护更新,所以会选择把打包好的apk 发布到自己的服务器,然后在数据库建一个版本号的表,然后剩下的就交给你androi ...

  7. android自定义app图标下载,安卓换图标

    安卓换图标app又叫换图标app,是一款可以快速为安卓手机用户替换手机图标的软件,换图标变得更简单哦!平台上有更多的手机app图标可以选择,也可以自己设置喜欢的图标图片,超级实用,打造个性化桌面,快来 ...

  8. android command line tools 下载,Mac 安装 Android commandlinetools 各种报错的问题

    解压后直接运行 sdkmanager 各种报错: $ ./sdkmanager --update Warning: Could not create settings java.lang.Illega ...

  9. android下载并安装最新apk

    package com.hbk.layoutdemo;import java.io.File; import java.io.FileOutputStream; import java.io.Inpu ...

  10. android 模拟器 启动,android开发之启动模拟器并安装游戏apk

    本文不讲环境设置,也不讲程序代码,咱们想讲如何把一个游戏APK文件,在模拟器上跑起来! 首先到网上下几个ANDROID的游戏到本地保存, 然后启动模拟器! 启动模拟器用命令行 CMD-> CD ...

最新文章

  1. express给html设置缓存,webpack + express 实现文件精确缓存
  2. 复旦大学邱锡鹏教授等「Transformers全面综述」论文
  3. 还是自己写的东西比较放心
  4. 理解 Android 的 Binder 机制
  5. 搞机器学习需要数学基础吗?
  6. 改革开放成就我 感谢祖国
  7. tolua打包Android后路径出错,unity+tolua 64位android崩溃排查过程记录
  8. java 实现数据结构之队列
  9. 大家一起做训练 第一场 A Next Test
  10. vue写js代码_vue.js弹出式音乐播放器特效代码
  11. 《java程序设计》第十一周学习小结 201671010130
  12. 乐高科技系列搭建指南 pdf_近30年十辆乐高科技系列摩托车回顾_积木
  13. C++ explicit禁止单参构造函数隐式调用
  14. 第三章 数据链路层[课后习题+练习题]
  15. 使用javamail通过代理发送邮件
  16. python调用window dll和linux so例子
  17. QT使用tableWidget显示双排列表 并且选中用红框圈出来
  18. 电信行业大数据应用的四个方向
  19. [笔记]Open3D基础知识及例程demo
  20. ORB-SLAM2 ORBmatcher.cc读代码一

热门文章

  1. Ruff自主研发的NB-IoT智能网关获得联通实验室测试通过
  2. 微商选择满意商品的方法
  3. 如何快速调整SMT贴片编程中的特殊元件角度?
  4. Android ndk开发入门集锦一
  5. Java中什么不是线程状态_并发基础(四) java中线程的状态
  6. linux---操作命令/文件与目录的权限/软连接/
  7. 一文搞懂JVM内存结构
  8. 腾讯网课下载.m3u8下载器流媒体下载
  9. 计算机快捷键大全列表6,快捷键大全excel
  10. IO、NIO和AIO的区别