Gỡ lỗi lỗi giải quyết phần phụ thuộc

Khi thêm một phần phụ thuộc, bạn có thể gặp phải vấn đề về các phần phụ thuộc mà phần phụ thuộc ban đầu yêu cầu, cũng như xung đột giữa các phiên bản phần phụ thuộc. Dưới đây là cách phân tích biểu đồ phần phụ thuộc và khắc phục các vấn đề thường gặp phát sinh.

Để xem hướng dẫn về cách khắc phục các lỗi giải quyết phần phụ thuộc liên quan đến logic bản dựng tuỳ chỉnh, hãy xem bài viết Chiến lược giải quyết phần phụ thuộc tuỳ chỉnh.

Hiển thị các phần phụ thuộc của mô-đun

Một số phần phụ thuộc trực tiếp có thể có các phần phụ thuộc riêng. Những phần phụ thuộc riêng đó được gọi là phần phụ thuộc bắc cầu. Thay vì yêu cầu bạn khai báo thủ công từng phần phụ thuộc bắc cầu, Gradle sẽ tự động thu thập và bổ sung các khai báo này cho bạn. Trình bổ trợ Android cho Gradle sẽ cung cấp một tác vụ hiển thị danh sách các phần phụ thuộc mà Gradle sẽ phân giải cho một mô-đun nhất định.

Đối với mỗi mô-đun, báo cáo này cũng sẽ nhóm các phần phụ thuộc dựa trên biến thể bản dựng, nhóm tài nguyên kiểm thử và đường dẫn lớp. Sau đây là báo cáo mẫu về đường dẫn lớp thời gian chạy của một mô-đun ứng dụng được nhóm dựa trên biến thể bản dựng gỡ lỗi và đường dẫn lớp biên dịch của nhóm tài nguyên kiểm thử đo lường.

debugRuntimeClasspath - Dependencies for runtime/packaging
+--- :mylibrary (variant: debug)
+--- com.google.android.material:material:1.0.0@aar
+--- androidx.appcompat:appcompat:1.0.2@aar
+--- androidx.constraintlayout:constraintlayout:1.1.3@aar
+--- androidx.fragment:fragment:1.0.0@aar
+--- androidx.vectordrawable:vectordrawable-animated:1.0.0@aar
+--- androidx.recyclerview:recyclerview:1.0.0@aar
+--- androidx.legacy:legacy-support-core-ui:1.0.0@aar
...

debugAndroidTest
debugAndroidTestCompileClasspath - Dependencies for compilation
+--- androidx.test.ext:junit:1.1.0@aar
+--- androidx.test.espresso:espresso-core:3.1.1@aar
+--- androidx.test:runner:1.1.1@aar
+--- junit:junit:4.12@jar
...

Để chạy tác vụ này, hãy tiến hành như sau:

  1. Chọn View (Hiển thị) > Tool Windows (Cửa sổ công cụ) > Gradle (hoặc nhấp vào Gradle trong thanh cửa sổ công cụ).
  2. Mở rộng phần AppName > Tasks (Nhiệm vụ) > android rồi nhấp đúp vào androidDependencies. Sau khi Gradle thực thi nhiệm vụ, cửa sổ Run (Chạy) sẽ mở ra để hiển thị dữ liệu đầu ra.

Để biết thêm thông tin về cách quản lý các phần phụ thuộc trong Gradle, hãy xem bài viết Kiến thức cơ bản về việc quản lý phần phụ thuộc trong Hướng dẫn sử dụng Gradle.

Loại trừ các phần phụ thuộc bắc cầu

Khi phát triển trong phạm vi nhất định, một ứng dụng có thể chứa một số phần phụ thuộc, bao gồm cả các phần phụ thuộc trực tiếp và phần phụ thuộc bắc cầu (các thư viện mà thư viện đã nhập của ứng dụng phụ thuộc vào). Để loại trừ các phần phụ thuộc bắc cầu không còn cần thiết nữa, bạn có thể sử dụng từ khoá exclude như minh hoạ bên dưới:

Kotlin

dependencies {
    implementation("some-library") {
        exclude(group = "com.example.imgtools", module = "native")
    }
}

Groovy

dependencies {
    implementation('some-library') {
        exclude group: 'com.example.imgtools', module: 'native'
    }
}

Loại trừ các phần phụ thuộc bắc cầu khỏi cấu hình kiểm thử

Nếu bạn cần loại trừ một số phần phụ thuộc bắc cầu nhất định khỏi các quy trình kiểm thử thì mã mẫu nêu trên có thể không hoạt động như mong đợi. Đó là do cấu hình kiểm thử (ví dụ: androidTestImplementation) mở rộng cấu hình implementation của mô-đun. Nghĩa là khi Gradle giải quyết, thì cấu hình kiểm thử này luôn chứa các phần phụ thuộc implementation.

Vì vậy, bạn phải loại trừ các phần phụ thuộc bắc cầu khỏi các quy trình kiểm thử tại thời điểm thực thi như đoạn mã bên dưới:

Kotlin

android.testVariants.all {
    compileConfiguration.exclude(group = "com.jakewharton.threetenabp", module = "threetenabp")
    runtimeConfiguration.exclude(group = "com.jakewharton.threetenabp", module = "threetenabp")
}

Groovy

android.testVariants.all { variant ->
    variant.getCompileConfiguration().exclude group: 'com.jakewharton.threetenabp', module: 'threetenabp'
    variant.getRuntimeConfiguration().exclude group: 'com.jakewharton.threetenabp', module: 'threetenabp'
}

Lưu ý: Bạn vẫn có thể sử dụng từ khoá exclude trong khối phần phụ thuộc như trong mã mẫu nguyên gốc ở phần Loại trừ các phần phụ thuộc bắc cầu để bỏ qua các phần phụ thuộc bắc cầu dành riêng cho cấu hình kiểm thử và không được đưa vào các cấu hình khác.

Sửa lỗi phân giải phần phụ thuộc

Khi bạn thêm nhiều phần phụ thuộc vào dự án ứng dụng, những phần phụ thuộc trực tiếp và bắc cầu đó có thể xung đột với nhau. Trình bổ trợ Android cho Gradle sẽ cố gắng giải quyết những xung đột này một cách linh hoạt, nhưng một số xung đột có thể dẫn đến lỗi biên dịch hoặc lỗi thời gian chạy.

Để kiểm tra xem những phần phụ thuộc đang gây ra lỗi, hãy kiểm tra cây phần phụ thuộc của ứng dụng và tìm các phần phụ thuộc xuất hiện nhiều lần hoặc có các phiên bản xung đột.

Nếu không thể dễ dàng xác định được các phần phụ thuộc trùng lặp, hãy thử sử dụng giao diện người dùng của Android Studio để tìm các phần phụ thuộc có chứa lớp trùng lặp đó như sau:

  1. Chọn Navigate > Class (Di chuyển > Lớp) từ thanh trình đơn.
  2. Trong hộp thoại tìm kiếm vừa bật lên, hãy nhớ đánh dấu vào hộp Include non-project items (Bao gồm các mục không thuộc dự án).
  3. Nhập tên của lớp xuất hiện trong lỗi bản dựng.
  4. Kiểm tra kết quả của các phần phụ thuộc có chứa lớp đó.

Các phần sau đây mô tả các loại lỗi phân giải phần phụ thuộc mà bạn có thể gặp phải và cách khắc phục.

Sửa lỗi trùng lặp lớp

Nếu một lớp xuất hiện nhiều lần trên đường dẫn lớp thời gian chạy, một lỗi tương tự như sau sẽ xảy ra:

Program type already present com.example.MyClass

Lỗi này thường xảy ra do một trong các trường hợp sau:

  • Một phần phụ thuộc nhị phân có chứa một thư viện mà ứng dụng của bạn cũng đưa vào dưới dạng một phần phụ thuộc trực tiếp. Chẳng hạn, ứng dụng của bạn khai báo một phần phụ thuộc trực tiếp trong Thư viện A và Thư viện B, nhưng Thư viện A đã đưa Thư viện B vào tệp nhị phân của mình.
    • Để giải quyết vấn đề này, hãy xoá Thư viện B ở dạng phần phụ thuộc trực tiếp.
  • Ứng dụng của bạn có phần phụ thuộc tệp nhị phân cục bộ và phần phụ thuộc tệp nhị phân từ xa trên cùng một thư viện.
    • Để giải quyết vấn đề này, hãy xoá một trong các phần phụ thuộc nhị phân.

Khắc phục xung đột giữa các đường dẫn lớp

Khi Gradle phân giải đường dẫn lớp biên dịch, trước tiên sẽ phân giải đường dẫn lớp thời gian chạy và sử dụng kết quả đó để xác định phiên bản của các phần phụ thuộc sẽ được thêm vào đường dẫn lớp biên dịch. Nói cách khác, đường dẫn lớp thời gian chạy xác định số phiên bản bắt buộc đối với các phần phụ thuộc giống nhau trên đường dẫn hạ nguồn.

Đường dẫn lớp thời gian chạy của ứng dụng cũng xác định số phiên bản mà Gradle yêu cầu để so khớp với các phần phụ thuộc trong đường dẫn lớp thời gian chạy dành cho APK kiểm thử của ứng dụng. Hình 1 mô tả sơ đồ hệ phân cấp các đường dẫn lớp.

Hình 1. Số phiên bản của các phần phụ thuộc xuất hiện trên nhiều đường dẫn lớp phải khớp với hệ phân cấp này.

Xung đột khi các phiên bản khác nhau của cùng một phần phụ thuộc xuất hiện trên nhiều classpath có thể xảy ra khi ứng dụng của bạn có một phiên bản của một phần phụ thuộc sử dụng cấu hình phần phụ thuộc implementation và một mô-đun thư viện sử dụng một phiên bản khác của phần phụ thuộc đó bằng cấu hình runtimeOnly.

Khi phân giải các phần phụ thuộc trên đường dẫn lớp thời gian chạy và đường dẫn lớp biên dịch, Trình bổ trợ Android cho Gradle phiên bản 3.3.0 trở lên sẽ cố gắng tự động khắc phục một số xung đột phiên bản nhất định. Chẳng hạn, nếu classpath thời gian chạy bao gồm Thư viện A phiên bản 2.0 và classpath biên dịch bao gồm Thư viện A phiên bản 1.0, thì trình bổ trợ sẽ tự động cập nhật phần phụ thuộc trên classpath biên dịch thành Thư viện A phiên bản 2.0 để tránh lỗi.

Tuy nhiên, nếu classpath thời gian chạy bao gồm Thư viện A phiên bản 1.0 và classpath biên dịch bao gồm Thư viện A phiên bản 2.0, thì trình bổ trợ sẽ không hạ cấp phần phụ thuộc trên classpath biên dịch xuống thành Thư viện A phiên bản 1.0 và bạn vẫn sẽ nhận được một lỗi như sau:

Conflict with dependency 'com.example.library:some-lib:2.0' in project 'my-library'.
Resolved versions for runtime classpath (1.0) and compile classpath (2.0) differ.

Để giải quyết vấn đề này, hãy làm theo một trong những cách sau:

  • Thêm phiên bản phần phụ thuộc mong muốn dưới dạng phần phụ thuộc api vào mô-đun thư viện. Nghĩa là, chỉ có mô-đun thư viện khai báo phần phụ thuộc này, nhưng mô-đun ứng dụng cũng sẽ có quyền truy cập vào API của phần phụ thuộc đó một cách bắc cầu.
  • Ngoài ra, bạn có thể khai báo một phần phụ thuộc nhất định trong cả hai mô-đun, nhưng cũng nên đảm bảo rằng mỗi mô-đun sử dụng cùng một phiên bản của phần phụ thuộc đó. Cân nhắc việc định cấu hình các thuộc tính trên toàn dự án để đảm bảo rằng các phiên bản của từng phần phụ thuộc vẫn nhất quán xuyên suốt dự án của bạn.