From d12c575dd646867035899e2f3884e32632e8fc3e Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Thu, 4 Jun 2026 13:14:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(update):=20=E6=B7=BB=E5=8A=A0=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=A3=80=E6=9F=A5=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E5=92=8C=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 UpdateInfo.requiresLogin 字段支持登录后更新检测 - 添加 UpdateSDK.ignoreVersion 和 clearIgnoredVersions 方法实现版本忽略功能 - 扩展 checkAppUpdate 方法支持 userIdOverride 参数用于 H5 登录后重检 - 在 ConfigFileReader 中添加日志输出便于调试配置文件读取问题 - 优化 XuqmInitializerProvider 自动初始化错误处理和日志记录 - 实现非强制更新版本忽略机制,支持下次检测时不弹窗提示 - 添加 15 秒超时等待 SDK 初始化完成的异步处理逻辑 --- docs/sdk-update-CHANGELOG.md | 21 +++++++++ .../com/xuqm/sdk/internal/ConfigFileReader.kt | 11 ++++- .../sdk/internal/XuqmInitializerProvider.kt | 5 ++- .../java/com/xuqm/sdk/update/UpdateSDK.kt | 45 ++++++++++++++++--- .../com/xuqm/sdk/update/model/UpdateInfo.kt | 2 + 5 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 docs/sdk-update-CHANGELOG.md diff --git a/docs/sdk-update-CHANGELOG.md b/docs/sdk-update-CHANGELOG.md new file mode 100644 index 0000000..ebd96d9 --- /dev/null +++ b/docs/sdk-update-CHANGELOG.md @@ -0,0 +1,21 @@ +# sdk-update 更新日志 + +## 1.1.0 + +### 新增 + +- `UpdateInfo.requiresLogin: Boolean`:服务端要求先登录再检测更新时为 `true`,客户端可据此静默等待登录后重检。 +- `UpdateSDK.ignoreVersion(context, versionCode)`:将指定版本码标记为已忽略,存储于 `SharedPreferences`。非强制更新时,`checkAppUpdate` 检测到被忽略的版本会直接返回 `needsUpdate=false`,不弹窗。 +- `UpdateSDK.clearIgnoredVersions(context)`:清除所有已忽略版本记录。 +- `UpdateSDK.checkAppUpdate(context, userIdOverride)` 新增可选参数 `userIdOverride: String?`:优先级高于 `XuqmSDK.currentLoginSession?.userId`,供 H5 login 回调触发补充检测时使用。 + +### 兼容性 + +- 完全向后兼容 `1.0.x`,现有调用无需修改。 +- `updateInfo.requiresLogin` 默认值为 `false`,服务端未升级时行为不变。 + +--- + +## 1.0.9 + +- 历史版本。 \ No newline at end of file diff --git a/sdk-core/src/main/java/com/xuqm/sdk/internal/ConfigFileReader.kt b/sdk-core/src/main/java/com/xuqm/sdk/internal/ConfigFileReader.kt index 0ed76eb..b3d2fd6 100644 --- a/sdk-core/src/main/java/com/xuqm/sdk/internal/ConfigFileReader.kt +++ b/sdk-core/src/main/java/com/xuqm/sdk/internal/ConfigFileReader.kt @@ -1,6 +1,7 @@ package com.xuqm.sdk.internal import android.content.Context +import android.util.Log import com.google.gson.Gson /** @@ -10,19 +11,25 @@ import com.google.gson.Gson */ internal object ConfigFileReader { + private const val TAG = "XuqmSDK" private const val CONFIG_PATH = "xuqm/config.xuqm" private const val CONFIG_DIR = "xuqm" private val gson = Gson() fun read(context: Context): ConfigFile? { return try { - val path = findConfigPath(context) ?: return null + val path = findConfigPath(context) ?: run { + Log.w(TAG, "No config file found in assets/$CONFIG_DIR/") + return null + } + Log.d(TAG, "Reading config file: $path") context.assets.open(path).use { stream -> val encrypted = stream.bufferedReader().readText() val json = ConfigFileCrypto.decrypt(encrypted) gson.fromJson(json, ConfigFile::class.java) } - } catch (_: Exception) { + } catch (e: Exception) { + Log.e(TAG, "Failed to read/decrypt config file", e) null } } diff --git a/sdk-core/src/main/java/com/xuqm/sdk/internal/XuqmInitializerProvider.kt b/sdk-core/src/main/java/com/xuqm/sdk/internal/XuqmInitializerProvider.kt index 62e7d1e..819af2d 100644 --- a/sdk-core/src/main/java/com/xuqm/sdk/internal/XuqmInitializerProvider.kt +++ b/sdk-core/src/main/java/com/xuqm/sdk/internal/XuqmInitializerProvider.kt @@ -4,10 +4,11 @@ import android.content.ContentProvider import android.content.ContentValues import android.database.Cursor import android.net.Uri +import android.util.Log import com.xuqm.sdk.XuqmSDK /** - * Auto-initializes XuqmSDK at app startup when a license file is present. + * Auto-initializes XuqmSDK at app startup when a config file is present. * Registered in sdk-core AndroidManifest so any module (im/update/push/license) * triggers one-shot initialization without explicit init calls from the app. */ @@ -18,6 +19,8 @@ class XuqmInitializerProvider : ContentProvider() { if (!XuqmSDK.isInitialized()) { XuqmSDK.autoInitialize(ctx) } + }.onFailure { e -> + Log.w("XuqmSDK", "Auto-initialization skipped: ${e.message}") } return true } diff --git a/sdk-update/src/main/java/com/xuqm/sdk/update/UpdateSDK.kt b/sdk-update/src/main/java/com/xuqm/sdk/update/UpdateSDK.kt index 3cced93..40236b4 100644 --- a/sdk-update/src/main/java/com/xuqm/sdk/update/UpdateSDK.kt +++ b/sdk-update/src/main/java/com/xuqm/sdk/update/UpdateSDK.kt @@ -32,7 +32,29 @@ object UpdateSDK { }.getOrDefault(rawUrl) } - suspend fun checkAppUpdate(context: Context): UpdateInfo? = withContext(Dispatchers.IO) { + 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 userIdOverride 显式传入用户 ID,优先级高于 [XuqmSDK.currentLoginSession]。 + * 用于登录后补充检测的场景:当服务端返回 [UpdateInfo.requiresLogin] 时,H5 侧完成登录并传入 userId, + * 再调用此方法重新检测。 + */ + suspend fun checkAppUpdate(context: Context, userIdOverride: String? = null): UpdateInfo? = withContext(Dispatchers.IO) { awaitInitialization() val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { @@ -41,16 +63,27 @@ object UpdateSDK { @Suppress("DEPRECATION") packageInfo.versionCode } - val userId = XuqmSDK.currentLoginSession?.userId + val userId = userIdOverride ?: XuqmSDK.currentLoginSession?.userId runCatching { - api.checkUpdate(XuqmSDK.appKey, "ANDROID", versionCode, userId).data?.let { - it.copy(downloadUrl = normalizeDownloadUrl(it.downloadUrl) ?: it.downloadUrl) + api.checkUpdate(XuqmSDK.appKey, "ANDROID", versionCode, userId).data?.let { info -> + val normalized = info.copy(downloadUrl = normalizeDownloadUrl(info.downloadUrl) ?: info.downloadUrl) + // 非强制更新且已被用户忽略时,压制弹窗 + if (normalized.needsUpdate && !normalized.forceUpdate && isVersionIgnored(context, normalized.versionCode)) { + normalized.copy(needsUpdate = false) + } else { + normalized + } } }.getOrNull() } - private fun awaitInitialization() { - XuqmSDK.requireInit() + 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( diff --git a/sdk-update/src/main/java/com/xuqm/sdk/update/model/UpdateInfo.kt b/sdk-update/src/main/java/com/xuqm/sdk/update/model/UpdateInfo.kt index 72fb185..efa24e3 100644 --- a/sdk-update/src/main/java/com/xuqm/sdk/update/model/UpdateInfo.kt +++ b/sdk-update/src/main/java/com/xuqm/sdk/update/model/UpdateInfo.kt @@ -9,4 +9,6 @@ data class UpdateInfo( val forceUpdate: Boolean = false, val appStoreUrl: String = "", val marketUrl: String = "", + /** 服务端要求先登录再检测更新时为 true,此时 needsUpdate 通常为 false */ + val requiresLogin: Boolean = false, )