一、通知栏自动下载更新
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后自动下载更新。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
VersionUpdateConfig.getInstance()//获取配置实例
.setContext(LoginActivit这里写界面.this)//设置上下文
.setDownLoadURL("https://www.armpro/assets/这里是下载地址.apk")//设置文件下载链接
.setNewVersion("3")//设置即将下载的APK的版本号,避免重复下载
.setFileSavePath("/storage/emulated/0/")//设置文件保存路径(可不设置)
.setNotificationIconRes(R.drawable.ic_launcher)//设置通知图标
.setNotificationSmallIconRes(R.drawable.ic_launcher)//设置通知小图标
.setNotificationTitle("软件有新版本,正在下载中...")//设置通知标题
.startDownLoad();//开始下载
}
接下来就不用再做任何处理了,有新版本时启动APP,就会自动在通知栏显示下载进度,在下载完成后会自动跳到安装页面。
项目源码地址:https://github/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/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, //是否有更新 默认true
NoIgnorable: true, //不 可忽略更新 强制:true 非强制:false
versionCode: 51, //服务端的版本号
versionName: "2.4.1", //服务端的版本名
updateLog: "\n1、更改保存图片的存储路径。\n2、更改软件更新的提示模式。\n3、调整非强制更新控制方式。\n4、新增存储权限的申请授权位置。", //更新提示内容
apkUrl: "https://www.yuming/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/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">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android: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"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//忽略证书校验的相关方法,http报证书异常或者不安全链接就启用它
//handleSSLHandshake();
//应用内直链更新,要先动态申请获取存储权限
getReadPermissions();
//启动子线程--更新功能实现调用
new Thread() {
@Override
public 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
loadNewVersionProgress();//程序内直接下载最新的版本程序
}
}).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新
@Override
public 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() {//正按钮
@Override
public void onClick(DialogInterface dialog, int which) {
loadNewVersionProgress();//下载最新的版本程序
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {//负按钮
@Override
public void onClick(DialogInterface dialog, int which) {
// finish(); //屏蔽销毁,不做任何处理
}
}).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮
@Override
public 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() {
@Override
public 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() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
finish();
}
}).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新
@Override
public 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, "全部权限已经授权成功");
}
}
}
/**
* 一个或多个权限请求结果回调
* 循环回调获取权限,除非勾选禁止后不再询问,之后提示用户引导用户去设置
*/
@Override
public 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];
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public 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() {
@Override
public 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
...
<provider
android: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-data
android: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/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
在要检测更新界面的Activity界面的Oncreate
方法下写检测更新的代码,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//检测本程序的版本,这里假设从服务器中获取到最新的版本号为3
//如果检测本程序的版本号小于服务器的版本号,那么提示用户更新
if (getVersionCode() < 3) {
showDialogUpdate();//弹出提示版本更新的对话框
} else {
//否则弹窗,说现在是最新的版本
Toast.makeText(this, "当前已是最新版本", Toast.LENGTH_SHORT).show();
}
}
之后在要检测更新界面的Activity界面下写 相关代码上面的Oncreate
下的代码直接调用了这块,如下所示:
/*
//检测本程序的版本,这里假设从服务器中获取到最新的版本号为3
public 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
//Toast.makeText(MainActivity.this, "选择确定哦", 0).show();
loadNewVersionProgress();//下载最新的版本程序
}
}). .
// 设置取消按钮,null是什么都不做,并关闭对话框
// setNegativeButton("取消", null);
setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
// 生产对话框
AlertDialog alertDialog = builder.create();
// 显示对话框
alertDialog.show();
}
/**
* 下载新版本程序
*/
private void loadNewVersionProgress() {
final String uri = "https://www.armpro/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() {
@Override
public 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/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
public static final String UPDATE_FORCE_URL = "https://gitee/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
public static final String DOWNLOAD_TEST_URL = "https://gitee/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_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
...
// androidx project
implementation '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下,加入以下成员变量信息,用来控制以后的远程更新、版本检测、版本强制更新等等信息。
public static final String UPDATE_TEST_URL = "https://gitee/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
public static final String UPDATE_FORCE_URL = "https://gitee/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
public static final String DOWNLOAD_TEST_URL = "https://gitee/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/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/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
public static final String UPDATE_FORCE_URL = "https://gitee/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
public static final String DOWNLOAD_TEST_URL = "https://gitee/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";
@Override
protected 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 {
@Override
protected void attachBaseContext(Context base) {
EasyUpdate.setUpdateConfigProvider(new IUpdateConfigProvider() {
@Override
public UpdateConfig getUpdateConfig(Context context) {
return UpdateConfig.create().setIsDebug(true) //开启Debug 日志功能,查看详细的日志信息
.setDownloadServiceProxy(new AriaDownloadServiceProxyImpl(context)); //设置断点续传功能
}
});
super.attachBaseContext(base);
}
}
在安卓的AndroidManifest.xml
下application
标签下写入 android:name=".你的类名"
,我这里根据我创建的位置为:android:name=".dao.MyApp"
,具体代码如下所示:
<application
android: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 {
@Override
public 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() { //设置版本更新出错的监听
@Override
public 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.xml
下application
标签下写入 android:name=".你的类名"
,我这里根据我创建的位置为:android:name=".dao.MyApp"
,具体代码如下所示:
<application
android: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/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
public static final String UPDATE_FORCE_URL = "https://gitee/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
public static final String DOWNLOAD_TEST_URL = "https://gitee/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.使用原生系统的更新弹窗
在自定义图片和主题弹窗
的基础上,只需要配置两个类HProgressDialogUtils
和CustomUpdatePrompter
,以及稍微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() {
@Override
public void onClick(DialogInterface dialog, int which) {
updateProxy.startDownload(updateEntity, new OnFileDownloadListener() {
@Override
public void onStart() {
HProgressDialogUtils.showHorizontalProgressDialog(updateProxy.getContext(), "下载进度", false);
}
@Override
public void onProgress(float progress, long total) {
HProgressDialogUtils.setProgress(Math.round(progress * 100));
}
@Override
public boolean onCompleted(File file) {
HProgressDialogUtils.cancel();
return true; //这个true表示是直接安装,需要Root权限,否则就是只下载不提示安装
}
@Override
public void onError(Throwable throwable) {
HProgressDialogUtils.cancel();
}
});
}
});
if (updateEntity.isIgnorable()) {
builder.setNegativeButton("暂不升级", new DialogInterface.OnClickListener() {
@Override
public 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 提示界面参数
*/
@Override
public 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/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
public static final String UPDATE_FORCE_URL = "https://gitee/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
public static final String DOWNLOAD_TEST_URL = "https://gitee/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";
@Override
protected 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_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
...
//JsonUtil 报红的话 添加这个
implementation 'com.github.xuexiangjys.XUtil:xutil-core:2.0.0'
// androidx project
implementation '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/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/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 {
@Override
public 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() { //设置版本更新出错的监听
@Override
public 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.xml
下application
标签找 android:name=".你的类名"
,找到后直接按住Ctrl+鼠标点击
即可跳转,并将上面的代码加入Oncreate
中;如果没有的话,就要自己创建类名并且按照下面代码的自己搞一个。
<application
android: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/xuexiangjys/XUpdate/raw/master/jsonapi/update_custom.json";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//默认App更新 + 自定义Api + 自定义提示弹窗(系统)
XUpdate.newBuild(this)
.updateUrl(mUpdateUrl3)
.updateChecker(new DefaultUpdateChecker() {
@Override
public void onBeforeCheck() {
super.onBeforeCheck();
CProgressDialogUtils.showProgressDialog(Login更新界面Activity.this, "查询中...");
}
@Override
public void onAfterCheck() {
super.onAfterCheck();
CProgressDialogUtils.cancelProgressDialog(Login更新界面Activity.this);
}
@Override
public 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 {
//不相等,所以取消任何ProgressDialog
cancelProgressDialog();
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 {
@Override
public 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;
}
@Override
public void parseJson(String json, @NonNull IUpdateParseCallback callback) throws Exception {
//当isAsyncParser为 true时调用该方法, 所以当isAsyncParser为false可以不实现
callback.onParseResult(getParseResult(json));
}
@Override
public 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
updateProxy.startDownload(updateEntity, new OnFileDownloadListener() {
@Override
public void onStart() {
HProgressDialogUtils.showHorizontalProgressDialog(updateProxy.getContext(), "下载进度", false);
}
@Override
public void onProgress(float progress, long total) {
HProgressDialogUtils.setProgress(Math.round(progress * 100));
}
@Override
public boolean onCompleted(File file) {
HProgressDialogUtils.cancel();
return true; //这个表示是直接安装,需要Root权限
}
@Override
public void onError(Throwable throwable) {
HProgressDialogUtils.cancel();
}
});
}
});
if (updateEntity.isIgnorable()) { //默认为false就是不显示暂不升级 -- 达到强制更新效果
builder.setNegativeButton("暂不升级", new DialogInterface.OnClickListener() {
@Override
public 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
*/
@NonNull
public 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 提示界面参数
*/
@Override
public 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/xuexiangjys
Github项目: https://github/xuexiangjys/XUpdate
快速使用XUpdateAPI: https://github/xuexiangjys/XUpdateAPI
更多推荐
Android开发 应用软件更新通用方式--强制/非强制/远程控制/浏览器 更新
发布评论