0%

Android Gradle最佳实践系列5:多模块构建

Android Studio不仅可以为应用和库创建模块,还可以为Android Wear,Android TV,Google App Engine等创建模块。 所有这些模块可以在单个项目中一起管理。 例如,我们可能需要创建一个应用,该应用使用Google Cloud Endpoints作为后端,并包含与Android Wear的集成。 在这种情况下,就需要有一个包含三个不同模块的项目:一个用于手机应用程序,一个用于后端,一个用于Android Wear集成。 了解多模块项目的结构和构建可以显着加快我们的开发效率,缩短开发周期。

Gradle和Gradle Android插件的文档都使用多项目构建(multiproject builds)的术语。 但是,在Android Studio中,模块和项目之间有区别。 模块指的是一个单独应用:例如Android应用程序或Google App Engine后端。而一个项目(Project)是所有模块的集合。在本书中,我们使用术语模块项目与IDE的方式相同,以避免混淆。 在阅读本篇文章内容时请记住这一点。

在本章中,我们将介绍多模块构建的理论,然后展示一些在实际开发中有用的例子:

  • 解析多模块构建
  • 向项目添加模块
  • 最佳实践

解析多模块构建

通常,项目根目录下包含多个子模块来组织多模块项目。要告诉Gradle项目是如何结构化的,哪些目录包含模块,我们需要在项目的根目录中提供一个settings.gradle文件。然后每个模块都可以提供自己的build.gradle文件。我们已经在第2章“基本构建自定义”中了解了settings.gradle和build.gradle文件的工作方式,因此这里我们将仅关注如何将它们用于多模块项目中。

多模块项目结构目录如下:

1
2
3
4
5
6
7
project
|-setting.gradle
|-build.gradle
|-app
|-buid.gradle
|-library
|-build.gradle

这是设置具有多模块项目最简单和最直接的方法。 settings.gradle文件声明项目中所有模块,其内容如下所示:

1
include ':app', ':library'

确保应用程序和库模块包含在构建配置中,我们只需要添加模块的目录名称到settings文件中即可。

要将库模块添加为应用模块的依赖,需要将其添加到应用程序模块的build.gradle文件:

1
2
3
dependencies {
implementation project(':library')
}

为了对模块添加依赖,您需要使用 project() 方法,并以模块路径作为参数。

如果要使用子目录来组织模块,可以配置Gradle以满足我们的需求。 例如,如果我们项目具有如下所示的目录结构:

1
2
3
4
5
6
7
8
9
10
project
|-setting.gradle
|-build.gradle
|-app
|-build.gradle
|-libraries
|-library1
|-build.gradle
|-library2
|-build.gradle

应用程序模块跟原来一样还是位于项目根目录下,但项目现在有两个不同的库模块。这些库模块不是位于项目的根目录,而是在一个特定的libraries目录下。基于这个目录结构,我们可以这样在settings.gradle声明应用程序和库模块:

1
include ':app', ':libraries:library1', ':libraries:library2'

注意到在子目录中声明模块是很容易的,所有路径都相对于根目录(settings.gradle文件所在的位置),冒号用作路径中的正斜杠的替换。

当在子目录中将模块作为依赖关系添加到另一个模块时,应该始终从根目录引用它。 这意味着,如果上一个示例中的应用程序模块依赖于library1,应用程序模块的build.gradle文件应如下所示:

1
2
3
dependencies {
implementation project(':libraries:library1')
}

如果在子目录中声明依赖关系,所有路径应该仍然相对于根目录。 这样做的是因为Gradle从项目的根目录开始构建项目的依赖关系模型。

Build生命周期回顾

了解构建过程模型是如何构建的可以让你更容易理解多模块项目的组成原理。 我们已经在第1章 开始使用Gradle和Android Studio中讨论了构建生命周期,因此我们已经了解了基础知识,但是一些细节对于多模块构建尤其重要。

在第一阶段,初始化阶段,Gradle寻找一个settings.gradle文件。如果此文件不存在,Gradle会假定我们只有一个构建模块。如果有多个模块,则需要在setting文件中定义包含各个模块的目录。如果这些子目录包含自己的build.gradle文件,Gradle将处理这些子目录,并将它们合并到构建过程模型中。这就解释了为什么我们在app模块依赖library模块时,依赖的路径需要相对于根路径,Gradle总是尝试从根路径中找出依赖。

一旦我们了解了构建过程模型是如何组合在一起的,接下来就需要了解如下三种策略来进行多模块项目构建的配置。

  1. 根build.gradle统一配置:我们可以在根目录中的build.gradle文件中配置所有模块的构建过程,对于一个项目有全局构建配置,及对所有模块都配置的时候,在根build.gradle配置会更方便。但是如果不注意使用,也可能会使得根build.gradle文件变得非常混乱,特别是当我们不同模块需要不同的插件,每个都有需要定义自己的构建过程的时候。
  2. 子模块各自管理build.gradle:另一种方法是为每个模块分别建立build.gradle文件。该策略确保模块彼此不紧密耦合。并且更容易跟踪构建更改,因为我们不需要确定哪个更改适用于哪个模块,每一个模块都有一个独立的build.gradle文件进行管理。
  3. 混合配置(推荐):我们可以在项目根目录中创建一个构建文件,以定义所有模块的公共属性,以及每个模块的构建文件,以配置仅应用于该特定模块的设置。 Android Studio也是遵循此方法,它在根目录中创建一个build.gradle文件,并为模块创建另一个build.gradle文件。

模块任务

当我们在命令行界面中从项目根目录运行任务时,Gradle将计算项目中所有模块具有的任务并为每个模块执行它。如:我们有一个移动设备应用模块和一个Android Wear模块,运行 gradlew assembleDebug gradle将构建移动应用程序模块和Android Wear模块的debug版本。但是,当我们将目录路径更改为其中一个模块时,Gradle将只运行该特定模块的任务,即使我们在项目的根目录中使用Gradle包装器。从Android Wear模块目录运行 ./gradlew assembleDebug 只会构建 Android Wear 模块。

切换目录以运行特定于模块的任务会比较繁琐。我们还可以使用 模块名称 + 任务名称 ,实现在该特定模块上运行任务。例如,仅构建Android Wear模块,可以使用 gradlew :wear:assembleDebug 命令。

向项目添加模块

通过在Android Studio中执行添加新模块,会出现添加向导,通过引导可快速完成新模块创建。在某些情况下,添加模块Android Studio可能会编辑原有应用程序模块的构建文件。如,添加Android Wear模块时,AndroidStudio会假设我们想要在应用模块中使用它,会在构建文件的依赖块中添加一行依赖Android Wear模块的代码。

如下图所示是Android Studio中的New Module引导框的样子:

在接下来的部分中,我们将学习如何将不同模块添加到Android Studio中,顺带理解自定义属性,并如何修改构建过程。

添加Java Library模块

当我们添加新的Java库模块时,Android Studio生成的build.gradle文件如下所示:

1
2
3
4
5
apply plugin: 'java'

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}

Java库模块使用Java插件,而不是我们常用的Android插件。这意味着很多Android特定的属性和任务不可用,在Java库中,我们不也需要那些任务。

构建文件还具有基本的依赖关系管理设置,因此我们可以将JAR文件添加到libs文件夹,而无需任何特殊配置。 可以使用第3章“管理依赖关系”中学到的内容添加更多依赖关系。依赖关系配置不依赖于Android插件。

例如,要将名为javalib的Java库模块添加为应用程序模块的依赖关系。只需将此行添加到app模块的构建配置文件中:

1
2
3
dependencies {
implementation project(':javalib')
}

Gradle会在构建中导入一个名为javalib的模块。如果在app模块中添加此模块的依赖,那么javalib模块将始终在应用程序模块构建之前先行构建。

添加Android Library模块

Android Library的默认build.gradle文件从这行开始:

1
apply plugin: 'com.android.library'

在Android Library模块上添加依赖关系的方法与Java库完全相同:

1
2
3
dependencies {
implementation project(':androidlib')
}

Android Library不仅包含Java代码,而且还包含所有Android资源,例如manifest文件,string和layout。 在应用中引用Android Library后,你可以使用Library中的所有的class和resource。

集成Android Wear

如果我们想将应用程序深度整合到Android Wear中,就必须新增Android Wear模块。但需要注意的是,Android Wear模块也使用Android application plugin。这意味着application的所有构建属性和任务都可用。

build.gradle文件与常规Android应用程序模块不同的唯一部分是依赖性配置:

1
2
3
4
5
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.android.support:wearable:1.1.0'
implementation 'com.google.android.gms:play-services-wearable:6.5.87'
}

每个Android Wear应用都依赖于Google提供的几个Android Wear专用库。要在Android应用中依赖Android Wear应用,我们可以在Android应用中加入wear库的依赖:

1
2
3
dependencies {
wearApp project(':wear')
}

以上配置可确保Wear模块在进行必要的设置后将wear模块的APK被添加到Android应用程序的APK中。

最佳实践

接下来我们学习几种方法来更容易地处理多模块项目;在使用多个模块时还有一些注意事项,本节也会详细介绍。

在Android Studio中运行模块的gradle任务

正如我们在第2章 自定义Build配置中学习到的,可以在Android Studio直接运行Gradle任务。当有多个模块时,Android Studio也会识别它们,并分组显示所有可用任务及相关描述。

Gradle工具窗口使运行特定模块的Gradle任务变得更加容易。但是问题是没有选项可以同时运行所有模块的任务,所以如果需要Gradle任务同时在所有模块上生效,使用命令行还是更快一些。

加快多模块编译速度

当我们构建多模块项目时,Gradle会按顺序处理所有模块。 随着多核计算机越来越多,可以设置并行构建来加快构建速度。此功能在Gradle中已存在,但默认情况下未启用。

如果要对项目应用并行构建执行,则需要在项目根目录下的gradle.properties文件中配置parallel属性:

1
org.gradle.parallel=true

Gradle会根据可用的CPU内核数选择正确的线程数。为了防止在同一模块并行执行而产生并发问题,每个线程都拥有一整个模块。

模块耦合

正如我们在第2章 自定义Build配置中看到的,可以使用build.gradle文件中的 allprojects 定义项目中所有模块的属性。当我们具有包含多个模块的项目时,可以使用任何模块中的 allprojects 将属性应用于项目中的所有模块。一个模块甚至可以引用另一个模块的属性。这些强大的功能可以使多模块构建的维护更容易。但缺点是,我们的模块变得耦合严重。

一旦两个模块访问对方的任务或属性,就认为它们是耦合的。这有几个后果,例如,模块变得难以移植,当我们试图将某一个项目引用到其他项目中时,因为模块间的耦合,导致抽离出模块很困难;模块耦合对并行构建也有影响,在模块中使用allprojects块将使并行构建执行无效。

我们可以通过不直接访问其他模块的任务或属性来避免耦合。通过使用根模块作为中介,使得模块仅耦合到根模块,而不是彼此耦合,从而解决模块间耦合问题。

总结

这一章,我们研究了如何在单个项目中设置多个模块, 还了解到了添加新模块会影响构建任务的运行方式。

然后,我们查看了一些添加新模块的实际示例,以及如何将每个模块集成到一个项目中。 最后,我们提到了一些优化技巧,使得在一个项目中使用多个模块更容易。

在下一章中,我们将设置各种测试,并了解如何使用Gradle来更容易运行这些测试。我们将直接在Java虚拟机上查看运行单元测试,也可以在真机或模拟器上运行测试。