一、通知栏自动下载更新

XVersionUpdate是VersionUpdate的升级版,全面优化代码,提高兼容性和稳定性,增强用户体验,帮助我们快速实现版本更新功能。

(1).效果图:


更新内容:

最近更新内容:
1.修复重复下载和进度条显示异常的bug
2.新增取消下载功能
3.兼容Android8.0
4.修复优化评论中的其他问题。

(2).实现过程:

build. gradle (app)文件中注入以下依赖

dependencies {......implementation 'com.androidkun:xversionupdate:1.0.6'
}

可以在首次启动界面的Oncreate 方法下调用API检测版本更新,让其有新版本,在启动APP后自动下载更新。

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);VersionUpdateConfig.getInstance()//获取配置实例.setContext(LoginActivit这里写界面.this)//设置上下文.setDownLoadURL("https://www.armpro.cn/assets/这里是下载地址.apk")//设置文件下载链接.setNewVersion("3")//设置即将下载的APK的版本号,避免重复下载.setFileSavePath("/storage/emulated/0/")//设置文件保存路径(可不设置).setNotificationIconRes(R.drawable.ic_launcher)//设置通知图标.setNotificationSmallIconRes(R.drawable.ic_launcher)//设置通知小图标.setNotificationTitle("软件有新版本,正在下载中...")//设置通知标题.startDownLoad();//开始下载}

接下来就不用再做任何处理了,有新版本时启动APP,就会自动在通知栏显示下载进度,在下载完成后会自动跳到安装页面。

项目源码地址:https://github.com/AndroidKun/XVersionUpdate


二、主界面弹窗选择 更新/强制更新/浏览器更新(通用更新)

这个版本的代码通用性很高,代码也很少,理论适用与任意的安卓系统(Android 10版本已测试可用),最简单的源代码实现的更新服务,检测远程更新代码多种更新方式的选择,代码可修改定制性很强,支持同时多个更新链接配置进行容错处理,应用内直链直接下载时失败时会弹出提示错误弹窗,并可选择浏览器下载。可以简单使用gitee搭建静态网页当后台更新服务(源码见下方↓)。

(1).弹窗效果图:



(2).实现过程:

1. 搭建json格式网页

这里提供源码和搭建后数据展示情况,具体详细的搭建教程很简单,随便百度就可以找到,这里不再重复搭建过程。

搭建源码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>https://yirj.gitee.io/项目名称</title>
</head>
<body>
<pre><code id="json"></code></pre>
</body>
<script type="text/javascript">
let btn = document.querySelector('#json');
let data = {"hasUpdate": true,"NoIgnorable": true,"versionCode": 51,"versionName": "2.4.1","updateLog": "\n1、更改保存图片的存储路径。\n2、更改软件更新的提示模式。\n3、调整非强制更新控制方式。\n4、新增存储权限的申请授权位置。","apkUrl": "https://www.yuming.com/assets/a某o130.apk","webUrl": "https://yirj.gitee.io/update111","apkSize": "29.5MB"};
btn.textContent = JSON.stringify(data, null, 2);
</script>
</html>

搭建成的链接

https://yirj.gitee.io/项目名称

访问相关展示

{hasUpdate: true,  //是否有更新 默认trueNoIgnorable: true, //不 可忽略更新  强制:true 非强制:falseversionCode: 51,   //服务端的版本号  versionName: "2.4.1", //服务端的版本名updateLog: "\n1、更改保存图片的存储路径。\n2、更改软件更新的提示模式。\n3、调整非强制更新控制方式。\n4、新增存储权限的申请授权位置。", //更新提示内容apkUrl: "https://www.yuming.com/assets/a某o130.apk",//新版本APK直链下载地址webUrl: "https://yirj.gitee.io/update111",//浏览器更新链接,随意放(直链、蓝奏、官网均可)apkSize: "29.5MB" //新版本的大小 随意写就好
}

2. 创建file_paths.xml及修改AndroidManifest.xml

创建file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path name="external_files" path="."/>
</paths>

修改AndroidManifest.xml

    <!-- 拥有完全的网络访问权限 --><uses-permission android:name="android.permission.INTERNET" /><!-- 修改或删除您的USB存储设备中的内容 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 查看网络连接 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><application.....android:label="@string/app_name"android:requestLegacyExternalStorage="true"><providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /></provider>.........
</application>

3. 启动主界面加入检测更新代码

在软件的检测更新界面的Oncreate方法下,加入检测更新的代码。
更新弹窗出现条件hasUpdate为true且用户使用的APP的版本号<服务端的版本号,就会提示更新。
强制更新出现条件:有更新后跳转到showDialogUpdate方法,判断 NoIgnorable为true则就是强制更新。

import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
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.net.URLConnection;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;public class MainActivity extends Activity {private String TAG;public static JSONObject jSONObject = null;private static boolean hasUpdate, NoIgnorable = true; //是否有更新。 不可忽略的更新private static int versionCode = 0;private static String versionName, updateLog, apkSize, apkUrl, webUrl = "";private static String[] upl = new String[]{"https://yirj.gitee.io/更新链接1", "https://yirj.gitee.io/更新链接2"};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//忽略证书校验的相关方法,http报证书异常或者不安全链接就启用它//handleSSLHandshake();//应用内直链更新,要先动态申请获取存储权限getReadPermissions();//启动子线程--更新功能实现调用new Thread() {@Overridepublic void run() {Looper.prepare();try {GetServerJson();//hasUpdate为true 且 本程序的版本号小于服务器的版本号,那么提示用户更新if (hasUpdate & getVersionCode() < versionCode) {showDialogUpdate();//弹出提示版本更新的对话框} else {//Toast.makeText(MainActivity.this, "当前已是最新版本", Toast.LENGTH_SHORT).show();}} catch (Exception e) {e.printStackTrace();}Looper.loop();}}.start();}/*** 获取当前使用的软件包的版本号*/public int getVersionCode() {try {//获取packagemanager的实例PackageManager packageManager = getPackageManager();//getPackageName()是你当前类的包名,0代表是获取版本信息PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);Log.e("TAG", "版本号" + packInfo.versionCode);  //更新软件用的是版本号return packInfo.versionCode;} catch (Exception e) {e.printStackTrace();}return 1;}/*** 提示版本更新的对话框*/public void showDialogUpdate() {//hasUpdate为true且程序版本号<服务端版本号,提示用户更新if (NoIgnorable) { //NoIgnorable为true 就是强制更新,无“取消”按钮// 这里的属性可以一直设置,因为每次设置后返回的是一个builder对象AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setCancelable(false); //开启强制更新,无法触摸外部关闭builder.setTitle("是否升级到" + versionName + "版本?").// 设置提示框的图标setIcon(R.drawable.ic_launcher).// 设置要显示的信息setMessage("新版本大小:" + apkSize + "\n" + updateLog).// 设置确定按钮setPositiveButton("更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {loadNewVersionProgress();//程序内直接下载最新的版本程序}}).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新 @Overridepublic void onClick(DialogInterface dialog, int which) {Uri uri = Uri.parse(webUrl);Intent intent = new Intent(Intent.ACTION_VIEW, uri);startActivity(intent);finish(); //强制更新,点击后销毁应用}});// 显示对话框builder.create().show();} else {AlertDialog.Builder builder = new AlertDialog.Builder(this);
//            builder.setCancelable(false); //非强制更新,屏蔽此行,触摸外部或退出键可关闭builder.setTitle("是否升级到" + versionName + "版本?").setMessage("新版本大小:" + apkSize + "\n" + updateLog).setPositiveButton("更新", new DialogInterface.OnClickListener() {//正按钮@Overridepublic void onClick(DialogInterface dialog, int which) {loadNewVersionProgress();//下载最新的版本程序}}).setNegativeButton("取消", new DialogInterface.OnClickListener() {//负按钮@Overridepublic void onClick(DialogInterface dialog, int which) {//                        finish(); //屏蔽销毁,不做任何处理}}).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮@Overridepublic void onClick(DialogInterface dialog, int which) {Uri uri = Uri.parse(webUrl);Intent intent = new Intent(Intent.ACTION_VIEW, uri);startActivity(intent);//finish(); //屏蔽销毁,访问浏览器,程序不会退出}});// 显示对话框builder.create().show();}}//轮询验证两个更新链接,返回有效链接public static String checkUrl(String[] ltl) {String resultUrl = null;for (String url : ltl) {resultUrl = url;try {//调用检查链接是否有效的方法String result = get(url);if (result != null && result.length() != 0) {break;}} catch (Exception e) {e.printStackTrace();}}return resultUrl;}//检查更新链接是否有效的方法public static String get(String url) {URL infoUrl = null;InputStream inStream = null;String line = "";try {infoUrl = new URL(url);URLConnection connection = infoUrl.openConnection();HttpURLConnection httpConnection = (HttpURLConnection) connection;int responseCode = httpConnection.getResponseCode();if (responseCode == HttpURLConnection.HTTP_OK) {inStream = httpConnection.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));StringBuilder strber = new StringBuilder();while ((line = reader.readLine()) != null)strber.append(line + "\n");inStream.close();int start = strber.indexOf("{");int end = strber.indexOf("}");String json = strber.substring(start, end + 1);return json;}} catch (MalformedURLException e) {} catch (IOException e) {}return "";}/*** 使用检查过的有效链接,获取服务端json数据*/public static JSONObject GetServerJson() {URL infoUrl = null;InputStream inStream = null;String line = "";try {String uurl = checkUrl(upl);infoUrl = new URL(uurl); //json格式信息的API,使用案例。URLConnection connection = infoUrl.openConnection();HttpURLConnection httpConnection = (HttpURLConnection) connection;int responseCode = httpConnection.getResponseCode();if (responseCode == HttpURLConnection.HTTP_OK) {inStream = httpConnection.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));StringBuilder strber = new StringBuilder();while ((line = reader.readLine()) != null)strber.append(line + "\n");inStream.close();int start = strber.indexOf("{");int end = strber.indexOf("}");String json = strber.substring(start, end + 1);if (json != null) {try {jSONObject = new JSONObject(json);hasUpdate = jSONObject.getBoolean("hasUpdate");NoIgnorable = jSONObject.getBoolean("NoIgnorable");versionCode = jSONObject.getInt("versionCode");versionName = jSONObject.getString("versionName");updateLog = jSONObject.getString("updateLog");apkSize = jSONObject.getString("apkSize");apkUrl = jSONObject.getString("apkUrl");webUrl = jSONObject.getString("webUrl");return jSONObject;} catch (JSONException e) {e.printStackTrace();}}}} catch (MalformedURLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}/*** 应用内直链升级方法,下载新版本程序*/private void loadNewVersionProgress() {ProgressDialog pd = new ProgressDialog(this);pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);//获取手机的根目录存储位置,以及直链链接最后一个“/”后文字作为文件名展示在界面pd.setMessage("下载最新版本安装包到:" + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + apkUrl.substring(apkUrl.lastIndexOf("/") + 1));pd.setCancelable(false); //开启强制更新,触摸屏幕其他位置无法关闭pd.show();//启动子线程下载任务new Thread() {@Overridepublic void run() {Looper.prepare();try {File file = getFileFromServer(apkUrl, pd);  //调用下载服务方法动态显示进度//sleep(2000);//设置休眠两秒之后再启动安装installApk(file);  //下载完成直接安装
//                    pd.dismiss(); //屏蔽,结束掉进度条对话框,防止强制更新出Bug} catch (Exception e) {//直链下载apk异常失败提示容错。AlertDialog.Builder builder = new AlertDialog.Builder(LoginActivity.this);builder.setCancelable(false);//开启强制更新,触摸屏幕其他位置无法关闭builder.setTitle("下载失败:").setMessage("1.请检查存储权限是否开启。\n2.请检查网络连接是否正常。\n3.使用浏览器下载新版本。");builder.setPositiveButton("退出", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface arg0, int arg1) {finish();}}).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新@Overridepublic void onClick(DialogInterface dialog, int which) {Uri uri = Uri.parse(webUrl);Intent intent = new Intent(Intent.ACTION_VIEW, uri);startActivity(intent);if (NoIgnorable) {finish(); //强制更新,点击后销毁应用}else{pd.dismiss(); //不强制更新,跳转到浏览器并销毁应用内下载失败的进度条}}});builder.create().show();}Looper.loop();}}.start();}/*** 安装apk*/protected void installApk(File file) {Intent intent = new Intent();intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setAction(Intent.ACTION_VIEW);if (Build.VERSION.SDK_INT >= 24) {Uri apkUri = FileProvider.getUriForFile(this, "com.bysj.yrj(你的包名).fileprovider", file); //这里要写你程序的包名,已实验不可使用${applicationId}intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);intent.setDataAndType(apkUri, "application/vnd.android.package-archive");} else {intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");}this.startActivity(intent);}/*** 从服务器获取apk文件的代码* 传入网址uri,进度条对象即可获得一个File文件* (要在子线程中执行哦)*/public static File getFileFromServer(String uri, ProgressDialog pd) throws Exception {if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {URL url = new URL(uri);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);//获取到文件的大小pd.setMax(conn.getContentLength());  //字节的方式显示下载进度InputStream is = conn.getInputStream();//获取直链链接最后一个“/”后文字作为文件名,下载存储到手机File file = new File(Environment.getExternalStorageDirectory(), apkUrl.substring(apkUrl.lastIndexOf("/", apkUrl.lastIndexOf("")) + 1));FileOutputStream fos = new FileOutputStream(file);BufferedInputStream bis = new BufferedInputStream(is);byte[] buffer = new byte[1024];int len;int total = 0;while ((len = bis.read(buffer)) != -1) {fos.write(buffer, 0, len);total += len;//获取当前下载量pd.setProgress(total);//字节方式显示下载量}fos.close();bis.close();is.close();return file;} else {return null;}}/*** 权限的验证及处理,相关方法*/private void getReadPermissions() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE}, 10001);} else {//没有则请求获取权限,示例权限是:存储权限,需要其他权限请更改或者替换ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 10001);}} else {//如果已经获取到了权限则直接进行下一步操作Log.e(TAG, "全部权限已经授权成功");}}}/*** 一个或多个权限请求结果回调* 循环回调获取权限,除非勾选禁止后不再询问,之后提示用户引导用户去设置*/@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode) {case 10001:for (int i = 0; i < grantResults.length; i++) {//                   如果拒绝获取权限if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {//判断是否勾选禁止后不再询问boolean flag = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]);if (flag) {getReadPermissions();return;//用户权限是一个一个的请求的,只要有拒绝,剩下的请求就可以停止,再次请求打开权限了} else { // 勾选不再询问,并拒绝Toast.makeText(this, "请到设置中打开权限", Toast.LENGTH_LONG).show();return;}}}//Toast.makeText(this, "权限开启完成", Toast.LENGTH_LONG).show();break;default:break;}}/*** 忽略https的证书校验 的相关方法*/public static void handleSSLHandshake() {try {TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}@Overridepublic void checkClientTrusted(X509Certificate[] certs, String authType) {}@Overridepublic void checkServerTrusted(X509Certificate[] certs, String authType) {}}};SSLContext sc = SSLContext.getInstance("TLS");sc.init(null, trustAllCerts, new SecureRandom());HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {@Overridepublic boolean verify(String hostname, SSLSession session) {return true;}});} catch (Exception ignored) {}}}

三、进入界面弹窗进度条选择/强制更新

这个版本的代码好像是只适用于Android 8.0以下的手机APP,检测远程更新代码。

(1).效果图:

(2).实现过程:

build.gradle (:app)构建文件中添加相关的依赖

dependencies {...implementation 'com.android.support:support-compat:28.0.0'
}

在项目的AndroidManifest.xml文件中添加相关的提供者等等信息。

    <application...<providerandroid:name="android.support.v4.content.FileProvider"<!--android:name="androidx.core.content.FileProvider"-->android:authorities="com.bysj.yrj自己的包名.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /></provider>...</application>

在项目的xml文件夹中创建file_paths.xml文件,并在文件中加入以下代码:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path name="external_files" path="."/>
</paths>

在要检测更新界面的Activity界面的Oncreate方法下写检测更新的代码,如下所示:

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//检测本程序的版本,这里假设从服务器中获取到最新的版本号为3//如果检测本程序的版本号小于服务器的版本号,那么提示用户更新if (getVersionCode() < 3) {showDialogUpdate();//弹出提示版本更新的对话框} else {//否则弹窗,说现在是最新的版本Toast.makeText(this, "当前已是最新版本", Toast.LENGTH_SHORT).show();}}

之后在要检测更新界面的Activity界面下写 相关代码上面的Oncreate下的代码直接调用了这块,如下所示:

 /*//检测本程序的版本,这里假设从服务器中获取到最新的版本号为3public void checkVersion() {//如果检测本程序的版本号小于服务器的版本号,那么提示用户更新if (getVersionCode() < 3) {showDialogUpdate();//弹出提示版本更新的对话框} else {//否则弹窗,说现在是最新的版本Toast.makeText(this, "当前已经是最新的版本", Toast.LENGTH_LONG).show();}}*//*** 提示版本更新的对话框*/private void showDialogUpdate() {// 这里的属性可以一直设置,因为每次设置后返回的是一个builder对象AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setCancelable(false); //开启强制更新,无法关闭// 设置提示框的标题builder.setTitle("版本升级").// 设置提示框的图标
//                        setIcon(R.mipmap.ic_launcher).// 设置要显示的信息setMessage("发现新版本!更新新版本?").// 设置确定按钮setPositiveButton("更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {//Toast.makeText(MainActivity.this, "选择确定哦", 0).show();loadNewVersionProgress();//下载最新的版本程序}}).                .// 设置取消按钮,null是什么都不做,并关闭对话框// setNegativeButton("取消", null);setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {finish();}
});// 生产对话框AlertDialog alertDialog = builder.create();// 显示对话框alertDialog.show();}/*** 下载新版本程序*/private void loadNewVersionProgress() {final String uri = "https://www.armpro.cn/assets/armpro130.apk";  //测试使用的下载APP直链final ProgressDialog pd;    //进度条对话框pd = new ProgressDialog(this);pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);pd.setMessage("正在下载安装包,请稍后");pd.setCancelable(false); //开启强制更新,触摸屏幕其他位置无法关闭pd.show();//启动子线程下载任务new Thread() {@Overridepublic void run() {try {Looper.prepare();File file = getFileFromServer(uri, pd);  //从服务器下载相关的APP流动态显示进度sleep(3000);installApk(file);  //下载完成之后,直接休眠3秒后进行安装
//                    pd.dismiss(); //结束掉进度条对话框Looper.loop();} catch (Exception e) {//下载apk失败Toast.makeText(getApplicationContext(), "下载新版本失败", Toast.LENGTH_LONG).show();e.printStackTrace();}}}.start();}/*** 安装apk*/protected void installApk(File file) {Intent intent = new Intent();intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setAction(Intent.ACTION_VIEW);if (Build.VERSION.SDK_INT >= 24) {Uri apkUri = FileProvider.getUriForFile(this, "com.bysj.yrj这里是自己的包名.fileprovider", file);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);intent.setDataAndType(apkUri, "application/vnd.android.package-archive");} else {intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");}this.startActivity(intent);}/*** 从服务器获取apk文件的代码* 传入网址uri,进度条对象即可获得一个File文件* (要在子线程中执行哦)*/public static File getFileFromServer(String uri, ProgressDialog pd) throws Exception {//如果相等的话表示当前的sdcard挂载在手机上并且是可用的if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {URL url = new URL(uri);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);//获取到文件的大小pd.setMax(conn.getContentLength());InputStream is = conn.getInputStream();//            long time = System.currentTimeMillis();//当前时间的毫秒数Date date = new Date();// 创建一个时间对象,获取到当前的时间SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss");// 设置时间显示格式String time = sdf.format(date);File file = new File(Environment.getExternalStorageDirectory(), time + "信息管理系统_update.apk");FileOutputStream fos = new FileOutputStream(file);BufferedInputStream bis = new BufferedInputStream(is);byte[] buffer = new byte[1024];int len;int total = 0;while ((len = bis.read(buffer)) != -1) {fos.write(buffer, 0, len);total += len;//获取当前下载量pd.setProgress(total);}fos.close();bis.close();is.close();return file;} else {return null;}}/** 获取当前程序的版本号*/private int getVersionCode() {try {//获取packagemanager的实例PackageManager packageManager = getPackageManager();//getPackageName()是你当前类的包名,0代表是获取版本信息PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);Log.e("TAG", "版本号" + packInfo.versionCode);  //更新软件用的是版本号Log.e("TAG", "版本名" + packInfo.versionName);  //给用户看得叫做版本名return packInfo.versionCode;} catch (Exception e) {e.printStackTrace();}return 1;}/** 获取当前程序的版本名*/private String getVersionName() throws Exception {//获取packagemanager的实例PackageManager packageManager = getPackageManager();//getPackageName()是你当前类的包名,0代表是获取版本信息PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);Log.e("TAG", "版本号" + packInfo.versionCode);  //更新软件用的是版本号Log.e("TAG", "版本名" + packInfo.versionName);  //给用户看得叫做版本名return packInfo.versionName;}

四、使用更新框架进入检测更新/强制/远程

这个版本的代码已测试适用于Android系统 6、7、8、9、10、11手机APP,检测远程更新代码,之前出现过项目中运行代码在模拟器更新提示没有相关问题,但是打包安装在真机上检测更新就有问题,经过排查后原因发现是因为我在之前学习混淆时项目中配置使用了ProGuard,打包时自动优化掉了一些代码导致的。

(1).基础使用篇

1.效果图:

可根据Api形式自定义的选择是否更新、是否强制更新、也可配置断点续传下载,
更新后的APP安装包会放在/storage/emulated/0/Android/data/你的应用包名/cache/xupdate/版本号/

    public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";

2.实现过程:

添加Gradle依赖
1.先在项目根目录的 build.gradle 的 repositories 添加:

allprojects {repositories {...maven { url "https://jitpack.io" }}
}

2.然后在build.gradle(app)的dependencies中添加:

android {...compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}
dependencies {...// androidx projectimplementation 'com.github.xuexiangjys:XUpdate:2.0.6'// support projectimplementation 'com.github.xuexiangjys:XUpdate:1.1.6'implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-easy:1.0.0'// 如果需要使用断点续传下载功能的话添加该依赖(可选)implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-downloader-aria:1.0.0'}

在项目的gradle.properties文件下开启Android X的构建支持,如果没有的话,就在Project的目录下创建一个,并在其中加入以下代码即可。

org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true

3.在要检测更新界面的Activity下,加入以下成员变量信息,用来控制以后的远程更新、版本检测、版本强制更新等等信息。

public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";

上面的Api访问的后的内容以及相关解释如下:

{"Code": 0, //0代表请求成功,非0代表失败"Msg": "", //请求出错的信息"UpdateStatus": 1, //0代表不更新,1代表有版本更新,不需要强制升级,2代表有版本更新,需要强制升级"VersionCode": 3,"VersionName": "1.0.2","ModifyContent": "1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。","DownloadUrl": "https://raw.githubusercontent.com/xuexiangjys/XUpdate/master/apk/xupdate_demo_1.0.2.apk","ApkSize": 2048"ApkMd5": "..."  //md5值没有的话,就无法保证apk是否完整,每次都会重新下载。
}

在当前界面的Oncreate方法下合适的位置写检测更新的代码,让其进入这个Activity界面后,直接执行此代码检测版本是否更新。

public class LoginActivity extends Activity {public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//使用检测更新的框架,检测APP是否有新版本EasyUpdate.checkUpdate(this, UPDATE_TEST_URL);//断点续传更新相关功能的使用EasyUpdate.create(this, UPDATE_TEST_URL).updateHttpService(AriaDownloader.getUpdateHttpService(this)).update();//让其可在后台更新--是否强制更新由Url控制XUpdate.newBuild(this).updateUrl(UPDATE_TEST_URL).supportBackgroundUpdate(true).update();}}
}

使用XUpdate 的日志功能,更加详细的查看运行情况,如果出现任何问题,可开启debug模式来追踪问题,需要新创建一个类MyApp来初始化Application,代码可与上面的代码配合使用,例如:使用检测更新的框架,检测APP是否有新版本的代码,使用并配置下方代码,可以让其,开启Debug 和 断点续传的功能,代码如下所示:

public class MyApp extends Application {@Overrideprotected void attachBaseContext(Context base) {EasyUpdate.setUpdateConfigProvider(new IUpdateConfigProvider() {@Overridepublic UpdateConfig getUpdateConfig(Context context) {return UpdateConfig.create().setIsDebug(true)  //开启Debug  日志功能,查看详细的日志信息.setDownloadServiceProxy(new AriaDownloadServiceProxyImpl(context)); //设置断点续传功能}});super.attachBaseContext(base);}
}

在安卓的AndroidManifest.xmlapplication 标签下写入 android:name=".你的类名",我这里根据我创建的位置为:android:name=".dao.MyApp" ,具体代码如下所示:

    <applicationandroid:name=".dao.MyApp" android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme"><activity.....</activity></application>

(2).高级使用篇

在基础使用篇的基础上,开始更加深入的研究高级使用篇,高级使用篇的使用包括但不限于:自定义主题自定义弹窗风格智能更新只使用下载功能静默安装(Root)等等。这里根据作者的教程,尝试在自己的小Demo中实现,边学边写文章,并且总结一些我使用过程中遇到的一些问题,以及相关解决办法。

1.自定义图片和主题弹窗

效果图

实现过程:

使用XUpdate 的高级功能,需要新创建一个类MyApp来初始化Application,使用并配置下方代码,代码如下所示:

/*** @Author: yirj* @Date: 2021/2/25 9:28* @Remark: 说明*/
public class MyApp extends Application {@Overridepublic void onCreate() {super.onCreate();XUpdate.get().debug(true).isWifiOnly(true)                                               //默认设置只在wifi下检查版本更新.isGet(true)                                                    //默认设置使用get请求检查版本.isAutoMode(false)                                              //默认设置非自动模式,可根据具体使用配置.param("versionCode", UpdateUtils.getVersionCode(this))         //设置默认公共请求参数.param("appKey", getPackageName()).setOnUpdateFailureListener(new OnUpdateFailureListener() {     //设置版本更新出错的监听@Overridepublic void onFailure(UpdateError error) {if (error.getCode() != CHECK_NO_NEW_VERSION) {          //对不同错误进行处理
//                            ToastUtils.toast(error.toString());}}}).supportSilentInstall(true)                                     //设置是否支持静默安装,默认是true.setIUpdateHttpService(new OkHttpUpdateHttpServiceImpl()) //这个必须设置!实现网络请求功能。  OKHttpUpdateHttpService().init(this);                                                    //这个必须初始化}
}

出现的问题:
两处报红的情况,ToastUtils.toast(error.toString());OkHttpUpdateHttpService,第一处ToastUtils.直接的注释掉即可,不影响使用。第二处是关于网络请求库的OkHttpUpdateHttpService将其改为OkHttpUpdateHttpServiceImpl并找到 build.gradle (app)dependencies添加以下依赖,就可以解决这些报红问题了。

dependencies {.....//  MyApp中OKHttpUpdateHttpService 报红解决  添加网络请求库implementation 'com.zhy:okhttputils:2.6.2'implementation 'com.google.code.gson:gson:2.8.5'implementation 'com.squareup.okhttp3:okhttp:3.14.9'
}

在安卓的AndroidManifest.xmlapplication 标签下写入 android:name=".你的类名",我这里根据我创建的位置为:android:name=".dao.MyApp" ,具体代码如下所示:

    <applicationandroid:name=".dao.MyApp" android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme"><activity.....</activity></application>

在当前界面的Oncreate方法下合适的位置写检测更新的代码,让其进入这个Activity界面后,直接执行此代码检测版本是否更新。

@Override
public class LoginActivity extends Activity {public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);XUpdate.newBuild(this).updateUrl(UPDATE_TEST_URL)//这里的ResUtils工具类报红的话,直接使用getResources()替代//R.color.update_theme_color 需要创建颜色文件,继续往下看....promptThemeColor(getResources().getColor(R.color.update_theme_color)).promptButtonTextColor(Color.WHITE).promptTopResId(R.drawable.bg_update_top) //这里和你项目的图片存储一致.promptWidthRatio(0.7F).update();}
}

接下来是找到弹窗背景的主题图片 bg_update_top.png(原图可以到作者的项目中找),注意为了图片的比例问题,请务必将其添加到你项目的drawable-xxhdpi或者mipmap-xxhdpi文件夹下,而不是其他的drawable或者mipmap文件夹,容易造成图片主题大小的一些兼容问题。然后就在 res/values/ 下创建colors.xml文件,并写入相关颜色的配置,这样上面的代码报红就完全解决了。这样高级使用篇的自定义主题功能就搞定了。

<?xml version="1.0" encoding="utf-8"?>
<resources><color name="update_theme_color">#FFFFAC5D</color>
</resources>

2.使用原生系统的更新弹窗

自定义图片和主题弹窗的基础上,只需要配置两个类HProgressDialogUtilsCustomUpdatePrompter,以及稍微Oncreate修改一些调用就可以实现了。

效果图:

实现过程:

HProgressDialogUtils.java 代码如下所示:

/*** Created by Vector on 2016/8/12 0012.*/
public class HProgressDialogUtils {private static ProgressDialog sHorizontalProgressDialog;private HProgressDialogUtils() {throw new UnsupportedOperationException("cannot be instantiated");}@SuppressLint("NewApi")public static void showHorizontalProgressDialog(Context context, String msg, boolean isShowSize) {cancel();if (sHorizontalProgressDialog == null) {sHorizontalProgressDialog = new ProgressDialog(context);sHorizontalProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);sHorizontalProgressDialog.setCancelable(false);if (isShowSize) {sHorizontalProgressDialog.setProgressNumberFormat("%2dMB/%1dMB");}}if (!TextUtils.isEmpty(msg)) {sHorizontalProgressDialog.setMessage(msg);}sHorizontalProgressDialog.show();}public static void setMax(long total) {if (sHorizontalProgressDialog != null) {sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));}}public static void cancel() {if (sHorizontalProgressDialog != null) {sHorizontalProgressDialog.dismiss();sHorizontalProgressDialog = null;}}public static void setProgress(int current) {if (sHorizontalProgressDialog == null) {return;}sHorizontalProgressDialog.setProgress(current);if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {sHorizontalProgressDialog.dismiss();sHorizontalProgressDialog = null;}}public static void setProgress(long current) {if (sHorizontalProgressDialog == null) {return;}sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {sHorizontalProgressDialog.dismiss();sHorizontalProgressDialog = null;}}public static void onLoading(long total, long current) {if (sHorizontalProgressDialog == null) {return;}if (current == 0) {sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));}sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {sHorizontalProgressDialog.dismiss();sHorizontalProgressDialog = null;}}
}

CustomUpdatePrompter.java 代码如下所示:

/*** 自定义版本更新提示器** @author xuexiang* @since 2018/7/12 下午3:48*/
public class CustomUpdatePrompter implements IUpdatePrompter {/*** 显示自定义提示** @param updateEntity* @param updateProxy*/private void showUpdatePrompt(final @NonNull UpdateEntity updateEntity, final @NonNull IUpdateProxy updateProxy) {String updateInfo = UpdateUtils.getDisplayUpdateInfo(updateProxy.getContext(), updateEntity);AlertDialog.Builder builder = new AlertDialog.Builder(updateProxy.getContext()).setTitle(String.format("是否升级到%s版本?", updateEntity.getVersionName())).setMessage(updateInfo).setPositiveButton("升级", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {updateProxy.startDownload(updateEntity, new OnFileDownloadListener() {@Overridepublic void onStart() {HProgressDialogUtils.showHorizontalProgressDialog(updateProxy.getContext(), "下载进度", false);}@Overridepublic void onProgress(float progress, long total) {HProgressDialogUtils.setProgress(Math.round(progress * 100));}@Overridepublic boolean onCompleted(File file) {HProgressDialogUtils.cancel();return true; //这个true表示是直接安装,需要Root权限,否则就是只下载不提示安装}@Overridepublic void onError(Throwable throwable) {HProgressDialogUtils.cancel();}});}});if (updateEntity.isIgnorable()) {builder.setNegativeButton("暂不升级", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {UpdateUtils.saveIgnoreVersion(updateProxy.getContext(), updateEntity.getVersionName());}}).setCancelable(true);} else  {builder.setCancelable(false);}builder.create().show();}/*** 显示版本更新提示** @param updateEntity 更新信息* @param updateProxy  更新代理* @param promptEntity 提示界面参数*/@Overridepublic void showPrompt(@NonNull UpdateEntity updateEntity, @NonNull IUpdateProxy updateProxy, @NonNull PromptEntity promptEntity) {showUpdatePrompt(updateEntity, updateProxy);}
}

在当前界面的Oncreate方法下合适的位置写检测更新的代码,让其进入这个Activity界面后,直接执行此代码检测版本是否更新。

public class LoginActivity extends Activity {public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//原生系统弹窗的使用,好像默认是强制更新XUpdate.newBuild(this).updateUrl(UPDATE_TEST_URL).updatePrompter(new CustomUpdatePrompter()).update();}
}

3.默认App更新 + 自定义Api + 自定义提示弹窗(系统)

效果图:

实现过程:

添加Gradle依赖
1.先在项目根目录的 build.gradle 的 repositories 添加:

allprojects {repositories {...maven { url "https://jitpack.io" }}
}

2.然后在build.gradle(app)的dependencies中添加:

android {...compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}
dependencies {...//JsonUtil 报红的话 添加这个implementation 'com.github.xuexiangjys.XUtil:xutil-core:2.0.0'// androidx projectimplementation 'com.github.xuexiangjys:XUpdate:2.0.6'// support project//implementation 'com.github.xuexiangjys:XUpdate:1.1.6'implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-easy:1.0.0'// 如果需要使用断点续传下载功能的话添加该依赖(可选)implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-downloader-aria:1.0.0'}

在项目的gradle.properties文件下开启Android X的构建支持,如果没有的话,就在Project的目录下创建一个,并在其中加入以下代码即可。

org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true

3.在要检测更新界面的Activity下,加入以下成员变量信息,用来控制以后的远程更新、版本检测、版本强制更新等等信息。

//默认App更新 + 自定义Api + 自定义提示弹窗(系统)
private String mUpdateUrl3 = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_custom.json";

上面的Api访问的后的内容以及相关解释如下:

{hasUpdate: true, //控制是否有更新 true:开启  false: 关闭isIgnorable: true, //控制是否强制升级 true:不强制升级 false: 强制升级versionCode: 3, //这个服务版本号大于安装的版本号 会提示更新versionName: "1.0.2", //这个一般是给用户看的updateLog: " 1、优化api接口。 2、添加使用demo演示。 3、新增自定义更新服务API接口。 4、优化更新提示界面。", apkUrl: "https://xuexiangjys.oss-cn-shanghai.aliyuncs.com/apk/xupdate_demo_1.0.2.apk",apkSize: 4096    //KB为单位的更新包大小
}

使用XUpdate 的高级功能,需要在你的程序中初始化Application,使用并配置下方代码,代码如下所示:

/*** @Author: yirj* @Date: 2021/2/25 9:28* @Remark: 说明*/
public class 你的启动初始化类名 extends Application {@Overridepublic void onCreate() {super.onCreate();//从这里开始的XUpdate.get().debug(true).isWifiOnly(true)                                               //默认设置只在wifi下检查版本更新.isGet(true)                                                    //默认设置使用get请求检查版本.isAutoMode(false)                                              //默认设置非自动模式,可根据具体使用配置.param("versionCode", UpdateUtils.getVersionCode(this))         //设置默认公共请求参数.param("appKey", getPackageName()).setOnUpdateFailureListener(new OnUpdateFailureListener() {     //设置版本更新出错的监听@Overridepublic void onFailure(UpdateError error) {if (error.getCode() != CHECK_NO_NEW_VERSION) {          //对不同错误进行处理
//                            ToastUtils.toast(error.toString());}}}).supportSilentInstall(true)                                     //设置是否支持静默安装,默认是true.setIUpdateHttpService(new OkHttpUpdateHttpServiceImpl()) //这个必须设置!实现网络请求功能。  OKHttpUpdateHttpService().init(this);                                                    //这个必须初始化}
}

如果找不到你的启动初始化的类名,可以在项目的AndroidManifest.xmlapplication 标签找 android:name=".你的类名",找到后直接按住Ctrl+鼠标点击即可跳转,并将上面的代码加入Oncreate中;如果没有的话,就要自己创建类名并且按照下面代码的自己搞一个。

    <applicationandroid:name=".你创建的类所在的位置" android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme"><activity.....</activity></application>

在要检测升级界面的Oncreate方法下合适的位置写检测更新的代码,让其进入这个Activity界面后,直接执行此代码检测版本是否更新。

public class LoginActivity extends Activity {//默认App更新 + 自定义Api + 自定义提示弹窗(系统)private String mUpdateUrl3 = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_custom.json";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//默认App更新 + 自定义Api + 自定义提示弹窗(系统)XUpdate.newBuild(this).updateUrl(mUpdateUrl3).updateChecker(new DefaultUpdateChecker() {@Overridepublic void onBeforeCheck() {super.onBeforeCheck();CProgressDialogUtils.showProgressDialog(Login更新界面Activity.this, "查询中...");}@Overridepublic void onAfterCheck() {super.onAfterCheck();CProgressDialogUtils.cancelProgressDialog(Login更新界面Activity.this);}@Overridepublic void noNewVersion(Throwable throwable) {super.noNewVersion(throwable);// 没有最新版本的处理}}).updateParser(new CustomUpdateParser()).updatePrompter(new CustomUpdatePrompter()).update();}}
}

使用默认App更新 + 自定义Api + 自定义提示弹窗(系统)需要自定义配置几个类CProgressDialogUtils.java CustomUpdateParser.java CustomUpdatePrompter.java HProgressDialogUtils.java CustomResult.java ,可以在源 Github项目寻找,只需要稍微简单改改即可!此时加入之后代码中一般只会有一个JsonUtil的工具类显示报红。使用这种方式实现更新弹窗,其文字并不仅限于国际化中的中英文了。
CProgressDialogUtils.java 代码如下所示:

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;/*** Created by Vector on 2016/8/12 0012.*/
public class CProgressDialogUtils {private static final String TAG = CProgressDialogUtils.class.getSimpleName();private static ProgressDialog sCircleProgressDialog;private CProgressDialogUtils() {throw new UnsupportedOperationException("cannot be instantiated");}public static void showProgressDialog(Activity activity) {showProgressDialog(activity, "加载中", false, null);}public static void showProgressDialog(Activity activity, DialogInterface.OnCancelListener listener) {showProgressDialog(activity, "加载中", true, listener);}public static void showProgressDialog(Activity activity, String msg) {showProgressDialog(activity, msg, false, null);}public static void showProgressDialog(Activity activity, String msg, DialogInterface.OnCancelListener listener) {showProgressDialog(activity, msg, true, listener);}public static void showProgressDialog(final Activity activity, String msg, boolean cancelable, DialogInterface.OnCancelListener listener) {if (activity == null || activity.isFinishing()) {return;}if (sCircleProgressDialog == null) {sCircleProgressDialog = new ProgressDialog(activity);sCircleProgressDialog.setMessage(msg);sCircleProgressDialog.setOwnerActivity(activity);sCircleProgressDialog.setOnCancelListener(listener);sCircleProgressDialog.setCancelable(cancelable);} else {if (activity.equals(sCircleProgressDialog.getOwnerActivity())) {sCircleProgressDialog.setMessage(msg);sCircleProgressDialog.setCancelable(cancelable);sCircleProgressDialog.setOnCancelListener(listener);} else {//不相等,所以取消任何ProgressDialogcancelProgressDialog();sCircleProgressDialog = new ProgressDialog(activity);sCircleProgressDialog.setMessage(msg);sCircleProgressDialog.setCancelable(cancelable);sCircleProgressDialog.setOwnerActivity(activity);sCircleProgressDialog.setOnCancelListener(listener);}}if (!sCircleProgressDialog.isShowing()) {sCircleProgressDialog.show();}}public static void cancelProgressDialog(Activity activity) {if (sCircleProgressDialog != null && sCircleProgressDialog.isShowing()) {if (sCircleProgressDialog.getOwnerActivity() == activity) {sCircleProgressDialog.cancel();sCircleProgressDialog = null;}}}public static void cancelProgressDialog() {if (sCircleProgressDialog != null && sCircleProgressDialog.isShowing()) {sCircleProgressDialog.cancel();sCircleProgressDialog = null;}}}

CustomUpdateParser.java 代码如下所示:

import androidx.annotation.NonNull;import com.xuexiang.xupdate.entity.UpdateEntity;
import com.xuexiang.xupdate.listener.IUpdateParseCallback;
import com.xuexiang.xupdate.proxy.IUpdateParser;
//import com.xuexiang.xupdatedemo.entity.CustomResult;
import com.xuexiang.xutil.net.JsonUtil;/*** 自定义更新解析器** @author xuexiang* @since 2018/7/12 下午3:46*/
public class CustomUpdateParser implements IUpdateParser {@Overridepublic UpdateEntity parseJson(String json) throws Exception {return getParseResult(json);}private UpdateEntity getParseResult(String json) {CustomResult result = JsonUtil.fromJson(json, CustomResult.class);if (result != null) {return new UpdateEntity().setHasUpdate(result.hasUpdate).setIsIgnorable(result.isIgnorable).setVersionCode(result.versionCode).setVersionName(result.versionName).setUpdateContent(result.updateLog).setDownloadUrl(result.apkUrl).setSize(result.apkSize);}return null;}@Overridepublic void parseJson(String json, @NonNull IUpdateParseCallback callback) throws Exception {//当isAsyncParser为 true时调用该方法, 所以当isAsyncParser为false可以不实现callback.onParseResult(getParseResult(json));}@Overridepublic boolean isAsyncParser() {return false;}
}

CustomUpdatePrompter.java 代码如下所示:

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;import androidx.annotation.NonNull;import com.xuexiang.xupdate.XUpdate;
import com.xuexiang.xupdate.entity.CheckVersionResult;
import com.xuexiang.xupdate.entity.PromptEntity;
import com.xuexiang.xupdate.entity.UpdateEntity;
import com.xuexiang.xupdate.proxy.IUpdatePrompter;
import com.xuexiang.xupdate.proxy.IUpdateProxy;
import com.xuexiang.xupdate.proxy.impl.AbstractUpdateParser;
import com.xuexiang.xupdate.service.OnFileDownloadListener;
import com.xuexiang.xupdate.utils.UpdateUtils;import java.io.File;
import java.io.IOException;/*** 自定义版本更新提示器** @author xuexiang* @since 2018/7/12 下午3:48*/
public class CustomUpdatePrompter implements IUpdatePrompter {/*** 显示自定义提示* 这里的弹窗内容除了“新版本大小”文字之外可以 自定义改* @param updateEntity* @param updateProxy*/private void showUpdatePrompt(final @NonNull UpdateEntity updateEntity, final @NonNull IUpdateProxy updateProxy) {//        String updateInfo = UpdateUtils.getDisplayUpdateInfo(updateProxy.getContext(), updateEntity);String updateInfo = getVersionInfo(updateProxy.getContext(), updateEntity);AlertDialog.Builder builder = new AlertDialog.Builder(updateProxy.getContext()).setTitle(String.format("是否升级到%s版本?", updateEntity.getVersionName())).setMessage(updateInfo).setPositiveButton("升级", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {updateProxy.startDownload(updateEntity, new OnFileDownloadListener() {@Overridepublic void onStart() {HProgressDialogUtils.showHorizontalProgressDialog(updateProxy.getContext(), "下载进度", false);}@Overridepublic void onProgress(float progress, long total) {HProgressDialogUtils.setProgress(Math.round(progress * 100));}@Overridepublic boolean onCompleted(File file) {HProgressDialogUtils.cancel();return true; //这个表示是直接安装,需要Root权限}@Overridepublic void onError(Throwable throwable) {HProgressDialogUtils.cancel();}});}});if (updateEntity.isIgnorable()) {  //默认为false就是不显示暂不升级 -- 达到强制更新效果builder.setNegativeButton("暂不升级", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {//注释这个不会保存 暂不升级的信息
//                    UpdateUtils.saveIgnoreVersion(updateProxy.getContext(), updateEntity.getVersionName());}}).setCancelable(false); //默认true,为false就是触摸屏幕其他位置不可关闭} else {builder.setCancelable(false);}builder.create().show();}/*** 获取版本更新展示信息* 这里是更改弹窗的“新版本大小”几个字 为自定义的文字* @param updateEntity* @return*/@NonNullpublic static String getVersionInfo(Context context, @NonNull UpdateEntity updateEntity) {String targetSize = byte2FitMemorySize(updateEntity.getSize() * 1024);final String updateContent = updateEntity.getUpdateContent();String updateInfo = "";if (!TextUtils.isEmpty(targetSize)) {updateInfo = "测试new version大小:" + targetSize + "\n";}if (!TextUtils.isEmpty(updateContent)) {updateInfo += updateContent;}return updateInfo;}/*** 字节数转合适内存大小* <p>保留 1 位小数</p>** @param byteNum 字节数* @return 合适内存大小*/@SuppressLint("DefaultLocale")private static String byte2FitMemorySize(final long byteNum) {if (byteNum <= 0) {return "";} else if (byteNum < 1024) {return String.format("%.1fB", (double) byteNum);} else if (byteNum < 1048576) {return String.format("%.1fKB", (double) byteNum / 1024);} else if (byteNum < 1073741824) {return String.format("%.1fMB", (double) byteNum / 1048576);} else {return String.format("%.1fGB", (double) byteNum / 1073741824);}}/*** 显示版本更新提示** @param updateEntity 更新信息* @param updateProxy  更新代理* @param promptEntity 提示界面参数*/@Overridepublic void showPrompt(@NonNull UpdateEntity updateEntity, @NonNull IUpdateProxy updateProxy, @NonNull PromptEntity promptEntity) {showUpdatePrompt(updateEntity, updateProxy);}
}

HProgressDialogUtils.java 代码如下所示:

import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.text.TextUtils;/*** Created by Vector on 2016/8/12 0012.*/
public class HProgressDialogUtils {private static ProgressDialog sHorizontalProgressDialog;private HProgressDialogUtils() {throw new UnsupportedOperationException("cannot be instantiated");}@SuppressLint("NewApi")public static void showHorizontalProgressDialog(Context context, String msg, boolean isShowSize) {cancel();if (sHorizontalProgressDialog == null) {sHorizontalProgressDialog = new ProgressDialog(context);sHorizontalProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);sHorizontalProgressDialog.setCancelable(false);if (isShowSize) {sHorizontalProgressDialog.setProgressNumberFormat("%2dMB/%1dMB");}}if (!TextUtils.isEmpty(msg)) {sHorizontalProgressDialog.setMessage(msg);}sHorizontalProgressDialog.show();}public static void setMax(long total) {if (sHorizontalProgressDialog != null) {sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));}}public static void cancel() {if (sHorizontalProgressDialog != null) {sHorizontalProgressDialog.dismiss();sHorizontalProgressDialog = null;}}public static void setProgress(int current) {if (sHorizontalProgressDialog == null) {return;}sHorizontalProgressDialog.setProgress(current);if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {sHorizontalProgressDialog.dismiss();sHorizontalProgressDialog = null;}}public static void setProgress(long current) {if (sHorizontalProgressDialog == null) {return;}sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {sHorizontalProgressDialog.dismiss();sHorizontalProgressDialog = null;}}public static void onLoading(long total, long current) {if (sHorizontalProgressDialog == null) {return;}if (current == 0) {sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));}sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {sHorizontalProgressDialog.dismiss();sHorizontalProgressDialog = null;}}
}

CustomResult.java 代码如下所示:

import java.io.Serializable;/*** 自定义版本检查的结果** @author xuexiang* @since 2018/7/11 上午1:03*/
public class CustomResult implements Serializable {public boolean hasUpdate;public boolean isIgnorable;public int versionCode;public String versionName;public String updateLog;public String apkUrl;public long apkSize;
}

作者CSDN: https://blog.csdn.net/xuexiangjys
Github项目: https://github.com/xuexiangjys/XUpdate
快速使用XUpdateAPI: https://github.com/xuexiangjys/XUpdateAPI

Android开发 应用软件更新通用方式--强制/非强制/远程控制/浏览器 更新相关推荐

  1. Android 实现SKU选择通用方式

    效果如下: 说明: 实现sku的方式一般采用在获取到数据后拆分所有条件的可能性,实现方式参考js的实现,代码如下: SkuHelp.kt /** 不考虑服务端的格式类型,将对应格式翻译成如下格式:*[ ...

  2. Android 开发、成长、辅助等工具汇总(持续更新中)

    互联网的迅速发展,导致市面上各种各样的工具层出不穷,这本来是好事.但是过于频繁的尝试.更换工具,不仅仅浪费了我们的时间,最重要的是也影响了我们的成长.现如今同类工具的功能基本类似,优秀的ideal总会 ...

  3. Android开发的之基本控件和详解四种布局方式

    Android中的控件的使用方式和iOS中控件的使用方式基本相同,都是事件驱动.给控件添加事件也有接口回调和委托代理的方式.今天这篇博客就总结一下Android中常用的基本控件以及布局方式.说到布局方 ...

  4. Android开发规范[Java+android]

    说明:该文档由阿里<Java开发规范>和<Android开发规范>整理而来  [强制]必须遵守,违反本约定或将会引起严重的后果:  [推荐]尽量遵守,长期遵守有助于系统稳定 ...

  5. 在Eclipse上搭建Android开发环境

    声明:转摘请注明http://blog.csdn.net/longming_xu/article/details/28241045 前言:为什么要写这么一篇文章?网上介绍Android开发环境搭建的文 ...

  6. 一步步教你搭建Android开发环境(有图有真相)--“自吹自擂:史上最详细、最啰嗦、最新的搭建教程”

    声明:转摘请注明http://blog.csdn.net/longming_xu/article/details/28241045 前言:为什么要写这么一篇文章?网上介绍Android开发环境搭建的文 ...

  7. Pro Android 4 第一章 Android 开发平台介绍

    当今这个时代,计算比以往任何一个时代都距离人们更近.各种手持设备已经变身为计算平台.无论是平板和是手机,移动设备凭借其强大的通用计算能力,已经成为真正意义上的个人电脑(PC).所有的传统计算机厂商已经 ...

  8. 一个高薪的Android开发工程师需要具备什么能力?

    前言 任何工作,任何行业想要拿到高薪都需要这几点条件,时间,技术,关系. 时间,无非就是在这个行业摸爬滚打很久有自己一套赚钱的方法: 关系,说白了就是家中有人帮忙铺路: 技术,在一个行业技术专精就能受 ...

  9. Win10设置系统补丁更新服务器,win10系统手动更新补丁如何设置 win10系统手动安装更新方法...

    相信使用win10的用户都发现,我们的系统常常会自动更新补丁,其会造成占用网络.自动重启等多种问题,许多用户都会选择直接关闭自动更新,但是设置为关闭后我们之后将无法进行更新,因此更多用户更愿设置为手动 ...

  10. android开发关掉发现更新的官方版本,XUpdate:轻量级、高可用性的 Android 版本更新框架...

    一个轻量级.高可用性的Android版本更新框架 关于我 特点 支持post和get两种版本检查方式,支持自定义网络请求. 支持设置只在wifi下进行版本更新. 支持静默下载.自动版本更新. 提供界面 ...

最新文章

  1. hihocoder offer收割编程练习赛11 B 物品价值
  2. AI一分钟 | 富士康押宝人工智能,将投资21亿元用于AI研发
  3. ​一文读懂EfficientDet
  4. poj 3321 Apple Trie
  5. php判断表单提交是否为空,JS判断提交表单不能为空代码 多种方案
  6. 疯子的算法总结(二) STL Ⅰ 算法 ( algorithm )
  7. 前端学习(2473):创建页面组件
  8. 鼠标紧跟lable_紧跟当前软件工程趋势的12种方法
  9. MFC无法使用CDialogEx类
  10. Oracle视图、自定义函数、存储过程、触发器
  11. 【VS开发】图像颜色
  12. Java自带的keytool命令
  13. 【ANSYS命令流】通用后处理技术(三):列表显示结果及输出TXT文本(实例)
  14. Python3 - 苹果ID登录App Sign in with Apple
  15. php微信小商城系统设计,基于微信小程序的互联网商城系统设计开题报告
  16. CCF数据库专委会杜小勇:数据库40年激荡历程
  17. 01-选择属于自己的相机
  18. 【笔记本加配置记录】笔记本加内存条,华硕拆后盖
  19. Packet Tracer 5.0建构CCNA实验攻略——帧中继Frame Relay
  20. 昔人已乘黄鹤去 此地空余黄鹤楼-崔颢

热门文章

  1. 手机图片如何转化为Word文档?简单几步轻松转换
  2. Modelsim与ISE联和仿真错误
  3. 树莓派4B连接显示器 黑屏、左上角有光标问题
  4. 大鹏教你python数据分析
  5. Mac OS X 背后的故事(九)半导体的丰收(中)
  6. arXiv每日推荐-5.9:计算机视觉/图像处理每日论文速递
  7. mysql查看被锁住的表
  8. HP ProLiant DL380 Gen9 SPP更新固件的两种方式
  9. 你不需要完美-你需要的是行动与完成
  10. 通达信【波段底部机会】副图指标公式 操盘行情线 重心买入 源码