Android APP 自动更新实现(适用Android9.0)

您所在的位置:网站首页 安卓实现弹窗的软件是什么 Android APP 自动更新实现(适用Android9.0)

Android APP 自动更新实现(适用Android9.0)

2024-06-29 08:38| 来源: 网络整理| 查看: 265

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

一、实现思路:

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

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

3.更新过程包括:

①下载Apk文件。

②安装Apk文件。

二、实现步骤:

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

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

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

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

文件:file_paths.xml

文件:network_security_config.xml

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

文件:progress.xml

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

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;// 当前进度 // 应用程序Context private Context mContext; // 是否是最新的应用,默认为false private 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.setCancelable(false); builder.setTitle("软件版本更新"); builder.setMessage("有最新的软件包,请下载并安装!"); builder.setPositiveButton("立即下载", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ShowDownloadDialog(); } }); builder.setNegativeButton("以后再说", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.create().show(); } private void ShowDownloadDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(mContext); dialog.setCancelable(false); 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() { @Override public void onClick(DialogInterface dialog, int which) { intercept = true; } }); dialog.show(); DownloadApk(); } /** * 检查是否更新的内容 */ public void CheckUpdate() { new Thread(new Runnable() { @Override public 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*\"(?[^\"]*?)\"").matcher(config); if (m.find()) { outputFile = m.group("m"); } m = Pattern.compile("\"versionName\":\\s*\"(?[^\"]*?)\"").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() { @Override public void run() { URL url; try { //如果下载地址是HTTPS,则把这段加上,http则不需要 SSLContext sslContext = SSLContext.getInstance("SSL");//第一个参数为 返回实现指定安全套接字协议的SSLContext对象。第二个为提供者 TrustManager[] tm = {new MyX509TrustManager()}; sslContext.init(null, tm, new SecureRandom()); SSLSocketFactory ssf = sslContext.getSocketFactory(); 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 = Build.VERSION_CODES.N) {//判断版本大于等于7.0 //如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24,使用FileProvider兼容安装apk String 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即是发布更新包存放的网络路径。其他操作可以参考代码注释,就不再赘述了。

附:MyX509TrustManager.java import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; public class MyX509TrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // TODO Auto-generated method stub } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { // TODO Auto-generated method stub } @Override public X509Certificate[] getAcceptedIssuers() { // TODO Auto-generated method stub return null; } }

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

plugins { id 'com.android.application' } android { compileSdkVersion 28 buildToolsVersion "30.0.3" defaultConfig { applicationId "com.qingshan.blog" minSdkVersion 23 targetSdkVersion 30 versionCode 1 versionName "${releaseTime()}" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' android.applicationVariants.all { variant -> variant.outputs.all { outputFileName = "my_${releaseTime()}.apk" } } } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility 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 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()


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3