From ba8fc747403d9812b94f2d0336e6f7377cce679d Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Wed, 17 Jun 2026 11:32:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(sdk-update):=20=E4=BF=AE=E5=A4=8D=20checkAp?= =?UTF-8?q?pUpdate=20NPE=20=E2=80=94=20=E6=9C=8D=E5=8A=A1=E7=AB=AF=20versi?= =?UTF-8?q?onName=20=E4=B8=BA=20null=20=E6=97=B6=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gson 反序列化时会将 JSON null 注入 Kotlin non-null String 字段,绕过默认值。 引入内部 DTO UpdateInfoDto(所有字符串字段可空),转换为 UpdateInfo 时用 orEmpty() 补默认值。 Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/xuqm/sdk/update/UpdateSDK.kt | 2 +- .../java/com/xuqm/sdk/update/api/UpdateApi.kt | 31 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) 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 5d030e9..33d6153 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 @@ -184,7 +184,7 @@ object UpdateSDK { } runCatching { - api.checkUpdate(XuqmSDK.appKey, "ANDROID", versionCode, userId).data?.let { info -> + api.checkUpdate(XuqmSDK.appKey, "ANDROID", versionCode, userId).data?.toUpdateInfo()?.let { info -> val normalized = info.copy(downloadUrl = normalizeDownloadUrl(info.downloadUrl) ?: info.downloadUrl) val afterIgnore = if (!bypassIgnore && normalized.needsUpdate && !normalized.forceUpdate && isVersionIgnored(context, normalized.versionCode) diff --git a/sdk-update/src/main/java/com/xuqm/sdk/update/api/UpdateApi.kt b/sdk-update/src/main/java/com/xuqm/sdk/update/api/UpdateApi.kt index 857f4f7..140f698 100644 --- a/sdk-update/src/main/java/com/xuqm/sdk/update/api/UpdateApi.kt +++ b/sdk-update/src/main/java/com/xuqm/sdk/update/api/UpdateApi.kt @@ -6,12 +6,39 @@ import retrofit2.http.Query data class ApiResponse(val code: Int, val data: T?, val message: String) -interface UpdateApi { +/** 服务端响应的原始 DTO,所有字符串字段可为 null,防止 Gson 绕过 Kotlin 默认值引发 NPE。 */ +internal data class UpdateInfoDto( + val needsUpdate: Boolean = false, + val versionName: String? = null, + val versionCode: Int = 0, + val downloadUrl: String? = null, + val changeLog: String? = null, + val forceUpdate: Boolean = false, + val appStoreUrl: String? = null, + val marketUrl: String? = null, + val requiresLogin: Boolean = false, + val apkHash: String? = null, +) { + fun toUpdateInfo() = UpdateInfo( + needsUpdate = needsUpdate, + versionName = versionName.orEmpty(), + versionCode = versionCode, + downloadUrl = downloadUrl.orEmpty(), + changeLog = changeLog.orEmpty(), + forceUpdate = forceUpdate, + appStoreUrl = appStoreUrl.orEmpty(), + marketUrl = marketUrl.orEmpty(), + requiresLogin = requiresLogin, + apkHash = apkHash, + ) +} + +internal interface UpdateApi { @GET("api/v1/updates/app/check") suspend fun checkUpdate( @Query("appKey") appKey: String, @Query("platform") platform: String, @Query("currentVersionCode") currentVersionCode: Int, @Query("userId") userId: String? = null, - ): ApiResponse + ): ApiResponse }