news 2026/4/18 5:31:31

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/3/15 15:32:21

基于知识库智能问答客服的AI辅助开发实战:从架构设计到生产环境部署

基于知识库智能问答客服的AI辅助开发实战&#xff1a;从架构设计到生产环境部署 ---- 摘要&#xff1a;本文针对开发者在构建智能问答客服系统时面临的知识库管理复杂、响应速度慢、意图识别不准等痛点&#xff0c;提出一套基于RAG架构的AI辅助开发方案。通过对比传统规则引擎与…

作者头像 李华
网站建设 2026/4/18 3:29:04

从零到一:ESP32 I2S音频系统的硬件选型与实战避坑指南

从零到一&#xff1a;ESP32 I2S音频系统的硬件选型与实战避坑指南 1. 音频系统架构设计基础 在ESP32项目中构建音频系统时&#xff0c;选择合适的硬件组件和配置方案至关重要。I2S&#xff08;Inter-IC Sound&#xff09;总线作为数字音频传输的标准协议&#xff0c;能够提供…

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

基于eNSP的校园网络毕业设计实战:集成防火墙与安全策略部署

基于eNSP的校园网络毕业设计实战&#xff1a;集成防火墙与安全策略部署 一、为什么“有交换机就能毕业”不再够用 做校园网毕设&#xff0c;最容易踩的坑就是“拓扑一画&#xff0c;交换机一摆&#xff0c;VLAN一分&#xff0c;收工”。老师一问“外网怎么进来&#xff1f;”…

作者头像 李华