This is the first day of my participation in the Gwen Challenge in November. Check out the details: the last Gwen Challenge in 2021

preface

In the last article, I mainly explained the Gradle core model and a preliminary understanding of Gradle plug-ins. Gradle dependency management Gradle dependency management Gradle dependency management

1. Dependency management

Dependency management: In most cases, projects rely on reusable functionality in the form of a lib, and many projects may be cut up into separate sub-projects to form modular systems. Dependency management is a technique that automates the definition, resolution, and use of dependencies for a project.

Gradle provides powerful dependency management support and implements typical scenarios for modern software projects, as shown in the following figure:

As is shown in

Gradle builds with local and remote resources. Gradle builds with local and remote resources. Gradle builds with local and remote resources.

2. Gradle Repositories

The place where you store modules in Gradle is called a repository:

  • Once the repository is defined, Gradle knows how to find and retrieve modules. There are two types of resource libraries: local and remote
  • At run time, Gradle needs to locate the declaration of dependencies if required by the corresponding task. Dependencies may be downloaded from a remote library, retrieved from a local library, or other projects in a multi-project build. This process is called dependency resolution
  • Once parsed, the parsing mechanism stores dependent underlying files as a local cache that can be reused by subsequent builds rather than downloaded to a remote library
  • Modules can also provide additional metadata that describes more detailed information about the module, such as coordinates in the repository, project information, and so on. (group, name, version)

Resources are downloaded according to the developer’s Gradle location dependency declaration. How do you add Gradle dependencies?

Gradle relies on repositories

Adding a dependency repository:

  • Configure allprojects using allprojects{} in the project build.gradle and add a repository of dependencies using repositories{}.
  • Google () : Add a repository to look up dependencies in Google’s Maven repository;
  • MavenCenteral () : Adds a repository to look up dependencies in Maven’s central repository;
  • Jcenter () adds a repository to look up dependencies in Bintray’s JCenter repository;
    • Notice jecnter is about to be shut down, switch to mavenCentral. If you have your own dependent libraries published in JCenter, you need to migrate to Mavan Central. Jcenter () is also changed to mavenCenteral() by default in higher versions of AS.
  • MavenLocal () : Adds a repository to look up dependencies in the local Maven cache;
  • Maven {} : specify the address of a Maven repository, using the URL (path) method to add;
  • Ivy {} : Specifies an Ivy repository address, which is added using the URL (path) method.

Having said that, post a code to see:

// Configure all projects
allprojects {
    // Add a dependency repository for the project
    repositories {
        google() // Google Maven repository
        mavenCentral() // Maven central repository
        //jcenter() // Warning: this repository is going to shut down soon
        mavenLocal() // The local Maven repository
// maven {
// //url "http://xxxx" // get library from XXX
/ /}
// ivy {
// //url "http://xxxx" // get library from XXX
/ /}}}Copy the code

I’m sure most readers will be too familiar with this code to go into detail, so let’s jump right into the next topic: Gradle dependency scopes

4. Gradle depends on ranges

  • In Gradle build scripts developers can define dependencies in different scopes, such as compiling source code or executing tests. The scope of dependencies in Gradle is called dependency configuration
  • The Gradle plugin builds dependency configuration in several ways:
    • Implementation, API, compileOnly, runtimeOnly, annotationProcessor, lintChecks, lintPublish…
    • Apk /compile/ Provided has been deprecated.
  • Add the dependency configuration in build.gradle using dependencies{}.

4.1 The effects of Gradle dependency modes

There are too many here, I will directly screenshots of the official: interested can go to see the official documentation

These are all official native dependency configurations, but you can also customize them:

4.2 Gradle Custom dependency Configuration Items

Under the app bulid. Gradle

. Slightly configurations {ABC {// The logic before and after the dependency can be implemented here
        println "abc"}}// Dependency configuration
dependencies {
    abc "Org. Jetbrains. Kotlin: kotlin - stdlib: 1.5.10". Slightly}Copy the code

Running effect

Starting Gradle Daemon...
Gradle Daemon started in 2 s 539 ms

> Configure project :app
abc

BUILD SUCCESSFUL in 34m 9s
Copy the code

This is very rarely used, very rarely used except in very special cases, so I’ll just go through it briefly.

To get straight to the point of this article, rely on passing:

Gradle relies on passing

Dependency transfer: it means that different dependency modes have different transfer relationships. So how do you view the corresponding dependencies?

5.1 Viewing Module Dependencies

To see the dependency dependencies for the entire project, use the command:

  • gradlew app:dependencies –configuration releaseRuntimeClasspath
  • X.X.X (*) The dependency already exists and will not be repeated
  • X.X.X -> X.X.X The dependent version is replaced by the version indicated by the arrow
  • X.X.X -> X.X.X (*) The dependency version is replaced by the version indicated by the arrow, and the dependency is not repeated

Now for the code verification:

dependencies {

    abc "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    //implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    
    implementation 'androidx. Core: the core - KTX: 1.3.1'
    Androidx. core:core-ktx:1.3.1
    // group: 'androidx.core', name: 'core-ktx', version: '1.3.1'
    implementation group: 'androidx.core'.name: 'core-ktx'.version: '1.3.1'
    
    implementation'androidx. Appcompat: appcompat: 1.2.0'
    implementation 'androidx. Appcompat: appcompat: 1.1.0'
    implementation 'androidx.appcompat:appcompat:1.+'. Slightly}Copy the code

Note here, I will specially androidx. Appcompat: appcompat depend on the different versions, including 1 + said the latest, execute the following command to see the effect above:

+ - androidx. Appcompat: appcompat: 1.2.0 - > 1.4.0 - rc01 | + -- -- -- androidx. Annotation: the annotation: 1.3.0 - rc01 | + -- -- -- Androidx. Core: the core: 1.7.0 (*). | + -- -- -- androidx cursoradapter: cursoradapter: 1.0.0 |... Slightly |... Slightly | \ - androidx. Annotation: the annotation: 1.1.0 - > 1.3.0 - rc01 + - androidx. Appcompat: appcompat: 1.1.0 - > 1.4.0 - rc01 (*) + - androidx. Appcompat: appcompat: 1. + - > 1.4.0 - rc01 (*)Copy the code

From this running effect, we know:

  • Gradle dependency management is optimized
    • If the project has multiple versions of the same dependent library, the highest version is selected by default
    • Gradle automatically eliminates duplicate dependencies
    • Gradle supports dependency passing by default

Since Gradle dependencies are optimized for us, why do we still have dependency conflicts?

  • When introducing a third-party library that also relies on the Android support library, the version of the support library is not the same as the current version. In this case, Gradle automatically selects the highest version, causing incompatibility issues.
  • If you rely on the full library of a group at the same time, but also rely on one of the sub-libraries of the group, if the versions of the two libraries are not unified, it will lead to the problem of dependency conflict. The most typical is the problem caused by branch libraries starting with support-V4’s 24.2.0 release
  • Androidx package and support package conflicts

This problem often occurs before. When you depend on A, A depends on B, then you depend on C, and C directly depends on B instead of A. When the B versions of both parties are different, dependency conflict will occur.

Gradle has already converted all of our dependency trees to AndroidX.

+ - com. Android. Support: appcompat - v7:20.0.0 - > androidx. Appcompat: appcompat: 1.2.0 + - (*) Com. Android. Support. The constraint, the constraint - layout: 1.1.3 - > androidx. Constraintlayout: constraintlayout: 2.0.1 + - (*) Com. Android. Support: support - v4:23.0.0 - > androidx. Legacy: legacy support - v4:1.0.0Copy the code

The system resource bundle Gradle has already solved this problem for us. However, in order to prevent future dependency conflicts caused by non-system resource bundles, I would like to introduce a solution to this problem. After all, it is not bad to have more resources at all.

5.2 Ways to resolve dependency Conflicts

5.2.1 Exclude delivery dependencies

Let’s implement (” Androidx. room: room-Runtime :2.3.0″) implement (” Androidx. room: room-Runtime :2.3.0″)

+ - androidx. Room: room - the runtime: 2.3.0. | + -- -- -- androidx room: room - common: 2.3.0 | | \ -- -- -- Androidx. Annotation: the annotation: 1.1.0 - > 1.3.0 - rc01 | + -- -- -- androidx. Sqlite: sqlite - framework: 2.1.0 | | + -- -- -- Androidx. Annotation: the annotation: 1.0.0 - > 1.3.0 - rc01 | | \ - androidx sqlite: sqlite: 2.1.0 | | \ -- -- -- Androidx. Annotation: the annotation: 1.0.0 - > 1.3.0 - rc01 | + -- -- -- androidx. Sqlite: sqlite: 2.1.0 | + -- -- -- (*) Androidx. Arch. The core: the core - the runtime: 2.0.1 - > 2.1.0 (*) | \ - androidx annotation: the annotation - experimental: 1.1.0Copy the code

Androidx. room:room-runtime dependency tree androidx.room:room-runtime dependency tree

  • Androidx. Room: room – common: 2.3.0
    • Androidx. Annotation: the annotation: 1.1.0
  • Androidx. Sqlite: sqlite – framework: 2.1.0
    • Androidx. Annotation: the annotation: 1.0.0
    • Androidx. Sqlite: sqlite: 2.1.0
      • Androidx. Annotation: the annotation: 1.0.0
  • Androidx. Sqlite: sqlite: 2.1.0 (*)
  • Androidx. Arch. The core: the core – the runtime: 2.0.1

Through this structure, we can clearly recognize which subdependencies each dependency depends on, which subdependencies its next layer depends on, and even how baryonic dependencies there are.

Here we see that different sub-dependencies depend on different versions of annotation:annotation dependency. Let’s first assume annotation: Annotation dependency is not converted to 1.3.0-RC01, then compilation will definitely cause dependency conflict, and this problem can be solved in the following way.

    implementation ("Androidx. Room: a room - the runtime: 2.3.0." "){
        exclude group:'androidx.annotation'.module: 'annotation'
    }
// implementation Group: 'Androidx. annotation', name: 'annotation', version: '1.0.0' equals
    implementation("Androidx. Annotation: the annotation: 1.0.0.")
Copy the code

In Groovy code, we create an additional closure that specifies the annotation dependency to be removed from the Room-Runtime using the exclude keyword. Then rely the annotation separately externally and look at the dependency tree again:

+ - androidx. Room: room - the runtime: 2.3.0. | + -- -- -- androidx room: room - common: 2.3.0 | + -- -- -- Androidx. Sqlite: sqlite - framework: 2.1.0 | | \ - androidx sqlite: sqlite: 2.1.0. | + -- -- -- androidx sqlite: sqlite: 2.1.0 | + -- -- -- Androidx. Arch. The core: the core - the runtime: 2.0.1 - > 2.1.0 (*) | \ - androidx annotation: the annotation - experimental: 1.1.0 + - Androidx. Annotation: the annotation: 1.0.0 - > 1.3.0 - rc01Copy the code

We found annotations: there’s only one annotation.

So what if there are multiple dependency libraries that rely on annotation: Annotation and they all have different versions?

5.2.2 Froce Mandatory

// Force the version
configurations.all {
    resolutionStrategy {
        force 'androidx. Annotation: the annotation: 1.0.0'}}// Dependency configurationdependencies { ... Slightly implementation ("Androidx. Room: a room - the runtime: 2.3.0." ")/ / {
// exclude group:'androidx.annotation', module: 'annotation'
/ /}
// implementation Group: 'Androidx. annotation', name: 'annotation', version: '1.0.0' equals
/ / implementation (" androidx. Annotation: the annotation: 1.0.0 ")
}
Copy the code

In dependencies, we use force to specify annotation:annotation as 1.0.0.

+ - androidx. Room: room - the runtime: 2.3.0. | + -- -- -- androidx room: room - common: 2.3.0 | | \ -- -- -- Androidx. Annotation: the annotation: 1.1.0 - > 1.0.0. | + -- -- -- androidx sqlite: sqlite - framework: 2.1.0 | | + -- -- -- Androidx. Annotation: the annotation: 1.0.0 | | \ - androidx sqlite: sqlite: 2.1.0 | | \ - androidx annotation: the annotation: 1.0.0 | + -- -- -- androidx. Sqlite: sqlite: 2.1.0. (*) | + -- -- -- androidx arch. The core: the core - the runtime: 2.0.1 - > 2.1.0 | \ - (*) Androidx. Annotation: the annotation - experimental: 1.1.0Copy the code

We through the code annotation: the annotation: 1.1.0 – > 1.0.0 can see even if rely on high version 1.1.0 will be forced into 1.0.0.

6, summary

This article has a lot of text, but the content is relatively small, I believe that readers will learn something after reading this article.