一、安卓的版本更新
在移动互联网时代,App 版本更新是应用程序维护和优化的重要环节。通过定期更新,开发者可以提升用户体验、修复漏洞,并保持竞争力
版本大致更新例子如下
二、AppUpdater for Android介绍
AppUpdater特点:
- ✅ 专注于App更新一键傻瓜式升级
- ✅ 够轻量,体积小
- ✅ 支持监听下载和自定义下载流程
- ✅ 支持下载失败时,可重新下载
- ✅ 支持文件MD5校验,避免重复下载
- ✅ 支持通知栏提示内容和流程全部可配置
- ✅ 支持取消下载
- ✅ 支持使用HttpsURLConnection或OkHttpClient进行下载
- ✅ 支持Android 10(Q)
- ✅ 支持Android 11(R)
- ✅ 支持Android 12(S)
三、具体使用步骤
1. 在Project的 settings.gradle 里面添加远程仓库
xml
allprojects {
repositories {
//...
mavenCentral()
}
}
2. 在Module的 build.gradle 里面添加引入依赖项
xml
implementation 'com.github.jenly1314.AppUpdater:app-updater:1.2.0'
implementation 'com.github.jenly1314.AppUpdater:app-dialog:1.2.0'
3.新建VersionUpdateActivity
JAVA
package com.imindbot.medicalinformation.ui.test;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.imindbot.medicalinformation.BuildConfig;
import com.imindbot.medicalinformation.R;
import com.imindbot.medicalinformation.data.response.VersionInfoResponse;
import com.imindbot.medicalinformation.databinding.ActivityUploadDataBinding;
import com.imindbot.medicalinformation.databinding.ActivityVersionUpdateBinding;
import com.imindbot.medicalinformation.utils.VersionInfo;
import com.imindbot.medicalinformation.utils.VersionUtil;
import com.king.app.dialog.AppDialog;
import com.king.app.dialog.AppDialogConfig;
import com.king.app.updater.AppUpdater;
import com.king.app.updater.UpdateConfig;
import com.king.app.updater.callback.AppUpdateCallback;
import com.king.app.updater.callback.UpdateCallback;
import com.king.app.updater.http.OkHttpManager;
import java.io.File;
public class VersionUpdateActivity extends AppCompatActivity {
private final Object mLock = new Object();
private String mUrl = "https://gitlab.com/jenly1314/AppUpdater/-/raw/master/app/release/app-release.apk";
private ActivityVersionUpdateBinding binding;
private TextView tvProgress;
private ProgressBar progressBar;
private Toast toast;
private AppUpdater mAppUpdater;
private int version;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityVersionUpdateBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
int versionCode = pInfo.versionCode; // 获取 versionCode
String versionName = pInfo.versionName; // 获取 versionName
Log.d("AppVersion", "versionCode: " + versionCode + ", versionName: " + versionName);
binding.version.setText("版本号:" + versionCode+"版本名字:"+versionName);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
public Context getContext() {
return this;
}
public void showToast(String text) {
if (toast == null) {
synchronized (mLock) {
if (toast == null) {
toast = Toast.makeText(getContext(), text, Toast.LENGTH_SHORT);
}
}
}
toast.setText(text);
toast.show();
}
/**
* 简单一键后台升级
*/
private void clickBtn1() {
// mAppUpdater = new AppUpdater(getContext(), mUrl);
// mAppUpdater.start();
VersionInfoResponse.VersionData versionData = new VersionInfoResponse.VersionData();
versionData.setVersionNumber("2.0.1");
versionData.setUpdateContent("修复了bug");
versionData.setDownloadUrl("1.0.1");
// saveVersionInfo(versionData);
VersionUtil.saveVersionInfo(getApplicationContext(), "3.0", "更新内容","下载地址");
// 更新 UI
updateUI();
}
/**
* 一键下载并监听
*/
private void clickBtn2() {
UpdateConfig config = new UpdateConfig();
config.setUrl(mUrl);
config.addHeader("token", "xxxxxx");
mAppUpdater = new AppUpdater(getContext(), config)
.setHttpManager(OkHttpManager.getInstance())
.setUpdateCallback(new UpdateCallback() {
@Override
public void onDownloading(boolean isDownloading) {
if (isDownloading) {
showToast("已经在下载中,请勿重复下载。");
} else {
// showToast("开始下载...");
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_progress, null);
tvProgress = view.findViewById(R.id.tvProgress);
progressBar = view.findViewById(R.id.progressBar);
AppDialog.INSTANCE.showDialog(getContext(), view, false);
}
}
@Override
public void onStart(String url) {
}
@Override
public void onProgress(long progress, long total, boolean isChanged) {
if (isChanged) {
updateProgress(progress, total);
}
}
@Override
public void onFinish(File file) {
AppDialog.INSTANCE.dismissDialog();
showToast("下载完成");
}
@Override
public void onError(Exception e) {
AppDialog.INSTANCE.dismissDialog();
showToast("下载失败");
}
@Override
public void onCancel() {
AppDialog.INSTANCE.dismissDialog();
showToast("取消下载");
}
});
mAppUpdater.start();
}
private void updateProgress(long progress, long total) {
if (tvProgress == null || progressBar == null) {
return;
}
if (progress > 0) {
int currProgress = (int) (progress * 1.0f / total * 100.0f);
tvProgress.setText(getString(R.string.app_updater_progress_notification_content) + currProgress + "%");
progressBar.setProgress(currProgress);
} else {
tvProgress.setText(getString(R.string.app_updater_start_notification_content));
}
}
/**
* 系统弹框升级
*/
private void clickBtn3() {
new AlertDialog.Builder(this)
.setTitle("发现新版本")
.setMessage("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setPositiveButton("升级", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mAppUpdater = new AppUpdater.Builder(getContext())
.setUrl(mUrl)
.setSupportCancelDownload(true)
.build()
.setUpdateCallback(new AppUpdateCallback() {
@Override
public void onStart(String url) {
super.onStart(url);
// 模仿系统自带的横幅通知效果
AppDialogConfig config = new AppDialogConfig(getContext(), R.layout.dialog_heads_up);
config.setStyleId(R.style.app_dialog_heads_up)
.setWidthRatio(.95f)
.setGravity(Gravity.TOP);
AppDialog.INSTANCE.showDialog(getContext(), config);
new CountDownTimer(2000, 500) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
AppDialog.INSTANCE.dismissDialog();
}
}.start();
}
@Override
public void onProgress(long progress, long total, boolean isChanged) {
}
@Override
public void onFinish(File file) {
showToast("下载完成");
}
});
mAppUpdater.start();
}
}).show();
}
/**
* 简单弹框升级
*/
private void clickBtn4() {
AppDialogConfig config = new AppDialogConfig(getContext());
config.setTitle("简单弹框升级")
.setConfirm("升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickConfirm(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAppUpdater = new AppUpdater(getContext(), mUrl);
mAppUpdater.start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(config);
}
/**
* 简单自定义弹框升级
*/
private void clickBtn5() {
AppDialogConfig config = new AppDialogConfig(getContext(), R.layout.dialog);
config.setConfirm("升级")
.setHideCancel(true)
.setTitle("简单自定义弹框升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickConfirm(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAppUpdater = new AppUpdater.Builder(getContext())
.setUrl(mUrl)
.build();
mAppUpdater.start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(config);
}
/**
* 自定义弹框,优先缓存升级
*/
private void clickBtn6() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_custom, null);
TextView tvTitle = view.findViewById(R.id.tvTitle);
tvTitle.setText("自定义弹框升级");
TextView tvContent = view.findViewById(R.id.tvContent);
tvContent.setText("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、");
View btnCancel = view.findViewById(R.id.btnCancel);
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AppDialog.INSTANCE.dismissDialog();
}
});
View btnConfirm = view.findViewById(R.id.btnConfirm);
btnConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAppUpdater = new AppUpdater.Builder(getContext())
.setUrl(mUrl)
// .setApkMD5("3df5b1c1d2bbd01b4a7ddb3f2722ccca")// 支持MD5校验,如果缓存APK的MD5与此MD5相同,则直接取本地缓存安装,推荐使用MD5校验的方式
.setVersionCode(BuildConfig.VERSION_CODE)// 支持versionCode校验,设置versionCode之后,新版本versionCode相同的apk只下载一次,优先取本地缓存,推荐使用MD5校验的方式
.setFilename("AppUpdater.apk")
.setVibrate(true)
.build();
mAppUpdater.setHttpManager(OkHttpManager.getInstance()).start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(getContext(), view);
}
/**
* 简单DialogFragment升级
*/
private void clickBtn7() {
AppDialogConfig config = new AppDialogConfig(getContext());
config.setTitle("简单DialogFragment升级")
.setConfirm("升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickConfirm(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAppUpdater = new AppUpdater.Builder(getContext())
.setUrl(mUrl)
.build();
mAppUpdater.setHttpManager(OkHttpManager.getInstance()) // 使用OkHttp的实现进行下载
.setUpdateCallback(new UpdateCallback() { // 更新回调
@Override
public void onDownloading(boolean isDownloading) {
// 下载中:isDownloading为true时,表示已经在下载,即之前已经启动了下载;为false时,表示当前未开始下载,即将开始下载
}
@Override
public void onStart(String url) {
// 开始下载
}
@Override
public void onProgress(long progress, long total, boolean isChanged) {
// 下载进度更新:建议在isChanged为true时,才去更新界面的进度;因为实际的进度变化频率很高
}
@Override
public void onFinish(File file) {
// 下载完成
}
@Override
public void onError(Exception e) {
// 下载失败
}
@Override
public void onCancel() {
// 取消下载
}
}).start();
AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager());
}
});
AppDialog.INSTANCE.showDialogFragment(getSupportFragmentManager(), config);
}
private void clickCancel() {
if (mAppUpdater != null) {
mAppUpdater.stop();
}
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
clickBtn1();
break;
case R.id.btn2:
clickBtn2();
break;
case R.id.btn3:
clickBtn3();
break;
case R.id.btn4:
clickBtn4();
break;
case R.id.btn5:
clickBtn5();
break;
case R.id.btn6:
clickBtn6();
break;
case R.id.btn7:
clickBtn7();
break;
case R.id.btnCancel:
clickCancel();
break;
}
}
//存储远程版本信息
private void saveVersionInfo(VersionInfoResponse.VersionData versionData) {
SharedPreferences sharedPreferences = getSharedPreferences("app_version", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
// 存储远程版本信息
editor.putString("version_number", versionData.getVersionNumber());
editor.putString("update_content", versionData.getUpdateContent());
editor.putString("download_url", versionData.getDownloadUrl());
// editor.putLong("version_digit", versionData.getVersionDigit());
editor.apply();
}
// 获取本地版本号
private String getLocalVersion() {
try {
PackageManager pm = getPackageManager();
PackageInfo pi = pm.getPackageInfo(getPackageName(), 0);
return pi.versionName; // 获取应用版本号
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return "0.0.0";
}
}
private void updateUI() {
// SharedPreferences sharedPreferences = getSharedPreferences("app_version", MODE_PRIVATE);
// String newVersion = sharedPreferences.getString("version_number", "未获取到版本号"); // 默认值是 "未获取到版本号"
VersionInfo versionInfo = VersionUtil.getVersionInfo(getApplicationContext());
binding.version.setText("版本号:" + versionInfo);
}
}
4.编写 activity_version_update.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.test.VersionUpdateActivity">
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="40dp"
android:text="简单一键后台升级"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="一键监听进度升级"
app:layout_constraintTop_toBottomOf="@+id/btn1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="系统弹框升级"
app:layout_constraintTop_toBottomOf="@+id/btn2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="简单弹框升级"
app:layout_constraintTop_toBottomOf="@+id/btn3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="简单自定义弹框升级"
app:layout_constraintTop_toBottomOf="@+id/btn4"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="自定义弹框,优先缓存升级"
app:layout_constraintTop_toBottomOf="@+id/btn5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn7"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="简单DialogFragment升级"
app:layout_constraintTop_toBottomOf="@+id/btn6"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btnCancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="取消下载"
app:layout_constraintTop_toBottomOf="@+id/btn7"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<TextView
android:id="@+id/version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:textSize="25sp"
android:text="版本号:"
tools:ignore="MissingConstraints">
</TextView>
</androidx.constraintlayout.widget.ConstraintLayout>
效果如下
5.color文件夹下 /src/main/res/color/btn_cancel_color_selector.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/text_666"/>
<item android:state_pressed="false" android:color="@color/text_999"/>
</selector>
6. app/src/main/res/drawable-v24/ic_launcher_foreground.xml
xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#ffffff"
android:offset="0.0" />
<item
android:color="#ffffff"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
7. app/src/main/res/drawable-v24/ic_launcher_foreground.xml
xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#ffffff"
android:offset="0.0" />
<item
android:color="#ffffff"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
8. app/src/main/res/drawable/btn_confirm_selector.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape>
<solid android:color="@color/colorAccent"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item android:state_pressed="true">
<shape>
<solid android:color="@color/colorPrimaryDark"/>
<corners android:radius="20dp"/>
</shape>
</item>
</selector>
9 . app/src/main/res/drawable/dialog_title_bg.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="#F5F5F5" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
</shape>
10. app/src/main/res/layout/dialog.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="30dp"
android:background="@drawable/app_dialog_bg"
android:orientation="vertical">
<TextView
android:id="@+id/tvDialogTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp"
android:lines="1"
android:textSize="16sp"
android:background="@drawable/dialog_title_bg"
android:text="@string/app_dialog_title"/>
<TextView
android:id="@+id/tvDialogContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textSize="@dimen/app_dialog_content_text_size"
android:textColor="@color/app_dialog_content_color"
android:layout_marginBottom="16dp"
android:lineSpacingMultiplier="1.2" />
<include layout="@layout/app_dialog_line_h"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<Button
android:id="@+id/btnDialogCancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="@string/app_dialog_cancel"
android:textSize="18sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
<include
android:id="@+id/line"
layout="@layout/app_dialog_line_v"/>
<Button
android:id="@+id/btnDialogConfirm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="@string/app_dialog_confirm"
android:textSize="18sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
</LinearLayout>
11. app/src/main/res/layout/dialog_custom.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="30dp"
android:background="@drawable/app_dialog_bg"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="6dp"
android:lines="1"
android:textSize="16sp" />
<TextView
android:id="@+id/tvContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:lineSpacingMultiplier="1" />
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp"
android:gravity="center_horizontal">
<TextView
android:id="@+id/btnConfirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:gravity="center"
android:layout_weight="1"
android:text="升级"
android:textSize="16sp"
android:textColor="@color/white"
android:background="@drawable/btn_confirm_selector"/>
<TextView
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:gravity="center"
android:layout_weight="1"
android:text="@string/ignore"
android:textSize="16sp"
android:textColor="@color/btn_cancel_color_selector" />
</LinearLayout>
</LinearLayout>
12. app/src/main/res/layout/dialog_heads_up.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:gravity="center_vertical"
android:background="@drawable/app_dialog_bg">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@mipmap/ic_launcher"
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:contentDescription="@null"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/ivIcon"
android:layout_toEndOf="@+id/ivIcon"
android:textSize="14sp"
android:textColor="@color/app_dialog_title_color"
android:text="@string/app_updater_start_notification_content"/>
</RelativeLayout>
13. app/src/main/res/layout/dialog_progress.xml
java
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:gravity="center_vertical"
android:background="@drawable/app_dialog_bg">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@mipmap/ic_launcher"
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:contentDescription="@null"/>
<TextView
android:id="@+id/tvDialogTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/ivIcon"
android:layout_toEndOf="@+id/ivIcon"
android:textSize="14sp"
android:textColor="@color/app_dialog_title_color"
android:text="@string/app_updater_progress_notification_title"/>
<TextView
android:id="@+id/tvProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tvDialogTitle"
android:layout_marginTop="10dp"
android:text="@string/app_updater_start_notification_content"
android:textSize="14sp"
android:textColor="@color/app_dialog_content_color"
android:lineSpacingMultiplier="1.2" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="6dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:max="100"
android:layout_below="@+id/tvProgress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"/>
</RelativeLayout>
14. app/src/main/res/values/colors.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#5588FF</color>
<color name="colorPrimaryDark">#4B7CFD</color>
<color name="colorAccent">#5588FF</color>
<color name="ic_launcher_background">#FFFFFF</color>
<color name="white">#FFFFFF</color>
<color name="text_666">#666666</color>
<color name="text_999">#999999</color>
</resources>
15. app/src/main/res/values/strings.xml
xml
<resources>
<string name="app_name">AppUpdater</string>
<string name="ignore">忽略</string>
</resources>
16. app/src/main/res/values/styles.xml
xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textAllCaps">false</item>
</style>
<style name="app_dialog_heads_up" parent="android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowAnimationStyle">@style/app_dialog_top_animation</item>
</style>
<style name="OnClick">
<item name="android:clickable">true</item>
<item name="android:onClick">onClick</item>
</style>
</resources>
四、具体实战
Java
private VersionUpgradeModel versionUpgradeModel;
// 初始化 ViewModel
versionUpgradeModel = new ViewModelProvider(this).get(VersionUpgradeModel.class);
//检查新版本
binding.checkNewVersion.setOnClickListener(v -> {
if (!isRequestInProgress) {
isRequestInProgress = true; // 请求开始,设置标志位
updateVersion(); // 发送请求获取版本信息
}
});
//tv_version 版本哈
binding.tvVersion.setText("App版本:"+getLocalVersion());
versionUpgradeModel.getVersionInfoResponse().observe(this, new Observer<VersionInfoResponse>() {
@Override
public void onChanged(@Nullable VersionInfoResponse versionInfoResponse) {
if (versionInfoResponse != null) {
if (versionInfoResponse.code == 200) {
Log.i(GetTokenUtil.COMMON_TAG, "版本信息:" + versionInfoResponse);
// versionUpdate(versionInfoResponse.getData().getVersionNumber(), versionInfoResponse.getData().getUpdateContent(),versionInfoResponse.getData().getDownloadUrl());
// 进行版本比较并决定是否更新
checkForUpdate(versionInfoResponse.getData());
} else {
// 如果接口返回失败,可以显示错误提示
// Toast.makeText(AddHealthCheckupActivity.this, "获取版本信息失败", Toast.LENGTH_SHORT).show();
}
}
isRequestInProgress = false; // 请求结束,重置标志位
}
});
//获取本地版本信息并进行比较
private void checkForUpdate(VersionInfoResponse.VersionData versionData) {
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
int versionCode = pInfo.versionCode; // 获取 versionCode
String versionName = pInfo.versionName; // 获取 versionName
Log.d("AppVersion", "versionCode: " + versionCode + ", versionName: " + versionName);
if (VersionComparator.compare(versionData.versionNumber,versionName )) {
// 本地版本小于远程版本,提示更新
versionUpdate(versionData.getVersionNumber(), versionData.getUpdateContent(), versionData.getDownloadUrl());
} else {
// 本地版本已是最新,无需更新
ToastUtils.showSuccess("当前已是最新版本");
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
private void versionUpdate(String versionNumber, String versionContent, String apkUrl) {
// 确保Activity处于有效状态
if (!isFinishing() && !isDestroyed()) {
// 使用Activity的上下文
AppDialogConfig config = new AppDialogConfig(SystemSettingActivity.this);
config.setTitle("版本升级提示")
.setConfirm("升级") // 旧版本使用 setOk
// .setContent("版本号: " + versionNumber + "\n" + "\n"+ "App版本更新内容: "+"\n" + UpdateContentExtractor.extractAndFormatUpdateContent(versionContent))
.setContent( "App版本更新内容: "+"\n" + UpdateContentExtractor.extractAndFormatUpdateContent(versionContent)+"\n"+"\n"+"版本号: " + versionNumber)
.setOnClickConfirm(new View.OnClickListener() { // 旧版本使用 setOnClickOk
@Override
public void onClick(View v) {
UpdateConfig config = new UpdateConfig();
config.setUrl(apkUrl);
config.addHeader("token", "xxxxxx");
mAppUpdater = new AppUpdater(getContext(), config)
.setHttpManager(OkHttpManager.getInstance())
.setUpdateCallback(new UpdateCallback() {
@Override
public void onDownloading(boolean isDownloading) {
if (isDownloading) {
showToast("已经在下载中,请勿重复下载。");
} else {
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_progress, null);
tvProgress = view.findViewById(R.id.tvProgress);
progressBar = view.findViewById(R.id.progressBar);
AppDialog.INSTANCE.showDialog(getContext(), view, false);
}
}
@Override
public void onStart(String url) {
}
@Override
public void onProgress(long progress, long total, boolean isChanged) {
if (isChanged) {
updateProgress(progress, total);
}
}
@Override
public void onFinish(File file) {
AppDialog.INSTANCE.dismissDialog();
// showToast("下载完成");
ToastUtils.showSuccess("下载完成");
}
@Override
public void onError(Exception e) {
AppDialog.INSTANCE.dismissDialog();
// showToast("下载失败");
ToastUtils.showError("下载失败");
}
@Override
public void onCancel() {
AppDialog.INSTANCE.dismissDialog();
ToastUtils.showInfo("取消下载");
// showToast("取消下载");
}
});
mAppUpdater.start();
// 关闭对话框
AppDialog.INSTANCE.dismissDialog();
}
});
// 显示Dialog
AppDialog.INSTANCE.showDialog(SystemSettingActivity.this, config);
}
}
public void updateVersion( ) {
updateVersionExecutor.execute(new Runnable() {
@Override
public void run() {
Log.i(GetTokenUtil.COMMON_TAG, "开始查询版本信息:" );
versionUpgradeModel.checkVersion();
}
});
}
// 获取本地版本号
private String getLocalVersion() {
try {
PackageManager pm = getPackageManager();
PackageInfo pi = pm.getPackageInfo(getPackageName(), 0);
return pi.versionName; // 获取应用版本号
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return "0.0.0";
}
}
//升级进度条
private void updateProgress(long progress, long total) {
if (tvProgress == null || progressBar == null) {
return;
}
if (progress > 0) {
int currProgress = (int) (progress * 1.0f / total * 100.0f);
tvProgress.setText(getString(R.string.app_updater_progress_notification_content) + currProgress + "%");
progressBar.setProgress(currProgress);
} else {
tvProgress.setText(getString(R.string.app_updater_start_notification_content));
}
}