Bối cảnhGần đây tôi đã gặp bế tắc khi nâng cấp một dự án Android cũ để hỗ trợ phiên bản mới nhất của một SDK bên thứ ba phổ biến. Mọi thứ trông có vẻ ổn trong trình chỉnh sửa mã, nhưng ngay khi tôi bắt đầu quá trình build, Gradle đã thất bại với lỗi 'Duplicate class' rất khó chịu. Vấn đề cụ thể này bắt đầu xuất hiện thường xuyên sau khi Kotlin 1.8.0 được phát hành vì JetBrains đã quyết định hợp nhất các chức năng của kotlin-stdlib-jdk7 và kotlin-stdlib-jdk8 vào kotlin-stdlib chính.
Khi các phụ thuộc (dependencies) trong dự án của bạn là sự kết hợp giữa các thư viện cũ (trỏ đến các artifact riêng lẻ) và các thư viện mới hơn (trỏ đến artifact hợp nhất), Gradle sẽ thấy các lớp giống nhau ở hai nơi khác nhau và dừng quá trình build để ngăn chặn lỗi runtime.
Thông báo lỗiKết quả build thường trông như thế này:
Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found in modules kotlin-stdlib-jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21) and kotlin-stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.10)
Duplicate class kotlin.jvm.jdk8.JvmRepeatableKt found in modules kotlin-stdlib-jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21) and kotlin-stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.10)
...
Gỡ lỗi biểu đồ phụ thuộc (Dependency Graph)Trước khi bắt tay vào sửa lỗi, tôi cần xem thư viện nào vẫn đang kéo theo kotlin-stdlib-jdk8 cũ. Tôi đã sử dụng lệnh dependencies của Gradle trong terminal của Android Studio:
./gradlew :app:dependencies
Tôi đã tìm kiếm trong kết quả đầu ra (Ctrl+F) từ khóa "kotlin-stdlib-jdk8". Tôi phát hiện ra rằng một phiên bản cũ của thư viện networking đang kéo theo Kotlin 1.6.21 thông qua các phụ thuộc bắc cầu (transitive dependencies). Trong khi đó, dự án của tôi được thiết lập để sử dụng Kotlin 1.8.10. Vì 1.8.10 đã bao gồm các lớp đó, xung đột là điều không thể tránh khỏi.
Giải phápCó một vài cách để xử lý vấn đề này, nhưng phương pháp mạnh mẽ nhất mà tôi tìm thấy là ép buộc Gradle giải quyết tất cả các module Kotlin stdlib về cùng một phiên bản hoặc loại bỏ hoàn toàn các module dư thừa.
Tùy chọn 1: Sử dụng Ràng buộc phụ thuộc (Dependency Constraints) (Được khuyến nghị)Đây là cách tiếp cận sạch nhất. Nó báo cho Gradle biết rằng nếu thấy bất kỳ module cụ thể nào trong số này, nó phải sử dụng một phiên bản nhất định, ghi đè hiệu quả các phụ thuộc bắc cầu từ các thư viện cũ. Thêm đoạn mã này vào tệp app/build.gradle (hoặc build.gradle gốc nếu bạn có nhiều module):
dependencies {
constraints {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") {
because("kotlin-stdlib-jdk7 hiện là một phần của kotlin-stdlib")
}
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") {
because("kotlin-stdlib-jdk8 hiện là một phần của kotlin-stdlib")
}
}
}
Tùy chọn 2: Loại bỏ các Module trên toàn cụcNếu các ràng buộc không hoạt động vì lý do nào đó, bạn có thể buộc loại bỏ các module cũ khỏi toàn bộ cấu hình build. Thêm khối mã này vào tệp build.gradle cấp dự án của bạn:
subprojects {
project.configurations.all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'org.jetbrains.kotlin') {
if (details.requested.name == 'kotlin-stdlib-jdk7' || details.requested.name == 'kotlin-stdlib-jdk8') {
details.useTarget "org.jetbrains.kotlin:kotlin-stdlib:${details.requested.version}"
}
}
}
}
}
Tùy chọn 3: Cập nhật phiên bản KotlinĐôi khi, chỉ cần đảm bảo phiên bản Kotlin cấp dự án của bạn nhất quán trên tất cả các module là có thể khắc phục được vấn đề. Trong tệp build.gradle (cấp Dự án):
plugins {
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
}
Và hãy đảm bảo bạn không định nghĩa thủ công implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" trong tệp app/build.gradle. Chỉ cần sử dụng implementation "org.jetbrains.kotlin:kotlin-stdlib" hoặc để Kotlin Gradle plugin tự động xử lý.
Các bước xác minhSau khi áp dụng các thay đổi, tôi đã thực hiện các bước sau để đảm bảo lỗi đã được khắc phục triệt để:
- Clean Project: Vào
Build > Clean Project.- Invalidate Caches: VàoFile > Invalidate Caches... > Invalidate and Restart. Điều này đảm bảo không còn các artifact build cũ nào tồn tại.- Chạy Build: Chạy lệnh./gradlew assembleDebugtừ terminal.- Kiểm tra lại các phụ thuộc: Chạy lệnh./gradlew :app:dependencies | grep kotlin-stdlibđể xác nhận rằng chỉ có một phiên bản duy nhất đang được sử dụng.## Bài học rút raViệc chuyển đổi từ Kotlin 1.7 sang 1.8 đã thay đổi cách đóng gói thư viện chuẩn, đây là nguyên nhân gốc rễ của 90% các lỗi 'Duplicate class' này trong phát triển Android hiện đại. Bất cứ khi nào bạn thấy xung đột giữa artifactjdk7/jdk8vàstdlibcơ bản, đó gần như luôn là do sự không khớp về phiên bản. Sử dụngconstraintslà giải pháp ưu tiên của tôi hiện nay vì nó ghi lại lý do tại sao việc ghi đè lại tồn tại mà không can thiệp quá sâu vào cây phụ thuộc.

