Gradle:添加依赖项的优缺点

19

相比将依赖库作为依赖项添加,将依赖项添加到build.gradle中有哪些优缺点?

dependencies {
    compile project(':library')
    ...
    compile 'com.stackoverflow.android:some-great-library:1.0'
    ...
}

在开发Android项目时,我经常遇到与我正在寻找的确切解决方案相符的优秀库。然而,由于我需要的仅是这些特定库所提供内容的一小部分,我担心将它们添加为Gradle依赖项是否有些 过头了


根据@CommonsWare的评论,我有更具体的问题。

添加依赖项是否会:

  1. 明显降低编译时间吗?

  2. 增加发布版apk和调试版apk的大小,就像添加的依赖项大小一样吗?


下载库,将其修改为项目的一部分并在本地使用会过度繁琐,我认为。为什么一个 build.gradle 文件中的一行条目会被认为是“过度繁琐”呢? - CommonsWare
5
在您关注的领域中,本地模块和远程依赖项之间不应该有显着差异。它们是相同的代码、资源、资产和清单条目。主要是一个问题,即它们如何到达您的硬盘驱动器。如果有什么区别的话,我期望远程依赖项构建速度更快,因为Java源代码已经编译为Java字节码。话虽如此,我尚未进行比较这两者性能的分析。 - CommonsWare
9个回答

8
在Gradle中,依赖项被分组为配置作为依赖项配置。外部依赖项是对在当前构建之外构建的某些文件的依赖项,并存储在某种存储库中,例如Maven中央或企业Maven或Ivy存储库或本地文件系统中的目录。
依赖类型:
外部模块依赖项:
对某个存储库中的外部模块的依赖项。
项目依赖项:
对同一构建中的另一个项目的依赖项。
文件依赖项:
对本地文件系统上的一组文件的依赖项。
客户端模块依赖:
对外部模块的依赖,其中工件位于某个存储库中,但模块元数据由本地构建指定。您在需要覆盖模块元数据时使用此类依赖项。
Gradle API依赖项:
对当前Gradle版本的API的依赖关系。您在开发自定义Gradle插件和任务类型时使用此类依赖项。
本地Groovy依赖项:
对当前Gradle版本使用的Groovy版本的依赖项。您在开发自定义Gradle插件和任务类型时使用此类依赖项。
添加依赖项的好处:
在Gradle中,您只需声明您的依赖项的“名称”,其他层确定从哪里获取那些依赖项。
在子项目中可以进行代码更改。
清晰的直接依赖性包括所使用库的版本清单和清晰的间接依赖性管理(即所使用的库使用另一个库-POM文件){来自用户Robert Halter}。
在线可用性依赖。
所需的SDK库已与项目同步以进行编译。
缺点:
因此,它没有任何缺点,但在添加依赖项的初始阶段,我们必须进行同步,这比简单地拖放jar文件需要花费一些时间。
会显着降低编译时间吗?
否,因为只有在同步时才会编译库。
会增加发布APK和调试APK的大小,就像添加的依赖项一样大吗?
不是,但实际上是的,因为它只占用构建文件(二进制文件)的大小,这几乎不会增加几个字节或KB的大小,几乎无法跟踪。
因此,当我们只需要使用特定库的一小部分时,将它们作为Gradle依赖关系添加是否过度?
肯定会有像你这样的情况,只需要使用库的子集。在这种情况下,启用ProGuard可以优化性能分析,从字节码级别开始,在方法内部和跨方法进行分析,以使您的应用程序更小、运行更快,而不会过度杀伤力。

4

主要问题是您的安卓应用程序大小。 许多方法或类可能会使dex文件上限达到64k,并且大多数情况下需要启用multi-dex或jumbo模式。如果您有非常过时的客户,这可能会创建一些兼容性问题。

有时,您可能希望通过仅提取所需的类来解决此问题,但有时这并不容易。

如果启用了proguard,则未使用的类可以被系统地删除。


3

将第三方库的代码复制到您的项目中会导致支持该项目时出现一些问题。如果您需要使用新版本的该库,那怎么办呢?如果将其添加为依赖项,则只需在构建系统配置中更改版本号即可。


3

将子项目的依赖项添加到Gradle构建项目中有哪些利弊?

dependencies {
    compile project(':library')
}

优点

  • 可以更改:library子项目中的代码

缺点

  • 需要自己管理:library子项目的版本

在Gradle构建项目中添加依赖库a的版本有哪些优缺点?

dependencies {
    compile 'com.stackoverflow.android:some-great-library:1.0'
}
优点
  • 清晰直接的依赖关系,包括所使用的库的版本。
  • 管理明确的间接依赖关系(即所使用的库使用另一个库-POM文件)。
  • 其他人/组织负责库的功能-您可以专注于自己的代码。
缺点
将依赖项添加为:library子项目会显着降低编译时间吗?
  • 是的,它们也必须编译,因为它们在您的子项目中的源代码中。
  • Gradle通过任务的输入/输出定义来优化构建时间。在构建过程中查看UP-TO-DATE标记。
将依赖项添加为具有版本依赖库会显着降低编译时间吗?
  • 不会,因为库(.jar)已经编译好了。
将依赖项添加是否会增加发布-apk和调试-apk的大小,与添加的依赖项的大小一样大吗?
  • apk大小会增加所添加依赖项的二进制大小(.jar =压缩的.class文件)。
  • (如果启用了Proguard,则未使用的类可以被系统地删除-来自用户phdfong)

编译 'com.stackoverflow.android:some-great-library:1.0'。正如 OP 所说,如果你只需要其中的一些功能,则大部分库都不会被使用。将依赖项添加为版本依赖库是否会导致 Gradle 尝试重新获取更新? - Rachit Mishra
如果版本不是快照版本(即1.0-SNAPSHOT),那么通常不需要重新缓存最终版本。 否则,请使用./gradlew ... --refresh-dependencies ... 请参阅https://docs.gradle.org/current/userguide/gradle_command_line.html。 - Robert Halter

2
是否将第三方库作为依赖项添加:
当您的程序在编译时或运行时使用此库中的某些代码时,您需要声明对第三方库的依赖关系。这意味着当您的程序运行时必须能够访问第三方代码,因此它会随程序一起打包。因此,应用程序的大小会增加,这是不希望的,因为许多用户不喜欢庞大的应用程序。当您的程序很小而第三方库很大时,这也是荒谬的。
是否将第三方库作为模块添加:
我从未这样做过,但如果我理解正确,它与将库复制到项目中相同,以便您可以修改它。如果您使用发布版,则与远程依赖项编译相比,没有编译时间差异。所有内容都与上一段中相同,但另外您可以自由修改代码(如果第三方库许可允许)。
是否仅提取所需代码:
这意味着只提取您程序所需的部分代码,而不是整个第三方库。这样可以减少应用程序的大小,但需要注意确保提取的代码能够正常工作,并且不会遗漏任何重要的功能。
这是最好的解决方案,如果你只需要库中的几个方法,并且很容易提取它们(前提是许可证允许这样做)。另一方面,如果你使用一些可靠的第三方功能并想要在项目中提取和修改它们,那么你将来可能会面临维护问题(如果你决定迁移到新版本)。

添加依赖项:是否会显著降低编译时间?

通过正确设置Gradle(作为后台守护进程运行,使用并行处理),在典型项目中不会看到任何剧烈变化。

添加依赖项:是否会增加发布apk和调试apk的大小,就像添加的依赖项一样大?

依赖jar也会被发送给dex进行字节码转换,以及你的项目代码。这并不是大小上的1:1转换,因此答案是否定的。另外,ProGuard有一些技巧可以最小化jar文件的大小,Maven有一个插件可以将所有依赖的jar打包在一个jar中,这样它们都可以被混淆。

2
我认为这个问题不仅仅依赖于Gradle,而更普遍地取决于“依赖管理工具”的使用。
当然,有些情况下我们只需要库的一个子集。在这种情况下,导入整个依赖可能不是最好的解决方案。
那么这种情况到底有多“过度”?显然这取决于你的项目大小。你需要评估依赖项可以超过多少,“过度”的程度:例如,如果你只需要一个简单的函数,而你正在导入一个3MB的库,那么很可能是有点“过度”!
此外,你还需要考虑通常“大型”库被分成模块,因此你只能导入你需要的模块。在Android的情况下,正如@phdfond所说,你可以使用ProGuard在构建时删除未使用的类,或者你可以使用Gradle手动排除它们:
//...      
sourceSets {
         main {
             java {
                 exclude '**/IDontWantThisClass.java'
             }
         }

但是在我看来,这种情况并不常见,而且使用Gradle或其他“依赖管理工具”可以带来很多优点(请注意,在Gradle、Maven等工具中,依赖管理只是它们功能之一)。
例如,如果您正在使用Gradle,您可以一眼知道您的依赖关系,并且可以轻松地添加/删除它们或仅更改数字以更新其版本。您是否曾经想过,您的项目可能依赖于库B和C,而B和C都可能依赖于库D的两个不同版本?好吧,Gradle也可以帮助您解决这种情况。
我不能在这里继续讲下去,因为这会偏离主题,但是您可以在这些工具中找到大量信息。
最后,我认为“依赖管理工具”的最重要的“缺点”是初始学习和设置时间。如果您从未使用过像Gradle这样的工具,那么第一次肯定需要比拖放jar文件花费更多的时间,因此我强烈建议您在项目中使用Gradle,除非它只是一个“玩具项目”。

1

Gradle依赖是一种浪费

如果您正在使用某个稳定的库或者您对当前库的功能感到满意,那么Gradle依赖就是一种浪费。此外,当库具有向后兼容的依赖项或者具有一组您根本不需要的特性或类时,也是如此。

解决方案是从库中获取您所需的内容(只有在确定它将满足您的要求时)。

但并非总是如此

如果您想要自动更新和修复库,以及易于集成,许多库每天都在制作和维护。因此,APK大小对我们来说可能并不那么重要,而库的更新则更为重要,因此Gradle导入在这里很好。

关于APK大小的担忧

Gradle依赖可能会有很多无关的内容,向后支持您的应用程序不需要的内容,这最终会增加应用程序的大小,还会导致编译速度变慢。

因此,如果该库只有一个或少数几个类,您可以直接将其添加到某个包中的应用程序中。一个很好的例子是Google IO应用程序,其中一些库在不同的包中。

你还可以控制调试或发布apk中的内容,对于调试依赖使用debugCompile,对于测试依赖使用testCompile

将库添加为模块依赖项

将其作为库模块添加可以通过减少获取依赖项所需的下载时间来加快构建过程,这可以通过在Android Studio中启用Gradle的离线同步轻松实现。
此外,如果您开发了一些私有库并在整个项目中使用它,则库模块是最好的方式。如果不是这种情况,您将在maven central上发布和维护该库,并且可以通过gradle更容易地集成到您自己的项目组中。

1
我认为它们是根本不同的东西。 1. Gradle子项目
dependencies {
  compile project(':foobarlib')
}

在这个案例中,您依赖于一个Android子项目,它是您正在工作的gradle项目的一部分。您也可以将这种依赖关系称为模块,就像Intellij一样。
如果您希望进行更改,只需对根gradle项目进行gradle编译即可使更改生效。 2. Android库项目
dependencies {
  compile 'com.example:foobarlib:2.1.0'
}

在第二种情况下,您依赖的是一个“artifact”(即以JAR或AAR格式存档的文件),而不是一个项目。对于您的Gradle项目来说,这基本上是一个黑匣子。 Gradle所知道的只是它包含编译类(在AAR的情况下还包括资源),这些类是您的项目所需的。
如果您想在库中进行更改,首先需要找到其对应的项目以编辑其源代码,编译项目并将新的“artifact”发布到本地或远程“maven repository”中。最后,为了使您的项目看到对另一个项目所做的更改,您必须更新声明的依赖项中的版本号,以便Gradle知道它必须带来新发布的“artifact”。
这两种方法哪个更好取决于具体情况。只要考虑一下库是否有自己存在的意义,或者实际上它是否只是您的应用程序的一个模块即可。正如您所看到的,如果您只是在处理应用程序的一个模块,则第二种方法会增加太多开销,并且没有理由在没有其余应用程序的情况下对这些更改进行版本控制和分发。

1
1. slow down compilation time at a noticeable rate?

简短回答:是的。

长话短说:我们目前在项目中使用了Gradle,我体验到了编译时间上的显著差异。特别是对于具有多个模块依赖的项目而言更为如此。同时还要提到的是,如果您不断添加大量的依赖项,只是为了使用其中的一堆类,最终可能会遇到非常出名的:

Too many references: 66539 error; max is 65536.

我自己在使用资源密集型库(例如 play-services)时,已经多次遇到过这种情况(gradlemaven)。
2. `increase the size of release-apk and debug-apk, as much as the size of the added dependency?`

肯定会影响。对于发布 APK 来说,你可以优化 Proguard 到任何程度。但是,在调试 APK 的情况下,我见过大小变化高达 5-6 MB。

这只是我的个人意见。希望能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接