Android实现App版本检测、下载与安装新版本apk 您所在的位置:网站首页 下载安卓最新版本 Android实现App版本检测、下载与安装新版本apk

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

2023-07-30 17:28| 来源: 网络整理| 查看: 265

背景

很多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) { ... @Override public 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() { @Override public 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() { @Override public 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 call = fileRequest.download(url); call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { if (writeResponseBodyToDisk(response.body())) { downloadDialog.dismiss(); } else { mHandler.sendEmptyMessage(DOWNLOAD_ERROR); } } else { mHandler.sendEmptyMessage(DOWNLOAD_ERROR); } } @Override public void onFailure(Call 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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有