diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 394a1eb..2266b6c 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 = 15 - versionName = "1.7.2" + versionCode = 16 + versionName = "1.7.3" } 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 7f1c891..d0c8083 100644 --- a/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt +++ b/app/src/main/java/com/a2i/forwarder/core/UpdateChecker.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.OkHttpClient @@ -49,51 +50,71 @@ class UpdateChecker(private val context: Context) { fun start() { scope.launch { delay(5000) // 启动 5 秒后首检,给网络/应用初始化留时间 - check(force = false) + doCheck(throttle = false, toggle = true) // 启动必查(无视节流,但尊重开关) + while (isActive) { + delay(CHECK_INTERVAL_MS) // 常驻期间每 24h 巡检一次 + doCheck(throttle = true, toggle = true) + } } } - suspend fun check(force: Boolean): Result = withContext(Dispatchers.IO) { + /** 手动检查:无视节流和开关(用户主动)。 */ + suspend fun check(force: Boolean): Result = doCheck(throttle = !force, toggle = !force) + + private suspend fun doCheck(throttle: Boolean, toggle: Boolean): Result = withContext(Dispatchers.IO) { runCatching { val settings = app.settings + if (toggle && !settings.updateCheckEnabled.value) return@runCatching updateInfo.value val now = System.currentTimeMillis() - // 自动检查跳过条件(仅 force=false 时有效) - if (!force) { - if (!settings.updateCheckEnabled.value) return@runCatching updateInfo.value - if (now - settings.lastUpdateCheckTime.value < CHECK_INTERVAL_MS) return@runCatching updateInfo.value - } - val req = Request.Builder() - .url(RELEASES_API) - .header("Accept", "application/json") - .header("User-Agent", "a2i-android") - .get() - .build() - val info = http.newCall(req).execute().use { res -> - if (!res.isSuccessful) error("HTTP ${res.code}") - val body = res.body?.string() ?: error("空响应") - parseRelease(body) + if (throttle && now - settings.lastUpdateCheckTime.value < CHECK_INTERVAL_MS) return@runCatching updateInfo.value + + // 双源查询(Gitea 国内优先 + GitHub 备用),任一不通跳过,取最高版本 + val infos = RELEASES_APIS.mapNotNull { api -> + runCatching { fetchRelease(api) }.getOrNull() } settings.setLastUpdateCheck(now) + if (infos.isEmpty()) { + lastError.value = "所有更新源不可达" + return@runCatching updateInfo.value + } val current = currentVersionName() - val available = info != null && - isNewer(info.latestVersion, current) && - info.latestVersion != settings.ignoredVersion.value + val best = infos + .filter { isNewer(it.latestVersion, current) && it.latestVersion != settings.ignoredVersion.value } + .maxByOrNull { versionKey(it.latestVersion) } val prev = updateInfo.value - updateInfo.value = if (available) info else { - lastNotifiedVersion = null // 清除记录,等下一个新版 - null + if (best == null) { + updateInfo.value = null + lastNotifiedVersion = null + } else { + updateInfo.value = best } lastError.value = null // 发现新版且之前没推过通知 → 自动推送到所有已开启通道 - if (available && info != null && info.latestVersion != lastNotifiedVersion) { - lastNotifiedVersion = info.latestVersion - notifyNewVersion(prev, info) + if (best != null && best.latestVersion != lastNotifiedVersion) { + lastNotifiedVersion = best.latestVersion + notifyNewVersion(prev, best) } updateInfo.value }.onFailure { lastError.value = it.message ?: "检查失败" } } + private fun fetchRelease(api: String): UpdateInfo? { + val req = Request.Builder() + .url(api) + .header("Accept", "application/json") + .header("User-Agent", "a2i-android") + .get() + .build() + return http.newCall(req).execute().use { res -> + if (!res.isSuccessful) error("HTTP ${res.code}") + parseRelease(res.body?.string() ?: error("空响应")) + } + } + + private fun versionKey(v: String): String = + v.split(".").joinToString(".") { (it.substringBefore("-").toIntOrNull() ?: 0).toString().padStart(4, '0') } + /** 发现新版后,自动通过所有已开启的推送通道通知用户 */ private fun notifyNewVersion(prev: UpdateInfo?, info: UpdateInfo) { val s = app.settings @@ -225,7 +246,11 @@ class UpdateChecker(private val context: Context) { } companion object { - const val RELEASES_API = "https://UPDATE_SOURCE_HIDDEN/api/v1/repos/song/a2i/releases/latest" + // 双源:Gitea(国内优先)+ GitHub(备用),任一不通跳过,取最高版本 + val RELEASES_APIS = listOf( + "https://UPDATE_SOURCE_HIDDEN/api/v1/repos/song/a2i/releases/latest", + "https://api.github.com/repos/lsxf/a2i/releases/latest", + ) const val CHECK_INTERVAL_MS = 24L * 60 * 60 * 1000 }