From f784f3354c8334dbbaeab59d0b129da274e340bd Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Sat, 18 Jul 2026 08:28:33 +0800 Subject: [PATCH] fix(core): align okhttp for React Native hosts --- AGENTS.md | 2 ++ docs/IMPLEMENTATION_HANDOFF.md | 15 +++++++++- gradle/libs.versions.toml | 5 +++- sdk-core/build.gradle.kts | 30 +++++++++++++++++++ .../java/com/xuqm/sdk/network/ApiClient.kt | 4 ++- sdk-update/build.gradle.kts | 2 +- 6 files changed, 54 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4c5d5df..a24acda 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,6 +11,8 @@ - Android update capabilities exposed to RN must be production-safe: verified downloads, FileProvider installation, permission handling, progress, retry, cancellation, and deterministic error contracts. - Do not expand push scope for this iteration; the host retains Tencent Push. - Shared behavior must have one canonical implementation, with public API/test/demo/docs changes delivered together. +- Every capability and code category must have one canonical owner and one canonical implementation. Do not scatter the same responsibility across modules or layers, and do not keep copied variants, forwarding compatibility wrappers, duplicate helpers, models, validators, SDK facades, or state machines. Search before adding code; if ownership is wrong, move/refactor it and delete the superseded implementation in the same change. +- Removing obsolete, invalid, or duplicate files must also remove every newly empty package, source-set, resource, test, documentation, or module directory in the same change. Do not preserve empty structure with `.gitkeep`; ignored Gradle/Kotlin/build outputs are not source directories and must not be treated as project structure. ## Delivery constraints diff --git a/docs/IMPLEMENTATION_HANDOFF.md b/docs/IMPLEMENTATION_HANDOFF.md index 697be8d..c519637 100644 --- a/docs/IMPLEMENTATION_HANDOFF.md +++ b/docs/IMPLEMENTATION_HANDOFF.md @@ -1,6 +1,6 @@ # Android SDK 重构实施接管文档 -> 更新时间:2026-07-17 +> 更新时间:2026-07-18 > 分支:`main` > 发布约束:Maven 制品只允许通过 `https://jenkins.xuqinmin.com/` 发布。 @@ -21,9 +21,22 @@ - 增加 Java/RN 可直接使用的 `startDownloadAndInstall`、`UpdateInstallTask`、进度/结果回调和安装权限设置 Intent。 - `sdk-core`、`sdk-update`、`sample-app` 编译通过。 - `sdk-license` 模块、源码、初始化反射、Gradle/Jenkins 参数和架构文档引用已全部删除。 +- 本轮同时删除本地遗留的 `sdk-license` 构建目录及其空源码包,并收敛 update/push/im/sample 的空 package/resource 目录;源码树禁止保留空目录或 `.gitkeep` 占位。 +- 清理后 `:sdk-core:testDebugUnitTest :sdk-update:testDebugUnitTest` 重新执行并通过。 - `sdk-core` 1 个单元测试、`sdk-update` 2 个单元测试通过。 - 修复通知权限检查、MediaStore API 等级和 `java.time` minSdk 24 反向移植,core/update Release Lint 均通过。 +## 2026-07-18 RN 宿主网络兼容修复 + +- App4 真机链路发现 `sdk-core:1.1.6-SNAPSHOT` 将 OkHttp 提升到 5.3.2, + 与 React Native 0.86 的 `okhttp-urlconnection:4.9.2` 混装后会在首次请求时 + 因 `okhttp3.internal.Util` 缺失导致原生进程崩溃。 +- `sdk-core` 统一使用 OkHttp BOM 4.12.0;这是 RN 0.86 当前可用的最新 4.x + 稳定兼容线,不允许下游单独提升某个 OkHttp 组件到 5.x。 +- `check` 增加 `verifyNetworkDependencyAlignment`,发布前强制确认所有 + `com.squareup.okhttp3` 组件版本一致。 +- `sdk-update` 的 Gson 改为复用版本目录定义,删除模块内的重复版本事实来源。 + ## 下一步 1. 补充 Android 仪器测试。 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d369f4b..e352dd5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,9 @@ composeBom = "2026.03.00" coroutines = "1.10.2" datastore = "1.1.7" retrofit = "3.0.0" -okhttp = "5.3.2" +# React Native 0.86 的网络层仍依赖 okhttp-urlconnection 4.x。公共 SDK 必须 +# 使用同一二进制兼容线;5.x 会在运行时导致 okhttp3.internal.Util 缺失。 +okhttp = "4.12.0" gson = "2.13.2" jserialization = "1.9.0" webkit = "1.14.0" @@ -61,6 +63,7 @@ retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } +okhttp-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" } gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } diff --git a/sdk-core/build.gradle.kts b/sdk-core/build.gradle.kts index e08ab97..552836e 100644 --- a/sdk-core/build.gradle.kts +++ b/sdk-core/build.gradle.kts @@ -39,6 +39,10 @@ android { dependencies { api(platform(libs.androidx.compose.bom)) + // sdk-core 会进入原生、RN 与 Flutter 等不同宿主。通过 BOM 统一所有 + // okhttp 组件(含宿主已有的 okhttp-urlconnection),避免同一 APK + // 混装 4.x 扩展与 5.x 主包造成运行时链接崩溃。 + api(platform(libs.okhttp.bom)) api(libs.bundles.network) api(libs.kotlinx.coroutines.android) api(libs.kotlinx.serialization.json) @@ -49,3 +53,29 @@ dependencies { testImplementation(libs.junit4) coreLibraryDesugaring(libs.desugar.jdk.libs) } + +val verifyNetworkDependencyAlignment by tasks.registering { + group = "verification" + description = "校验 sdk-core 的 OkHttp 组件均处于同一条二进制兼容线。" + + doLast { + val expected = libs.versions.okhttp.get() + val resolved = configurations.getByName("releaseRuntimeClasspath") + .resolvedConfiguration + .resolvedArtifacts + .mapNotNull { artifact -> + val id = artifact.moduleVersion.id + if (id.group == "com.squareup.okhttp3") id else null + } + .distinctBy { "${it.group}:${it.name}" } + val incompatible = resolved.filter { it.version != expected } + check(incompatible.isEmpty()) { + "OkHttp 组件未对齐到 $expected:" + + incompatible.joinToString { "${it.group}:${it.name}:${it.version}" } + } + } +} + +tasks.named("check").configure { + dependsOn(verifyNetworkDependencyAlignment) +} diff --git a/sdk-core/src/main/java/com/xuqm/sdk/network/ApiClient.kt b/sdk-core/src/main/java/com/xuqm/sdk/network/ApiClient.kt index 3e8f1ce..99b1d90 100644 --- a/sdk-core/src/main/java/com/xuqm/sdk/network/ApiClient.kt +++ b/sdk-core/src/main/java/com/xuqm/sdk/network/ApiClient.kt @@ -210,7 +210,9 @@ object ApiClient { } private fun logResponseBody(response: Response) { - val body = response.body + // 204、HEAD 等合法响应可以没有响应体。日志拦截器不得为了读取调试信息 + // 改变请求结果,更不能在 OkHttp 调度线程触发空指针异常。 + val body = response.body ?: return val contentType = body.contentType()?.toString().orEmpty() if (!isTextual(contentType)) return val bodyText = runCatching { diff --git a/sdk-update/build.gradle.kts b/sdk-update/build.gradle.kts index fe22e00..0605bdd 100644 --- a/sdk-update/build.gradle.kts +++ b/sdk-update/build.gradle.kts @@ -33,6 +33,6 @@ android { dependencies { api(project(":sdk-core")) implementation(libs.kotlinx.coroutines.android) - implementation("com.google.code.gson:gson:2.10.1") + implementation(libs.gson) testImplementation(libs.junit4) }