fix: 修复应用更新检查机制
🔧 更新检查优化: - 复刻 FongMi/TV 的更新检查机制 - 使用独立 Release 仓库托管更新信息 - 避免 GitHub API 频率限制问题 - 默认启用自动更新检查功能 📂 文件变更: - 重写手机版 Updater.java,统一更新检查逻辑 - 修改 Github.java,指向新的 Release 仓库 - 启用 getAutoUpdateCheck() 默认值为 true ✅ 功能改进: - 手机版和TV版使用相同的更新检查机制 - 支持国内外网络环境自动切换 - 错误处理和用户提示优化
This commit is contained in:
@@ -0,0 +1,131 @@
|
|||||||
|
# XMBOX v3.0.7 Release Notes
|
||||||
|
|
||||||
|
## 🎯 重要更新
|
||||||
|
|
||||||
|
XMBOX v3.0.7 是一个重要的稳定性和用户体验升级版本,包含关键崩溃修复和全面的UI优化。
|
||||||
|
|
||||||
|
## 🐛 核心修复
|
||||||
|
|
||||||
|
### 关键崩溃修复
|
||||||
|
- **修复 VodConfig 空指针崩溃** - 解决应用销毁时的 NullPointerException
|
||||||
|
- **修复 LiveConfig 初始化问题** - 增强单例模式的安全性
|
||||||
|
- **优化生命周期管理** - 改进 Activity 销毁时的资源清理
|
||||||
|
|
||||||
|
### 稳定性提升
|
||||||
|
- 添加构造函数初始化,防止 clear() 方法空指针异常
|
||||||
|
- 增强错误处理机制和异常捕获
|
||||||
|
- 全面的空值安全检查
|
||||||
|
|
||||||
|
## 🎨 UI/UX 全面升级
|
||||||
|
|
||||||
|
### 新增隐私协议页面
|
||||||
|
- 符合应用商店规范的隐私政策界面
|
||||||
|
- 完善的任务栈管理,防止用户回退到协议页面
|
||||||
|
- 优雅的应用退出机制
|
||||||
|
|
||||||
|
### 界面优化
|
||||||
|
- **修复按钮文字显示** - 解决长文本显示不完整问题
|
||||||
|
- 调整按钮文字大小:16sp → 13sp
|
||||||
|
- 增加按钮高度:48dp → 56dp
|
||||||
|
- 支持多行文本显示和居中对齐
|
||||||
|
|
||||||
|
### 空状态优化
|
||||||
|
- **恢复完整 Lottie 动画** - 54KB 高质量动画效果
|
||||||
|
- **位置调整** - 空状态动画向上移动 40dp,提升视觉平衡
|
||||||
|
- **川渝方言文案** - 更新为 "这里撒子内容都没得~"
|
||||||
|
- 新增多个专用空状态布局:搜索、收藏、通用
|
||||||
|
|
||||||
|
## 📺 TV版本专项优化
|
||||||
|
|
||||||
|
### 选集按钮优化
|
||||||
|
- **黄色高亮显示** - 选中状态文字改为黄色 (#FFEB3B)
|
||||||
|
- **专用颜色方案** - 新增 episode_text.xml 选择器
|
||||||
|
- **精准影响范围** - 仅修改视频详情页,不干扰其他界面
|
||||||
|
|
||||||
|
### 视觉体验提升
|
||||||
|
- 保持与手机版一致的核心功能
|
||||||
|
- 针对电视遥控器操作优化
|
||||||
|
- 更清晰的焦点状态指示
|
||||||
|
|
||||||
|
## ⚡ 技术改进
|
||||||
|
|
||||||
|
### 架构优化
|
||||||
|
- 改进单例模式的实现
|
||||||
|
- 增强并发安全性
|
||||||
|
- 优化内存使用效率
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
- 添加完善的空值检查
|
||||||
|
- 改进异常处理逻辑
|
||||||
|
- 增强调试和错误报告机制
|
||||||
|
|
||||||
|
## 📱 平台支持
|
||||||
|
|
||||||
|
### Android 兼容性
|
||||||
|
- **最低要求**: Android 5.0 (API 21)
|
||||||
|
- **推荐版本**: Android 7.0 及以上
|
||||||
|
- **架构支持**: ARM64-V8A、ARM V7A
|
||||||
|
|
||||||
|
### 设备支持
|
||||||
|
- 手机、平板设备
|
||||||
|
- Android TV、机顶盒
|
||||||
|
- 各类智能电视设备
|
||||||
|
|
||||||
|
## 🔧 开发者信息
|
||||||
|
|
||||||
|
### 构建信息
|
||||||
|
- **编译日期**: 2025-09-26
|
||||||
|
- **Gradle 版本**: 8.13
|
||||||
|
- **Target SDK**: 34
|
||||||
|
- **签名**: 正式发布签名
|
||||||
|
|
||||||
|
### 文件大小
|
||||||
|
- **手机版 ARM64**: 37MB
|
||||||
|
- **手机版 ARM32**: 32MB
|
||||||
|
- **TV版 ARM64**: 34MB
|
||||||
|
- **TV版 ARM32**: 30MB
|
||||||
|
|
||||||
|
## 📋 安装说明
|
||||||
|
|
||||||
|
### 首次安装
|
||||||
|
1. 下载对应架构的 APK 文件
|
||||||
|
2. 允许安装未知来源应用
|
||||||
|
3. 首次启动会显示隐私协议页面
|
||||||
|
4. 同意协议后即可正常使用
|
||||||
|
|
||||||
|
### 从旧版本升级
|
||||||
|
- 支持从 v3.0.0 及以上版本直接升级
|
||||||
|
- 用户数据和设置将自动保留
|
||||||
|
- 建议清除应用缓存以获得最佳体验
|
||||||
|
|
||||||
|
## ⚠️ 重要提醒
|
||||||
|
|
||||||
|
### 合规使用
|
||||||
|
- 本软件仅为技术性多媒体播放器外壳
|
||||||
|
- 不包含任何音视频内容
|
||||||
|
- 用户需自行配置合法的内容源
|
||||||
|
- 请遵守当地法律法规
|
||||||
|
|
||||||
|
### 隐私保护
|
||||||
|
- 新增的隐私协议确保用户知情同意
|
||||||
|
- 不收集用户个人信息
|
||||||
|
- 本地数据处理,保护用户隐私
|
||||||
|
|
||||||
|
## 🙏 致谢
|
||||||
|
|
||||||
|
感谢所有用户的反馈和建议,特别是:
|
||||||
|
- 崩溃报告和调试信息的提供者
|
||||||
|
- UI/UX 改进建议的贡献者
|
||||||
|
- 测试不同设备兼容性的志愿者
|
||||||
|
|
||||||
|
## 📞 支持与反馈
|
||||||
|
|
||||||
|
- **GitHub Issues**: https://github.com/Tosencen/XMBOX/issues
|
||||||
|
- **功能请求**: https://github.com/Tosencen/XMBOX/discussions
|
||||||
|
- **Bug 报告**: 请提供详细的设备信息和复现步骤
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**基于 FongMi/TV 项目开发 | GPL-3.0 开源协议**
|
||||||
|
|
||||||
|
> 如果这个项目对你有帮助,请给我们一个 ⭐ Star!
|
||||||
@@ -202,7 +202,7 @@ public class Setting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getAutoUpdateCheck() {
|
public static boolean getAutoUpdateCheck() {
|
||||||
return Prefers.getBoolean("auto_update_check", false);
|
return Prefers.getBoolean("auto_update_check", true); // 默认启用自动更新检查
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putAutoUpdateCheck(boolean autoUpdateCheck) {
|
public static void putAutoUpdateCheck(boolean autoUpdateCheck) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.view.View;
|
|||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.fongmi.android.tv.App;
|
|
||||||
import com.fongmi.android.tv.databinding.DialogUpdateBinding;
|
import com.fongmi.android.tv.databinding.DialogUpdateBinding;
|
||||||
import com.fongmi.android.tv.utils.Download;
|
import com.fongmi.android.tv.utils.Download;
|
||||||
import com.fongmi.android.tv.utils.FileUtil;
|
import com.fongmi.android.tv.utils.FileUtil;
|
||||||
@@ -15,11 +14,10 @@ import com.fongmi.android.tv.utils.Notify;
|
|||||||
import com.fongmi.android.tv.utils.ResUtil;
|
import com.fongmi.android.tv.utils.ResUtil;
|
||||||
import com.github.catvod.net.OkHttp;
|
import com.github.catvod.net.OkHttp;
|
||||||
import com.github.catvod.utils.Github;
|
import com.github.catvod.utils.Github;
|
||||||
|
import com.github.catvod.utils.Logger;
|
||||||
import com.github.catvod.utils.Path;
|
import com.github.catvod.utils.Path;
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.orhanobut.logger.Logger;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -28,26 +26,20 @@ import java.util.Locale;
|
|||||||
public class Updater implements Download.Callback {
|
public class Updater implements Download.Callback {
|
||||||
|
|
||||||
private DialogUpdateBinding binding;
|
private DialogUpdateBinding binding;
|
||||||
private Download download;
|
private final Download download;
|
||||||
private AlertDialog dialog;
|
private AlertDialog dialog;
|
||||||
private boolean dev;
|
private boolean dev;
|
||||||
private String downloadUrl;
|
|
||||||
private boolean forceCheck; // 标记是否是用户主动检查更新
|
|
||||||
|
|
||||||
private File getFile() {
|
private File getFile() {
|
||||||
return Path.cache("update.apk");
|
return Path.cache("update.apk");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getApkName() {
|
private String getJson() {
|
||||||
return "mobile-" + BuildConfig.FLAVOR_abi + ".apk";
|
return Github.getJson(dev, BuildConfig.FLAVOR_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getJson() {
|
private String getApk() {
|
||||||
String url = Github.getReleaseApi();
|
return Github.getApk(dev, BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_abi);
|
||||||
boolean usingCnMirror = Github.useCnMirror();
|
|
||||||
Logger.d("Using CN Mirror: " + usingCnMirror);
|
|
||||||
Logger.d("Update check URL: " + url);
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Updater create() {
|
public static Updater create() {
|
||||||
@@ -55,14 +47,12 @@ public class Updater implements Download.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Updater() {
|
public Updater() {
|
||||||
this.download = Download.create("", getFile(), this);
|
this.download = Download.create(getApk(), getFile(), this);
|
||||||
this.forceCheck = false; // 默认不是用户主动检查更新
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Updater force() {
|
public Updater force() {
|
||||||
Notify.show(R.string.update_check);
|
Notify.show(R.string.update_check);
|
||||||
Setting.putUpdate(true);
|
Setting.putUpdate(true);
|
||||||
this.forceCheck = true; // 标记为用户主动检查更新
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,112 +72,46 @@ public class Updater implements Download.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start(Activity activity) {
|
public void start(Activity activity) {
|
||||||
|
// 检查是否启用启动时自动检查更新
|
||||||
|
if (!Setting.getAutoUpdateCheck()) {
|
||||||
|
Logger.d("Auto update check is disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
App.execute(() -> doInBackground(activity));
|
App.execute(() -> doInBackground(activity));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean need(String tagName) {
|
private boolean need(int code, String name) {
|
||||||
Logger.d("Current version: " + BuildConfig.VERSION_NAME);
|
return Setting.getUpdate() && (dev ? !name.equals(BuildConfig.VERSION_NAME) && code >= BuildConfig.VERSION_CODE : code > BuildConfig.VERSION_CODE);
|
||||||
Logger.d("Latest version: " + tagName);
|
|
||||||
if (tagName.startsWith("v")) tagName = tagName.substring(1);
|
|
||||||
|
|
||||||
// 版本比较逻辑
|
|
||||||
try {
|
|
||||||
String[] currentParts = BuildConfig.VERSION_NAME.split("\\.");
|
|
||||||
String[] remoteParts = tagName.split("\\.");
|
|
||||||
|
|
||||||
// 比较主版本号
|
|
||||||
for (int i = 0; i < Math.min(currentParts.length, remoteParts.length); i++) {
|
|
||||||
int current = Integer.parseInt(currentParts[i]);
|
|
||||||
int remote = Integer.parseInt(remoteParts[i]);
|
|
||||||
|
|
||||||
if (remote > current) {
|
|
||||||
return Setting.getUpdate(); // 远程版本高于当前版本,需要更新
|
|
||||||
} else if (remote < current) {
|
|
||||||
return false; // 远程版本低于当前版本,不需要更新
|
|
||||||
}
|
|
||||||
// 如果相等,继续比较下一级版本号
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果前面的版本号都相等,但远程版本有更多的版本号,视为更新
|
|
||||||
if (remoteParts.length > currentParts.length) {
|
|
||||||
return Setting.getUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // 版本相同或远程版本较低,不需要更新
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// 如果版本号解析失败,退回到简单字符串比较
|
|
||||||
Logger.e("Version parsing failed", e);
|
|
||||||
return Setting.getUpdate() && !tagName.equals(BuildConfig.VERSION_NAME);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doInBackground(Activity activity) {
|
private void doInBackground(Activity activity) {
|
||||||
try {
|
try {
|
||||||
String jsonUrl = getJson();
|
String response = OkHttp.string(getJson());
|
||||||
Logger.d("Fetching update info from: " + jsonUrl);
|
|
||||||
String response = OkHttp.string(jsonUrl);
|
|
||||||
Logger.d("Update check response: " + response);
|
|
||||||
|
|
||||||
// 检查响应是否包含错误信息,只有在用户主动检查更新时才显示错误提示
|
// 检查响应是否包含错误信息
|
||||||
if (response.contains("rate limit exceeded")) {
|
if (response.contains("rate limit exceeded")) {
|
||||||
showErrorIfForceCheck("检查更新失败:API请求过于频繁,请稍后重试");
|
App.post(() -> Notify.show("检查更新失败:API请求过于频繁,请稍后重试"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.contains("Not Found Project") || response.contains("Not Found")) {
|
if (response.contains("Not Found Project") || response.contains("Not Found")) {
|
||||||
showErrorIfForceCheck("检查更新失败:更新服务暂时不可用");
|
App.post(() -> Notify.show("检查更新失败:更新服务暂时不可用"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject release = new JSONObject(response);
|
JSONObject object = new JSONObject(response);
|
||||||
String tagName = release.getString("tag_name");
|
String name = object.optString("name");
|
||||||
String body = release.getString("body");
|
String desc = object.optString("desc");
|
||||||
JSONArray assets = release.getJSONArray("assets");
|
int code = object.optInt("code");
|
||||||
|
if (need(code, name)) {
|
||||||
// Find the correct APK asset
|
App.post(() -> show(activity, name, desc));
|
||||||
String apkName = getApkName();
|
|
||||||
for (int i = 0; i < assets.length(); i++) {
|
|
||||||
JSONObject asset = assets.getJSONObject(i);
|
|
||||||
if (asset.getString("name").equals(apkName)) {
|
|
||||||
downloadUrl = asset.getString("browser_download_url");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadUrl != null && need(tagName)) {
|
|
||||||
download = Download.create(downloadUrl, getFile(), this);
|
|
||||||
App.post(() -> show(activity, tagName, body));
|
|
||||||
} else if (downloadUrl != null) {
|
|
||||||
// 找到APK但不需要更新,只在用户主动检查更新时提示已是最新版
|
|
||||||
if (forceCheck) {
|
|
||||||
App.post(() -> Notify.show("已是最新版本 " + tagName));
|
|
||||||
}
|
|
||||||
Logger.d("Already latest version: " + tagName);
|
|
||||||
} else {
|
} else {
|
||||||
// 未找到对应的APK文件
|
// 不需要更新,提示已是最新版
|
||||||
// 只在用户主动检查更新时显示提示
|
Logger.d("Already latest version: " + name);
|
||||||
if (forceCheck) {
|
|
||||||
App.post(() -> Notify.show("检查更新完成,未找到适合此设备的安装包"));
|
|
||||||
}
|
|
||||||
Logger.d("APK not found for this device");
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.e("Update check failed", e);
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
// 添加用户友好的错误提示,只有在用户主动检查更新时才显示
|
App.post(() -> Notify.show("检查更新失败:网络连接异常"));
|
||||||
App.post(() -> {
|
|
||||||
if (forceCheck) { // 只有在用户主动检查更新时才显示错误提示
|
|
||||||
String errorMsg = "检查更新失败";
|
|
||||||
if (e.getMessage() != null && e.getMessage().contains("rate limit")) {
|
|
||||||
errorMsg = "检查更新失败:API请求过于频繁,请稍后重试"; // 统一错误提示文本
|
|
||||||
} else if (e.getMessage() != null && e.getMessage().contains("Not Found")) {
|
|
||||||
errorMsg = "检查更新失败:更新服务暂时不可用";
|
|
||||||
} else {
|
|
||||||
errorMsg = "检查更新失败,请稍后重试";
|
|
||||||
}
|
|
||||||
Notify.show(errorMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,16 +145,6 @@ public class Updater implements Download.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 只有在用户主动检查更新时才显示错误提示
|
|
||||||
* @param message 错误提示消息
|
|
||||||
*/
|
|
||||||
private void showErrorIfForceCheck(String message) {
|
|
||||||
if (forceCheck) {
|
|
||||||
App.post(() -> Notify.show(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void progress(int progress) {
|
public void progress(int progress) {
|
||||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(String.format(Locale.getDefault(), "%1$d%%", progress));
|
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(String.format(Locale.getDefault(), "%1$d%%", progress));
|
||||||
@@ -238,7 +152,6 @@ public class Updater implements Download.Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void error(String msg) {
|
public void error(String msg) {
|
||||||
Logger.e("Download error: " + msg);
|
|
||||||
Notify.show(msg);
|
Notify.show(msg);
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
@@ -248,4 +161,4 @@ public class Updater implements Download.Callback {
|
|||||||
FileUtil.openFile(file);
|
FileUtil.openFile(file);
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,12 +14,10 @@ import okhttp3.Response;
|
|||||||
|
|
||||||
public class Github {
|
public class Github {
|
||||||
|
|
||||||
public static final String URL = "https://raw.githubusercontent.com/Tosencen/XMBOX/main";
|
public static final String URL = "https://raw.githubusercontent.com/Tosencen/XMBOX-Release/main";
|
||||||
public static final String API_URL = "https://api.github.com/repos/Tosencen/XMBOX/releases/latest";
|
|
||||||
|
|
||||||
// 国内镜像地址 - 使用Gitee作为镜像
|
// 国内镜像地址 - 使用Gitee作为镜像
|
||||||
public static final String CN_URL = "https://gitee.com/ochenoktochen/XMBOX/raw/main";
|
public static final String CN_URL = "https://gitee.com/ochenoktochen/XMBOX-Release/raw/main";
|
||||||
public static final String CN_API_URL = "https://gitee.com/api/v5/repos/ochenoktochen/XMBOX/releases/latest";
|
|
||||||
|
|
||||||
// 存储测速结果
|
// 存储测速结果
|
||||||
private static Boolean useCnMirror = null;
|
private static Boolean useCnMirror = null;
|
||||||
@@ -34,9 +32,6 @@ public class Github {
|
|||||||
return CN_URL + "/" + path + "/" + name;
|
return CN_URL + "/" + path + "/" + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getReleaseApi() {
|
|
||||||
return useCnMirror() ? CN_API_URL : API_URL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getJson(boolean dev, String name) {
|
public static String getJson(boolean dev, String name) {
|
||||||
if (useCnMirror()) {
|
if (useCnMirror()) {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# GitHub CLI 创建 Release 脚本
|
||||||
|
# 使用前请先运行: gh auth login
|
||||||
|
|
||||||
|
echo "创建 XMBOX v3.0.7 Release..."
|
||||||
|
|
||||||
|
gh release create v3.0.7 \
|
||||||
|
--title "XMBOX v3.0.7 - 全面优化稳定性和用户体验" \
|
||||||
|
--notes-file RELEASE_NOTES_v3.0.7.md \
|
||||||
|
--draft \
|
||||||
|
~/Desktop/mobile-arm64_v8a-v3.0.7.apk \
|
||||||
|
~/Desktop/mobile-armeabi_v7a-v3.0.7.apk \
|
||||||
|
~/Desktop/leanback-arm64_v8a-v3.0.7.apk \
|
||||||
|
~/Desktop/leanback-armeabi_v7a-v3.0.7.apk
|
||||||
|
|
||||||
|
echo "Release 创建完成(草稿状态)"
|
||||||
|
echo "请在 GitHub 上检查并发布"
|
||||||
Reference in New Issue
Block a user