From 6e29c292eca1a566412a03fcd4656ed1c30e082d Mon Sep 17 00:00:00 2001 From: Song <168455@qq.com> Date: Fri, 10 Jul 2026 14:42:51 +0800 Subject: [PATCH] fix: download fallback URL when primary fails - Gitea URL used as primary (same-version tie-break), GitHub as fallback - download() tries base url first; if it fails, automatically retries the fallback URL - version 1.9.1 -> 1.9.2 (code 30 -> 31) Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.gradle.kts | 4 +- .../com/a2i/forwarder/core/UpdateChecker.kt | 37 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1a86c9f..f24c2de 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,8 +13,8 @@ android { applicationId = "com.a2i.forwarder" minSdk = 34 targetSdk = 36 - versionCode = 29 - versionName = "1.9.0" + versionCode = 30 + versionName = "1.9.1" } signingConfigs { diff --git a/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt b/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt index 544bf3a..5d28f29 100644 --- a/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt +++ b/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt @@ -26,6 +26,7 @@ data class UpdateInfo( val apkSize: Long, val releaseNotes: String, val releasePageUrl: String, + val fallbackUrl: String? = null, // 主 URL 下载失败时尝试的备选链接 ) /** @@ -71,9 +72,12 @@ class UpdateChecker(private val context: Context) { // 先查 GitHub(公开默认源),从其 release notes 动态发现额外源(如自建 Gitea),取最高版本 // 同版本时 Gitea 优先(国内可达,GitHub 可能被墙导致下载失败) val infos = mutableListOf() + var ghInfo: UpdateInfo? = null + var giteaInfo: UpdateInfo? = null val gh = runCatching { fetchRelease(GITHUB_API) }.getOrNull() if (gh != null) { - val giteaInfo = parseUpdateSource(gh.releaseNotes)?.let { runCatching { fetchRelease(it) }.getOrNull() } + ghInfo = gh + giteaInfo = parseUpdateSource(gh.releaseNotes)?.let { runCatching { fetchRelease(it) }.getOrNull() } // Gitea 放前面,同版本时优先用它下载(国内可达) if (giteaInfo != null) infos.add(giteaInfo) infos.add(gh) @@ -92,7 +96,9 @@ class UpdateChecker(private val context: Context) { updateInfo.value = null lastNotifiedVersion = null } else { - updateInfo.value = best + // 主 URL 下载失败时尝试另一个源的链接 + val fallback = if (best == giteaInfo) ghInfo?.apkUrl else giteaInfo?.apkUrl + updateInfo.value = best.copy(fallbackUrl = fallback) } lastError.value = null @@ -179,14 +185,29 @@ class UpdateChecker(private val context: Context) { downloading.value = true downloadProgress.value = 0 lastError.value = null - try { - val req = Request.Builder().url(info.apkUrl).get().build() + // 主 URL 失败时自动尝试备选 URL + val urls = listOf(info.apkUrl) + (info.fallbackUrl?.let { listOf(it) } ?: emptyList()) + for ((idx, url) in urls.withIndex()) { + if (idx > 0) downloadProgress.value = 0 // 重试时重置进度 + val file = tryDownload(url, info.latestVersion) + if (file != null) { + downloading.value = false + return@withContext file + } + } + downloading.value = false + null + } + + private suspend fun tryDownload(url: String, version: String): File? { + return try { + val req = Request.Builder().url(url).get().build() http.newCall(req).execute().use { res -> - if (!res.isSuccessful) { lastError.value = "HTTP ${res.code}"; return@use null } - val body = res.body ?: run { lastError.value = "空响应"; return@use null } + if (!res.isSuccessful) { lastError.value = "HTTP ${res.code}"; return@tryDownload null } + val body = res.body ?: run { lastError.value = "空响应"; return@tryDownload null } val total = body.contentLength() val dir = context.getExternalFilesDir(null) ?: context.cacheDir - val target = File(dir, "gotmsg-${info.latestVersion}.apk") + val target = File(dir, "gotmsg-${version}.apk") target.outputStream().use { sink -> val input = body.byteStream() val buf = ByteArray(8 * 1024) @@ -205,8 +226,6 @@ class UpdateChecker(private val context: Context) { } catch (e: Exception) { lastError.value = e.message ?: "下载失败" null - } finally { - downloading.value = false } }