Gradle 4种不同依赖方式的区别

1 篇文章 0 订阅
1 篇文章 0 订阅

目录

四种依赖方式的区别

如何确定依赖项顺序


四种依赖方式的区别

主要演示 implementation、api、compileOnly、runtimeOnly 四种依赖方式的区别。

配置

行为

implementation

Gradle 会将依赖项添加到编译类路径,并将依赖项打包到构建输出。不过,其他模块只有在运行时才能使用该依赖项。

api

Gradle 会将依赖项添加到编译类路径和构建输出。当一个模块包含

api依赖项时,会让 Gradle 了解该模块要以传递方式将该依赖项导出到其他模块,以便这些模块在运行时和编译时都可以使用该依赖项。

compileOnly

Gradle 只会将依赖项添加到编译类路径(也就是说,不会将其添加到构建输出)。如果您创建 Android 模块时在编译期间需要相应依赖项,但它在运行时可有可无,此配置会很有用。

runtimeOnly

Gradle 只会将依赖项添加到构建输出,以便在运行时使用。也就是说,不会将其添加到编译类路径。

详细描述见官方 https://developer.android.com/studio/build/dependencies#dependency_configurations

上面说的编译时,运行时,通俗的说,编译时就是能不能在 studio 中不同的模块里点出来相关依赖项中的类,

运行时就是能不能在 app 运行时执行到依赖项中的类。

我们创建一个示例工程,主模块 app ,其他的5个模块 A、B、C、D、E

各模块添加测试类

模块B:

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

模块C:

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

模块D:

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

模块E:

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

模块A:

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

在 A 的 gradle 文件中使用不同的方式依赖上面的四个模块:

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

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

然后在模块 A 的类中尝试调用四个模块中的类:

我们发现只有 runtimeOnly 方式依赖的模块 E 中的 LibraryElephant 类找不到,所以 [ runtimeOnly 依赖方式不在编译时存在 ]

然后在 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}")
        }
    }
}

模块app:

gradle 中添加对模块 A 的依赖:

implementation project(':libraryA')

然后在 app 中尝试调用以上5个模块中的代码:

发现只能调用到模块 A 和模块 C 中的类,A 是直接引用,C 在 A 中是 api project(':libraryC') 方式依赖,透过模块 A 继续暴露在了 app 模块中,所以 [api 依赖方式会在编译时将依赖项中的代码对外暴露]

同时 B、D、E 中的代码无法点出来,所以 [implementation、runtimeOnly、compileOnly依赖方式不会在编译时将依赖项中的代码对外暴露]

在 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()

}

运行日志:

1、红框是在主模块 app 中写的代码

2、篮框是在模块 A 中写的代码

3、绿框是在模块 B 中写的代码

4、红色箭头是在模块 app 和模块 A 中写的代码

日志对比着测试代码,可以很容易的验证不同依赖方式的异同点。

另外根据上面红色箭头的日志,可以很容易的看到只有 compileOnly 依赖方式不会把代码添加到运行时,其余方式均会将依赖项的代码添加到运行时。但是 compileOnly 方式依赖的代码在 studio 中可以正常的调用,只有在运行时执行到时会报错,这就是 compileOnly 方式的潜在风险,所以使用这种方式时一定要确保依赖的项目在他处依赖进运行时了。

另外还有绿框中的日志,这是在模块 B 中通过反射正常执行了 模块 C 中的代码。而 B 和 C 没有任何依赖关系,所以也可以得出一个结论,只要依赖项能被输出到运行时,就可以在项目的任何地方通过直接调用或反射的形式调用。

再来一遍总结:

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

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

如何确定依赖项顺序

当存在多个模块间依赖时,如何对所有的模块排序

以下是官方给的一个示例https://developer.android.com/studio/build/dependencies#dependency-order

依赖项的列出顺序指明了每个库的优先级:第一个库的优先级高于第二个,第二个库的优先级高于第三个,依此类推。在合并资源或将清单元素从库中合并到应用中时,此顺序很重要。

例如,如果您的项目声明以下内容:

依赖 LIB_A 和 LIB_B(按此顺序)

LIB_A 依赖于 LIB_C 和 LIB_D(按此顺序)

LIB_B 也依赖于 LIB_C

那么,扁平型依赖项顺序将如下所示:

LIB_A

LIB_D

LIB_B

LIB_C

这可以确保 LIB_A 和 LIB_B 都可以替换 LIB_C;并且 LIB_D 的优先级仍高于 LIB_B,因为 LIB_A(依赖前者)的优先级高于 LIB_B。

也就是说基于以上的依赖示例,模块优先级是 A>D>B>C

那这个扁平的依赖顺序是怎么来的呢?

推导的排序算法:

感觉类似深度优先的算法,app 依赖 A 和 B, A B 之间排序是

A>B,没问题。

根据深度优先算法,这时候应该先继续以 A 为起点往下寻找,这时候发现 A 依赖 C 和 D,所以:

A>C>D

C D 没有再依赖其他,到此为止,A C D 都没有再往下依赖了,所以停止。A>C>D 作为一个整体的 A 加入到 A>B 的排序中。

所以 A>B 变为:

A>C>D>B

然后开始查找 B,即以 B 为启点往下找,发现 B 依赖 C , 所以得出

B>C

C 没有再依赖其他,所以到这整个依赖关系就结束,所以 B>C 也作为一个整体的 B,加入到 A>C>D>B 中,变为:

A>C>D>B>C

很显然,这里 C 重复了,如何取舍这一大一小两个 C 呢,依据上面的两个依赖关系 A>C>D 和 B>C,C 应该同时满足小于 A 也小于 B。所以最终的结果是 A>D>B>C

简单来看,我们可以概括为一个“保小原则”,即原始排序中出现重复的依赖项时,选择小的,保留小的那一个。

以上推导方式属于个人理解,如有错误还望指正。

那要是出现循环依赖了呢?

别怕,直接报错了:

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

我说靓仔你说哎→→→→→→→→→→→→→→→→→→→→→→→→→→→靓仔点赞动作帅↓

                                                                                                                                                   ↓

                                                                                                                                                   ↓

Gradle中引入GitHub仓库作为项目依赖,通常需要通过`mavenCentral()`或自定义Maven仓库地址。首先,你需要在你的`build.gradle`文件中添加如下配置: 1. **使用本地仓库** (如果仓库已经发布到Maven Central): ```groovy repositories { mavenCentral() // 先从中央仓库查找 } dependencies { implementation 'com.github.username/repository-name:artifactId:version' } ``` 将`username`, `repository-name`, `artifactId`, 和 `version`替换为你想使用的GitHub用户名、仓库名、模块名称和版本。 2. **使用Git URL直接引用** (未发布到Maven Central): ```groovy repositories { maven { url "https://github.com/username/repository-name/raw/master" } // 使用Git克隆地址 } dependencies { implementation 'com.github.username/repository-name:artifactId:branch-or-tag' // 版本可以是分支名或标签 } ``` 这里将`username`, `repository-name`和`branch-or-tag`分别替换为实际的GitHub用户名、仓库名及你想引用的分支或tag。 3. **使用Gradle插件(如Gradle Plug-in Management)**: 如果仓库托管者提供了一个Gradle插件,可以在`plugins`块中添加并指定其版本: ```groovy plugins { id 'com.example.some-plugin' version '0.1.0' // 替换为实际插件ID和版本 } dependencies { implementation 'com.example.some-plugin:some-dependency:0.1.0' // 替换为插件依赖 } ``` 记得更新`id`为实际提供的插件ID。 **相关问题--:** 1. 如何验证Gradle是否成功加载了GitHub库的依赖? 2. Gradle如何处理GitHub上频繁变动的依赖版本? 3. 如果依赖的GitHub仓库有分支依赖,应该如何设置?
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值