XuqmGroup-AndroidSDK/sdk-update/src/main/java/com/xuqm/sdk/update/UpdateSDK.kt
XuqmGroup 784554ab4c refactor(sdk-update): 重构版本更新检测功能
- 移除 userIdOverride 参数,统一通过 XuqmSDK.login() 会话获取用户ID
- 新增 bypassIgnore 参数控制是否绕过已忽略版本
- 静默检查模式下已忽略版本不再弹窗,主动检查模式始终显示更新对话框
- 更新文档说明破坏性变更和新的使用方式
- 移除 requiresLogin 字段相关实现
2026-06-04 13:35:59 +08:00

113 行
4.6 KiB
Kotlin

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

package com.xuqm.sdk.update
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.content.FileProvider
import com.xuqm.sdk.XuqmSDK
import com.xuqm.sdk.file.FileSDK
import com.xuqm.sdk.core.ServiceEndpointRegistry
import com.xuqm.sdk.network.ApiClient
import com.xuqm.sdk.update.api.UpdateApi
import com.xuqm.sdk.update.model.UpdateInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
object UpdateSDK {
private val api: UpdateApi get() = ApiClient.create(UpdateApi::class.java, ServiceEndpointRegistry.updateBaseUrl)
private fun normalizeDownloadUrl(rawUrl: String?): String? {
if (rawUrl.isNullOrBlank()) return rawUrl
return runCatching {
val uri = Uri.parse(rawUrl)
if (uri.path?.startsWith("/files/apk/") == true) {
"${uri.scheme}://${uri.authority}/api/v1/updates${uri.path}"
} else {
rawUrl
}
}.getOrDefault(rawUrl)
}
private fun prefs(context: Context) =
context.applicationContext.getSharedPreferences("xuqm_update_prefs", android.content.Context.MODE_PRIVATE)
/** 忽略指定版本,下次检测到该版本时不再提示(强制更新版本不受此影响) */
fun ignoreVersion(context: Context, versionCode: Int) {
prefs(context).edit().putBoolean("ignored_v$versionCode", true).apply()
}
/** 清除所有已忽略版本记录 */
fun clearIgnoredVersions(context: Context) {
prefs(context).edit().clear().apply()
}
private fun isVersionIgnored(context: Context, versionCode: Int): Boolean =
prefs(context).getBoolean("ignored_v$versionCode", false)
/**
* 检测应用更新。
*
* @param bypassIgnore 是否绕过"忽略版本"过滤。
* - `false`(默认,静默检查):用户已忽略的版本不再弹窗,适合启动时后台检查。
* - `true`(主动检查):忽略记录不生效,始终弹出更新对话框;无更新时由调用方显示提示。
*
* userId 通过 [XuqmSDK.login] 设置会话后自动传递,无需外部覆盖。
*/
suspend fun checkAppUpdate(context: Context, bypassIgnore: Boolean = false): UpdateInfo? = withContext(Dispatchers.IO) {
awaitInitialization()
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode.toInt()
} else {
@Suppress("DEPRECATION")
packageInfo.versionCode
}
val userId = XuqmSDK.currentLoginSession?.userId
runCatching {
api.checkUpdate(XuqmSDK.appKey, "ANDROID", versionCode, userId).data?.let { info ->
val normalized = info.copy(downloadUrl = normalizeDownloadUrl(info.downloadUrl) ?: info.downloadUrl)
// 静默检查时跳过已忽略版本;主动检查bypassIgnore=true始终展示
if (!bypassIgnore && normalized.needsUpdate && !normalized.forceUpdate
&& isVersionIgnored(context, normalized.versionCode)
) {
normalized.copy(needsUpdate = false)
} else {
normalized
}
}
}.getOrNull()
}
private suspend fun awaitInitialization() {
if (XuqmSDK.isInitialized()) return
kotlinx.coroutines.withTimeoutOrNull(15_000L) {
while (!XuqmSDK.isInitialized()) {
kotlinx.coroutines.delay(100)
}
} ?: throw IllegalStateException("XuqmSDK not initialized. Check that config.xuqm is present and valid.")
}
suspend fun downloadAndInstall(
context: Context,
downloadUrl: String,
onProgress: (Int) -> Unit = {},
) = withContext(Dispatchers.IO) {
val apkFile = File(context.getExternalFilesDir(null), "update.apk")
FileSDK.downloadToFile(downloadUrl, apkFile, onProgress)
withContext(Dispatchers.Main) { installApk(context, apkFile) }
}
private fun installApk(context: Context, apkFile: File) {
val intent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", apkFile)
setDataAndType(uri, "application/vnd.android.package-archive")
}
context.startActivity(intent)
}
}