The difference between the 4 different dependency methods of Gradle

Table of contents

The difference between the four types of dependence

How to determine dependency order


The difference between the four types of dependence

It mainly demonstrates the difference between implementation, api, compileOnly, and runtimeOnly four dependency methods.

configuration

Behavior

implementation

Gradle will add the dependencies to the compile classpath and package the dependencies into the build output. However, other modules can only use this dependency at runtime.

api

Gradle will add dependencies to the compile classpath and build output. When a module contains

api dependency, let Gradle know that the module should transitively export that dependency to other modules so that those modules can use the dependency at both runtime and compile time.

compileOnly

Gradle only adds the dependency to the compile classpath (that is, it doesn't add it to the build output). This configuration is useful if you create an Android module that requires a dependency at compile time, but does not have it at runtime.

runtimeOnly

Gradle only adds dependencies to the build output for use at runtime. That is, it will not be added to the compile classpath.

For a detailed description, see the official https://developer.android.com/studio/build/dependencies#dependency_configurations

The above-mentioned compile time, runtime, in layman's terms, compile time is whether you can point out the classes in the related dependencies in different modules in the studio,

The runtime is whether the classes in the dependencies can be executed when the app is running.

We create a sample project, the main module app, and the other 5 modules A, B, C, D, E

Add test classes to each module

Module B:

class LibraryBee {
    fun keepHealth() {
        println("i am library B , 勤劳致富")
    }
}

Module C:

class LibraryCat {
    fun doNotCome() {
        println("i am library C, 爱卿平身")
    }
}

Module D:

class LibraryDog {
    fun happy() {
        println("I am library D, 来玩儿啊")
    }
}

Module E:

class LibraryElephant {
    fun run() {
        println(" I am library E,力拔山兮气盖世")
    }
}

Module A:

class LibraryAnt {
    fun hello() {
        println("hello , i am A,团结就是力量")
    }
}

Use different ways to depend on the above four modules in the gradle file of A:

dependencies {
    implementation project(':libraryB')//代码会在运行时,但编译时不会对外暴露
    api project(':libraryC')//代码会在运行时,编译时代码会对外完整暴露
    compileOnly project(':libraryD')//代码不会在运行时存在,编译时对当前模块暴露,不对外暴露
    runtimeOnly project(':libraryE')//代码会在运行时,编译时不存在,不对外暴露

    //凡是在运行时存在的代码,在任何模块中都可以通过反射调用到,比如 A implementation B、api C,B、C 之间可以互相调用
}

Then try to call the classes in the four modules in the class of module A:

We found that the LibraryElephant class in module E that only depends on the runtimeOnly way cannot be found, so [the runtimeOnly way does not exist at compile time]

Then add the code in A:

class LibraryAnt {
    fun hello() {
        println("hello , i am A,团结就是力量")
       
        println("a直接调用了 模块B")
        LibraryBee().keepHealth()
        
        println("a直接调用了 模块C")
        LibraryCat().doNotCome()
        
        println("a直接调用了 模块D")
        try {
            LibraryDog().happy()
        } catch (e: Throwable) {
            println("a直接调用 模块D 出错: ${e.message}")
        }
        
        println("a通过反射在运行时调用 模块E")
        try {
            val classE = Class.forName("com.hdf.librarye.LibraryElephant")
            val methodE = classE.getDeclaredMethod("run")
            methodE.invoke(classE.getConstructor().newInstance())
        } catch (e: Exception) {
            println("a通过反射在运行时调用 模块E 出错:${e.message}")
        }
    }
}

Module app:

Add a dependency on module A in gradle:

implementation project(':libraryA')

Then try to call the code in the above 5 modules in the app:

It is found that only the classes in module A and module C can be called, A is a direct reference, and C is dependent on api project(':libraryC') in A, and continues to be exposed in the app module through module A, so [ api The dependency method will expose the code in the dependency to the outside world at compile time] .

At the same time, the code in B, D, and E cannot be clicked out, so [implementation, runtimeOnly, compileOnly dependency methods will not expose the code in the dependencies to the outside world at compile time]

Add test code in App:

login.setOnClickListener {
    println("主模块只依赖 模块A, 模块A implementation 模块B、api 模块C、compileOny 模块D、runtimeOnly 模块E")
    println("主模块直接调用模块A的方法")
    LibraryAnt().hello()

    println("主模块中调用模块B的方法:通过反射调用")
    try {
        val classB = Class.forName("com.hdf.libraryb.LibraryBee")
        val methodB = classB.getDeclaredMethod("keepHealth")
        methodB.invoke(classB.getConstructor().newInstance())
    } catch (e: Exception) {
        println("主模块通过反射调用模块B出错,${e.message}")
    }

    println("主模块中调用模块D的方法:通过反射调用")
    try {
        val classB = Class.forName("com.hdf.libraryd.LibraryDog")
        val methodB = classB.getDeclaredMethod("happy")
        methodB.invoke(classB.getConstructor().newInstance())
    } catch (e: Exception) {
        println("主模块通过反射调用模块D出错,${e.message}")
    }

    println("主模块中调用模块E的方法:通过反射调用")
    try {
        val classB = Class.forName("com.hdf.librarye.LibraryElephant")
        val methodB = classB.getDeclaredMethod("run")
        methodB.invoke(classB.getConstructor().newInstance())
    } catch (e: Exception) {
        println("主模块通过反射调用模块D出错,${e.message}")
    }

    println("主模块直接调用模块C的方法")
    LibraryCat().doNotCome()

}

Running log:

1. The red box is the code written in the main module app

2. The basket is the code written in module A

3. The green box is the code written in module B

4. The red arrow is the code written in module app and module A

By comparing the log with the test code, it is easy to verify the similarities and differences of different dependency methods.

In addition, according to the log of the red arrow above, it can be easily seen that only the compileOnly dependency method will not add the code to the runtime, and the other methods will add the dependency code to the runtime. However, the code that the compileOnly method depends on can be called normally in the studio, and an error will be reported only when it is executed at runtime. This is the potential risk of the compileOnly method, so when using this method, you must ensure that the dependent project depends on other places to run It's time.

In addition, there are logs in the green box, which means that the code in module C is executed normally through reflection in module B. But B and C do not have any dependencies, so it can also be concluded that as long as the dependencies can be output to the runtime, they can be called directly or reflected anywhere in the project.

Summarize again:

dependencies {
    implementation project(':libraryB')//代码会在运行时存在,但编译时不会对外暴露
    api project(':libraryC')//代码会在运行时存在,编译时代码会对外完整暴露
    compileOnly project(':libraryD')//代码不会在运行时存在,编译时对当前模块暴露,不对外暴露
    runtimeOnly project(':libraryE')//代码会在运行时存在,编译时不存在,编译时对当前模块和对外都不暴露

    //凡是在运行时存在的代码,在任何模块中都可以通过反射调用到,比如 A implementation B、api C,B、C 之间可以互相调用
}

How to determine dependency order

How to sort all modules when there are multiple inter-module dependencies

The following is an example given by the official https://developer.android.com/studio/build/dependencies#dependency-order

The order in which the dependencies are listed indicates the priority of each library: the first library takes precedence over the second, the second over the third, and so on. This order is important when merging resources or merging manifest elements from a library into an app.

For example, if your project declares the following:

Depends on LIB_A and LIB_B (in that order)

LIB_A depends on LIB_C and LIB_D (in that order)

LIB_B also depends on LIB_C

Then, the flat dependency order will look like this:

LIB_A

LIB_D

LIB_B

LIB_C

This ensures that both LIB_A and LIB_B can replace LIB_C; and LIB_D still takes precedence over LIB_B because LIB_A (on which it depends) takes precedence over LIB_B.

That is to say, based on the above dependency example, the module priority is A>D>B>C

So how did this flat dependency order come about?

Derived sorting algorithm:

It feels like a depth-first algorithm, the app depends on A and B, and the sorting between AB is

A>B, no problem.

According to the depth-first algorithm, at this time, you should continue to search from A as the starting point. At this time, you find that A depends on C and D, so:

A>C>D

CD does not depend on others. So far, ACD has no further dependence, so stop. A>C>D A as a whole is added to the order of A>B.

So A>B becomes:

A>C>D>B

Then start to search for B, that is, start with B as the starting point and find that B depends on C, so it is concluded

B>C

C does not depend on others, so the entire dependency relationship ends, so B>C is also added to A>C>D>B as a whole, becoming:

A>C>D>B>C

Obviously, C is repeated here. How to choose between the big and the small C? According to the above two dependencies A>C>D and B>C, C should be smaller than A and B at the same time. So the final result is A>D>B>C

To put it simply, we can generalize it to a "principle of keeping small" , that is, when there are duplicate dependencies in the original sorting, choose the small one and keep the small one.

The above derivation method belongs to personal understanding, and please correct me if I am wrong.

What if there is a circular dependency?

Don't be afraid, report an error directly:

Circular dependency between the following tasks:
:libraryA:generateDebugRFile
\--- :libraryB:generateDebugRFile
     \--- :libraryA:generateDebugRFile (*)

I say pretty boy, you say hey→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→

                                                                                                                                                   ↓

                                                                                                                                                   ↓

Guess you like

Origin blog.csdn.net/xx23x/article/details/121860651