Android App自动更新基本上是每个App都需具备的功能,参考网上各种资料,自己整理了下,先来看看大致的界面:

一、实现思路:

1.发布Android App时,都会生成output-metadata.json文件和对应的apk文件。(不知道如何打包发布apk,可以网上搜一下)

2.output-metadata.json文件里面就记录了发布的程序版本,通过读取此文件来判断是否需要进行更新。

3.更新过程包括:

①下载Apk文件。

②安装Apk文件。

二、实现步骤:

1.申明权限:由于自动更新需要访问网络,下载更新包,执行安装操作,所以需要申明以下权限:

    <!-- 网络权限 --><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><!-- 在SDCard中创建与删除文件权限 --><uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"tools:ignore="ProtectedPermissions" /><!-- 存储权限 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 安装APK权限 --><uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

另外,配置AndroidManifest.xml文件时,还有2个细节需注意下:

(1)由于App更新包放在非https的网站下,需要配置app允许访问非http的网站。

(2)安装App时,Android7.0以上版本需要通过FileProvider方式进行安装,详情可以参考 通过代码安装APK的方法详解

文件:file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><!--安装包文件存储路径--><external-files-pathname="my_download"path="Download" /><external-pathname="."path="." />
</paths>

文件:network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config><base-config cleartextTrafficPermitted="true" />
</network-security-config>

2.权限配置完后,现在就开始制作更新程序了。添加更新进度布局。

文件:progress.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:id="@+id/titleBar"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><TextViewandroid:id="@+id/txtStatus"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="状态"android:textSize="10sp"android:textStyle="normal" /><ProgressBarandroid:id="@+id/progress"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_toLeftOf="@id/txtStatus" /></LinearLayout>
</LinearLayout>

里面就一个显示百分比的文本框,和一个进度条。

3.现在进入更新过程的核心操作阶段了,把检查更新,下载apk,安装apk等操作封装成了一个类。

package com.qingshan.blog;import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;import androidx.core.content.FileProvider;import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class AutoUpdater {// 下载安装包的网络路径private String apkUrl = "http://qingshanboke.com/uploadfiles/***/rc.***.blog/";protected String checkUrl = apkUrl + "output-metadata.json";// 保存APK的文件名private static final String saveFileName = "my.apk";private static File apkFile;// 下载线程private Thread downLoadThread;private int progress;// 当前进度// 应用程序Contextprivate Context mContext;// 是否是最新的应用,默认为falseprivate boolean isNew = false;private boolean intercept = false;// 进度条与通知UI刷新的handler和msg常量private ProgressBar mProgress;private TextView txtStatus;private static final int DOWN_UPDATE = 1;private static final int DOWN_OVER = 2;private static final int SHOWDOWN = 3;public AutoUpdater(Context context) {mContext = context;apkFile = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), saveFileName);}public void ShowUpdateDialog() {AlertDialog.Builder builder = new AlertDialog.Builder(mContext);builder.setTitle("软件版本更新");builder.setMessage("有最新的软件包,请下载并安装!");builder.setPositiveButton("立即下载", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {ShowDownloadDialog();}});builder.setNegativeButton("以后再说", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.dismiss();}});builder.create().show();}private void ShowDownloadDialog() {AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);dialog.setTitle("软件版本更新");LayoutInflater inflater = LayoutInflater.from(mContext);View v = inflater.inflate(R.layout.progress, null);mProgress = (ProgressBar) v.findViewById(R.id.progress);txtStatus = v.findViewById(R.id.txtStatus);dialog.setView(v);dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {intercept = true;}});dialog.show();DownloadApk();}/*** 检查是否更新的内容*/public void CheckUpdate() {new Thread(new Runnable() {@Overridepublic void run() {String localVersion = "1";try {localVersion = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}String versionName = "1";String outputFile = "";String config = doGet(checkUrl);if (config != null && config.length() > 0) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {Matcher m = Pattern.compile("\"outputFile\":\\s*\"(?<m>[^\"]*?)\"").matcher(config);if (m.find()) {outputFile = m.group("m");}m = Pattern.compile("\"versionName\":\\s*\"(?<m>[^\"]*?)\"").matcher(config);if (m.find()) {String v = m.group("m");versionName = m.group("m").replace("v1.0.", "");}}}if (Long.parseLong(localVersion) < Long.parseLong(versionName)) {apkUrl = apkUrl + outputFile;mHandler.sendEmptyMessage(SHOWDOWN);} else {return;}}}).start();}/*** 从服务器下载APK安装包*/public void DownloadApk() {downLoadThread = new Thread(DownApkWork);downLoadThread.start();}private Runnable DownApkWork = new Runnable() {@Overridepublic void run() {URL url;try {url = new URL(apkUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.connect();int length = conn.getContentLength();InputStream ins = conn.getInputStream();FileOutputStream fos = new FileOutputStream(apkFile);int count = 0;byte[] buf = new byte[1024];while (!intercept) {int numread = ins.read(buf);count += numread;progress = (int) (((float) count / length) * 100);// 下载进度mHandler.sendEmptyMessage(DOWN_UPDATE);if (numread <= 0) {// 下载完成通知安装mHandler.sendEmptyMessage(DOWN_OVER);break;}fos.write(buf, 0, numread);}fos.close();ins.close();} catch (Exception e) {e.printStackTrace();}}};/*** 安装APK内容*/public void installAPK() {try {if (!apkFile.exists()) {return;}Intent intent = new Intent(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//安装完成后打开新版本intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//判断版本大于等于7.0//如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24,使用FileProvider兼容安装apkString packageName = mContext.getApplicationContext().getPackageName();String authority = new StringBuilder(packageName).append(".fileprovider").toString();Uri apkUri = FileProvider.getUriForFile(mContext, authority, apkFile);intent.setDataAndType(apkUri, "application/vnd.android.package-archive");} else {intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");}mContext.startActivity(intent);android.os.Process.killProcess(android.os.Process.myPid());//安装完之后会提示”完成” “打开”。} catch (Exception e) {}}private Handler mHandler = new Handler() {public void handleMessage(android.os.Message msg) {switch (msg.what) {case SHOWDOWN:ShowUpdateDialog();break;case DOWN_UPDATE:txtStatus.setText(progress + "%");mProgress.setProgress(progress);break;case DOWN_OVER:Toast.makeText(mContext, "下载完毕", Toast.LENGTH_SHORT).show();installAPK();break;default:break;}}};public static String doGet(String httpurl) {HttpURLConnection connection = null;InputStream is = null;BufferedReader br = null;String result = null;try {URL url = new URL(httpurl);connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(15000);connection.setReadTimeout(60000);connection.connect();if (connection.getResponseCode() == 200) {is = connection.getInputStream();br = new BufferedReader(new InputStreamReader(is, "UTF-8"));StringBuffer sbf = new StringBuffer();String temp = null;while ((temp = br.readLine()) != null) {sbf.append(temp);sbf.append("\r\n");}result = sbf.toString();}} catch (MalformedURLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if (null != br) {try {br.close();} catch (IOException e) {e.printStackTrace();}}if (null != is) {try {is.close();} catch (IOException e) {e.printStackTrace();}}connection.disconnect();}return result;}
}

注意:上面的 apkUrl即是发布更新包存放的网络路径。其他操作可以参考代码注释,就不再赘述了。

这里有一个小技巧,可以设置每次打包时,程序按当前时间进行版本号设置。需修改build.gradle文件,像下面这样:

plugins {id 'com.android.application'
}
android {compileSdkVersion 28buildToolsVersion "30.0.3"defaultConfig {applicationId "com.qingshan.blog"minSdkVersion 23targetSdkVersion 30versionCode 1versionName "${releaseTime()}"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'android.applicationVariants.all { variant ->variant.outputs.all {outputFileName = "my_${releaseTime()}.apk"}}}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}
dependencies {implementation 'androidx.appcompat:appcompat:1.1.0'implementation 'com.google.android.material:material:1.1.0'implementation 'androidx.constraintlayout:constraintlayout:1.1.3'testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.1'androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'implementation 'cn.bingoogolapple:bga-qrcode-zbar:1.3.7'
}
def releaseTime() {return new Date().format("yyyyMMddHHmmss", TimeZone.getTimeZone("UTC"))
}

打包后,就可以得到类似的文件结构:

直接将这2个文件复制到发布服务器上进行发布即可。

4.在MainActivity.java中进行检查配置。在onCreate方法中加入代码:

        //检查更新try {//6.0才用动态权限if (Build.VERSION.SDK_INT >= 23) {String[] permissions = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.ACCESS_WIFI_STATE,Manifest.permission.INTERNET};List<String> permissionList = new ArrayList<>();for (int i = 0; i < permissions.length; i++) {if (ActivityCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {permissionList.add(permissions[i]);}}if (permissionList.size() <= 0) {//说明权限都已经通过,可以做你想做的事情去//自动更新AutoUpdater manager = new AutoUpdater(MainActivity.this);manager.CheckUpdate();} else {//存在未允许的权限ActivityCompat.requestPermissions(this, permissions, 100);}}} catch (Exception ex) {Toast.makeText(MainActivity.this, "自动更新异常:" + ex.getMessage(), Toast.LENGTH_SHORT).show();}

处理权限申请

@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);boolean haspermission = false;if (100 == requestCode) {for (int i = 0; i < grantResults.length; i++) {if (grantResults[i] == -1) {haspermission = true;}}if (haspermission) {//跳转到系统设置权限页面,或者直接关闭页面,不让他继续访问permissionDialog();} else {//全部权限通过,可以进行下一步操作AutoUpdater manager = new AutoUpdater(MainActivity.this);manager.CheckUpdate();}}}AlertDialog alertDialog;//打开手动设置应用权限private void permissionDialog() {if (alertDialog == null) {alertDialog = new AlertDialog.Builder(this).setTitle("提示信息").setMessage("当前应用缺少必要权限,该功能暂时无法使用。如若需要,请单击【确定】按钮前往设置中心进行权限授权。").setPositiveButton("设置", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {cancelPermissionDialog();Uri packageURI = Uri.parse("package:" + getPackageName());Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);startActivity(intent);}}).setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {cancelPermissionDialog();}}).create();}alertDialog.show();}private void cancelPermissionDialog() {alertDialog.cancel();}

至此,就完成了apk自动更新功能。

需要注意的几个地方:

1.权限申请一定要对,包括网络权限,存储权限,安装APK权限。

2.代码安装Apk时,需通过FileProvider方式进行安装。

3.程序配置了按时间生成版本号,直接对比版本号来进行判断是否需要更新。

Android APP 自动更新实现(适用Android9.0)相关推荐

  1. android通知栏应用程序更新,Android App自动更新之通知栏下载

    本文实例为大家分享了Android App自动更新通知栏下载的具体代码,供大家参考,具体内容如下 版本更新说明 这里有调用UpdateService启动服务检查下载安装包等 1. 文件下载,下完后写入 ...

  2. Android App自动更新解决方案(DownloadManager)

    Android App自动更新解决方案(DownloadManager) 参考文章: (1)Android App自动更新解决方案(DownloadManager) (2)https://www.cn ...

  3. android app 自动更新,app升级项目,新增强制更新(可静默),支持热更新(wgt),可支持高版本安卓系统...

    pure-updater 一个可以用的自动更新方案 经测试可支持 Android 9.0 已支持热更新 已支持静默的强制更新 如果您觉得还可以的话那就点个五星吧!谢谢! 已测试 android 8.0 ...

  4. android app 自动更新,AndroidUpdateDemo

    Android课程-App更新策略 @(Android) 第一节 课程介绍 概述 App更新是应用当中很常见的一个功能,基本上联网的app都应该具备这样的功能,对于更新迭代比较快速的产品,应用更新升级 ...

  5. android app自动更新界面_Android自定义view之模仿登录界面文本输入框(华为云APP)...

    好久不见!!!!!,最近终于挤出时间来更新文章了,废话不多说,直接开始. 效果图如下: 01 分析 1.组合多个控件完成此输入框静态效果 2.hint值上浮下潜动画 3.一些功能 02 步骤 01 自 ...

  6. android app实现更新功能

    功能演示 Android App自动更新基本上是每个App都需具备的功能.网上有各种更新方式,但基本都是往年的了,最近刚查资料写完一个app更新功能,发现了许多app更新的博客内没有详细说明的小问题, ...

  7. flutter APP自动更新

    flutter APP自动更新 前言 在pubspec.yaml中安装依赖 在main.dart文件中,初始化FlutterDownLoader 配置网络 在AndroidManifest.xml新增 ...

  8. Android如何实现APP自动更新

    先来看看要实现的效果图: 对于安卓用户来说,手机应用市场说满天飞可是一点都不夸张,比如小米,魅族,百度,360,机锋,应用宝等等,当我们想上线一款新版本APP时,先不说渠道打包的麻烦,单纯指上传APP ...

  9. web app升级—带进度条的App自动更新

    带进度条的App自动更新,效果如下图所示:   技术:vue.vant-ui.5+ 封装独立组件AppProgress.vue: <template><div><van- ...

最新文章

  1. java grizzly_java grizzly实现http服务器
  2. linux加密格式化吗,linux环境下给文件加密/解密的方法
  3. php视图,thinkPHP框架中视图的讲解(附代码)
  4. uva 11080(二分图染色)
  5. 佛山市南海技师学校计算机类,佛山南海信息技术学校2021年有哪些专业
  6. Linux新安装后设置root密码
  7. m40型工业机器人_工业机器人选型的9大参数
  8. Google Chrome 工程师:JavaScript 不容错过的八大优化建议
  9. Codeforces.1051G.Distinctification(线段树合并 并查集)
  10. Pandas系列(十四)数据转换函数map、apply、applymap以及分组apply
  11. mongodb的管理员和安全认证
  12. MySQL中exists和in的区别
  13. vac虚拟声卡我linux,下载_Virtual Audio Cable(虚拟声卡) V4.65 完美免费版_6z6z下载站
  14. axure原型素材模板-手机端蓝色科幻科技动态酷炫游戏大数据手机H5页面模板素材聊天
  15. mysql中身份证号判断男女人数
  16. 计算机毕业设计android的学生考勤请假app(源码+系统+mysql数据库+Lw文档)
  17. 狼的处世十大哲理(想养狼的人必应)
  18. JDBC基础操作汇总
  19. 还在用Word写论文?收下这个排版神器,轻松搞定所有公式!
  20. win7-64+usb安装

热门文章

  1. 【c语言】职工信息管理系统 包含读取写入txt文件,职工信息的增删改查
  2. 计算机分支结构语句的实验报告,c语言 实验报告三 分支结构程序设计
  3. CSS样式优先级顺序详解
  4. 无法将linkedHashMap转换为实体类和feign.FeignException$NotFound错误
  5. 3月14日云栖精选夜读 | 阿里云成为开源组织CDF创始成员,积极推动软件生态构建... 1
  6. 2023年无线蓝牙耳机排行榜,十款无线蓝牙耳机品牌推荐
  7. C. Yet Another Tournament
  8. I2S传输PCM音频数据分析总结
  9. 实用微信小程序项目源码
  10. 通用环形缓冲区 LwRB 使用指南