feat(update): 添加更新检查功能增强和配置文件读取日志
- 新增 UpdateInfo.requiresLogin 字段支持登录后更新检测 - 添加 UpdateSDK.ignoreVersion 和 clearIgnoredVersions 方法实现版本忽略功能 - 扩展 checkAppUpdate 方法支持 userIdOverride 参数用于 H5 登录后重检 - 在 ConfigFileReader 中添加日志输出便于调试配置文件读取问题 - 优化 XuqmInitializerProvider 自动初始化错误处理和日志记录 - 实现非强制更新版本忽略机制,支持下次检测时不弹窗提示 - 添加 15 秒超时等待 SDK 初始化完成的异步处理逻辑
这个提交包含在:
父节点
1a6a91e6fe
当前提交
d12c575dd6
21
docs/sdk-update-CHANGELOG.md
普通文件
21
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
|
||||
|
||||
- 历史版本。
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -9,4 +9,6 @@ data class UpdateInfo(
|
||||
val forceUpdate: Boolean = false,
|
||||
val appStoreUrl: String = "",
|
||||
val marketUrl: String = "",
|
||||
/** 服务端要求先登录再检测更新时为 true,此时 needsUpdate 通常为 false */
|
||||
val requiresLogin: Boolean = false,
|
||||
)
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户