news 2026/4/23 10:46:27

Moshi 重复类加载问题深度解析:从 ‘duplicate entry: com/squareup/moshi/recordjsonadapter$1.class‘ 错误到解决方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Moshi 重复类加载问题深度解析:从 ‘duplicate entry: com/squareup/moshi/recordjsonadapter$1.class‘ 错误到解决方案


1. 错误背景:当 Moshi 撞上“双胞胎”

第一次把项目跑真机时,Gradle 突然甩出一句
cause: duplicate entry: com/squareup/moshi/recordjsonadapter$1.class
打包流程直接中断。字面意思很直白:同一个类被重复写进了 APK。
Moshi 从 1.14 开始给 JDK 16+ 的 Record 做了适配,生成RecordJsonAdapter及其匿名内部类。如果两条依赖路径各自拉进了不同版本(或相同版本但被不同构建缓存),JAR 里就会出现两份.class,DX/D8 在合并时就会炸锅。
典型触发场景:

  • 主工程显式依赖moshi:1.15.0,某个二方库内部又依赖moshi:1.12.0
  • 混用moshimoshi-kotlin,后者自带moshiruntime传递依赖
  • 多模块项目里,A 模块api引入,B 模块implementation引入,版本未对齐

根因一句话:依赖树里同一坐标不同版本并存,且都携带了 Record 适配器代码

2. 技术方案对比:三把手术刀怎么选

方案思想优点缺点适用场景
依赖排除(exclude)把多余的那份直接踢出依赖树配置简单,APK 瘦身需要人工找出冲突源;升级后可能再次引入快速止血,单点冲突
强制版本(force / strict)统一强制解析到指定版本一次配置全局生效;Gradle 自动仲裁若旧库不兼容新 API 会运行时崩溃团队能统一版本管理
Shading(重定位)把类名整体搬家,物理隔离彻底避免冲突;可共存多版本构建耗时增加;调试堆栈变长SDK 厂商、二方库无法改源码时

3. 实现细节:直接能抄的 Gradle 片段

以下均以 Kotlin DSL 为例,Groovy DSL 把括号换成空格即可。

3.1 依赖排除

// build.gradle.kts dependencies { implementation("com.squareup.moshi:moshi:1.15.0") implementation("com.xxx:some-lib:3.2.1") { exclude(group = "com.squareup.moshi", module = "moshi") } }

验证命令:

./gradlew app:dependencies --configuration releaseRuntimeClasspath | grep moshi

确保只剩一条1.15.0

3.2 强制版本

// build.gradle.kts dependencyResolutionManagement { versionCatalogs { create("libs") { version("moshi", "1.15.0") library("moshi", "com.squareup.moshi", "moshi").versionRef("moshi") } } } configurations.all { resolutionStrategy.eachDependency { if (requested.group == "com.squareup.moshi" && requested.name == "moshi") { useVersion(libs.versions.moshi.get()) because("Align moshi to avoid duplicate RecordJsonAdapter") } } }

3.3 Shading(以 Shadow 插件为例)

plugins { id("com.github.johnrengelman.shadow") version "8.1.1" } shadowJar { archiveClassifier.set("shaded") relocate("com.squareup.moshi", "shaded.moshi") // 把 moshi 本身也打进去 from(project.sourceSets.main.get().output) configurations = listOf(project.configurations.runtimeClasspath.get()) } // 发布到本地仓库供其他模块依赖 publishing { publications { create<MavenPublication>("shadow") { artifact(shadowJar) artifactId = "my-moshi-runtime" } } }

主工程里直接依赖my-moshi-runtime即可,与业务代码零感知。

4. 性能考量:构建与运行双面看

  • 构建时间
    exclude/force 只做依赖解析,增量构建几乎无额外耗时;shade 需要重写字节码,全量打包增加 15~30 s(视 CPU 而定)。
  • APK 大小
    exclude/force 仅保留一份,体积最小;shade 会多拷贝一份 relocated 类,增加 300-400 KB。
  • 运行时
    前两种方案对启动速度无影响;shade 因类名变长,首次加载反射略慢(<5 ms),可忽略。
  • 维护成本
    shade 需要单独发布阴影包,CI 流程复杂;exclude/force 只需代码审查阶段保证版本一致即可。

5. 避坑指南:90% 的人都踩过的坑

  1. 只在debug排除,release忘记排除,结果发到线上才崩溃——用configurations.all统一处理。
  2. 用了force但二方库硬编码调用旧 API,运行时NoSuchMethodError——先用./gradlew dependencyInsight确认兼容。
  3. Shading 时把moshi-adapters也 relocate 了,导致 Kotlin 扩展找不到类——只 relocatemoshi核心库即可。
  4. 混用moshi-kotlin-codegenkapt,注解处理器生成的JsonAdapter与反射冲突——保持同一版本且只选一种代码生成方式。
  5. 升级 Android Gradle Plugin 后缓存未清,依旧报 duplicate——./gradlew clean并删除.gradle/caches/transforms-3

6. 进阶建议:把冲突扼杀在摇篮里

  • buildSrc写一份版本清单,所有模块统一引用,禁止硬编码数字。
  • CI 里加一条任务:解析依赖树并输出到 PR 评论,方便 Code Review 一眼看到新增库。
  • 使用 GradlefailOnVersionConflict()在本地提前失败,强制开发者显式解决。
  • 对二方 SDK 要求提供exclude-moshi的 pom 配置,或让厂商直接 shade。
  • 定期跑./gradlew dependencyUpdates,把整条网络保持最新,减少“旧+新”组合概率。

7. 小结与思考

依赖冲突不是编译器故意找茬,而是模块化生态的副作用。
Moshi 的RecordJsonAdapter只是冰山一角,今天遇到 duplicate,明天可能是 Okio、Kotlin Stdlib。
与其每次救火,不如把“版本仲裁策略”写进团队规范:统一入口、自动检测、渐进升级。
当你能一眼从依赖树里揪出那只“双胞胎”,就已经走在高质量交付的路上了。


写完这篇排查笔记,我又顺手把团队里的语音对话 Demo 升级了依赖——没错,就是那个用火山引擎豆包实时语音模型的小玩具。
如果你想亲手搭一个会“听、想、说”的 AI 伙伴,又不想被依赖冲突绊住脚,可以戳这个动手实验:从0打造个人豆包实时通话AI。
我按文档跑了一遍,半小时就能在浏览器里跟虚拟角色聊起来,顺便还能把今天学到的 Moshi 排坑技巧用上,一举两得。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 21:34:48

LabVIEW实现鼠标悬停波形曲线显示坐标官 网附件有源码

LabVIEW实现鼠标悬停波形曲线显示坐标官网附件有源码 在 LabVIEW 的波形图&#xff08;Waveform Graph&#xff09;中&#xff0c;实现 “鼠标悬停在波形曲线上时&#xff0c;自动显示对应点的 X/Y 坐标”。 步骤 创建事件结构 在程序框图中添加 “事件结构”&#xff0c;选择…

作者头像 李华
网站建设 2026/4/18 10:35:20

ChatGPT Edge 实战:AI 辅助开发的架构设计与性能优化

背景与痛点&#xff1a;AI 辅助开发的三座大山 过去一年&#xff0c;我们团队把“AI 结对编程”做成了一条流水线&#xff1a;需求 → 生成代码 → 单元测试 → 合并。跑通之后&#xff0c;大家却开始吐槽&#xff1a; 延迟抖动&#xff1a;云端 GPT-4 平均 800 ms&#xff0…

作者头像 李华
网站建设 2026/4/23 9:52:04

洛谷P1009_大整数类

P1009 [NOIP 1998 普及组] 阶乘之和 题目描述 用高精度计算出 S1!2!3!⋯n!S 1! 2! 3! \cdots n!S1!2!3!⋯n!&#xff08;n≤50n \le 50n≤50&#xff09;。 其中 ! 表示阶乘&#xff0c;定义为 n!n(n−1)(n−2)⋯1n!n\times (n-1)\times (n-2)\times \cdots \times 1n!n(n…

作者头像 李华
网站建设 2026/4/18 19:22:55

【27日 Docker 日志攻坚计划】:零信任架构下的审计级日志采集、脱敏、归档与合规留存(GDPR/等保2.0双认证)

第一章&#xff1a;Docker 27 日志集中管理方案全景概览在现代容器化生产环境中&#xff0c;Docker 27&#xff08;即 Docker Engine v27.x&#xff09;引入了更精细化的日志驱动扩展机制与原生可观测性集成能力。日志集中管理不再仅是“收集转发”&#xff0c;而是涵盖采集、过…

作者头像 李华