IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Gradle 4种不同依赖方式的区别 -> 正文阅读

[移动开发]Gradle 4种不同依赖方式的区别

目录

四种依赖方式的区别

如何确定依赖项顺序


四种依赖方式的区别

主要演示 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 (*)

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-11 15:50:25  更:2021-12-11 15:51:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:39:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码