Gradle for Android(五) 管理多模块构建

Android Studio不仅可以为app或者library创建模块,还可以为Android Wear,Android TV,Google App Engine等创建模块。所有的这些模块可以在一个工程中使用。你可能想要创建一个程序,它使用Google Cloud Endpoints作为后台,并与Android Wear集成。这种情况下,你的工程中需要有三个模块:app、后台、Android Wear集成。了解多模块工程的结构和构建,可以极大缩短你的开发周期。

Gradle和Gradle Android插件的文档都使用了多工程构建(multiproject builds)这一术语。在Android Studio中,moduleproject是有区别的。比如,一个模块可以是一个Android app或者一个Google App Engine后台。而一个工程是一个模块的集合。本书为避免混淆,使用IDE中的概念来理解moduleproject。在浏览文档时要谨记。

本章讲解多模块构建,并提供一些对实际工程很有用的示例。

  • 多模块构建的剖析
  • 为工程添加模块
  • 技巧和最佳实践

多模块构建的剖析

通常一个多模块工程会有一个根目录,每一个模块有一个子目录。为了让Gradle知道工程的结构,以及每个子目录是什么模块,你需要在根目录添加一个settings.gradle文件。每个模块可以提供它自己的build.gradle文件。我们在第二章已经介绍了settings.gradle文件和build.gradle文件的作用,这里我们介绍如何在多模块工程中使用它们。

下面是多模块工程的结构:

project
├─── setting.gradle
├─── build.gradle
├─── app
│     └─── build.gradle
└─── library
      └─── build.gradle

这是设置多模块工程最简单和直接的方式。settings.gradle文件声明了工程的所有模块:

include ':app', ':library'

这保证了applibrary模块包含在构建配置中。你需要做的只是将模块所在的目录添加进来。

你需要将以下代码添加到app模块的build.gradle文件中,以便app模块将library模块添加为依赖:

dependencies {
    compile project(':library')
}

为给模块添加依赖,你需要使用project()方法,参数为模块路径。

如果你想使用子目录来组织模块,Gradle也可以满足需求。比如,你的目录结构可能是这个样子的:

project
├─── setting.gradle
├─── build.gradle
├─── app
│    └─── build.gradle
└─── libraries
     ├─── library1
     │     └─── build.gradle
     └─── library2
           └─── build.gradle

app模块还在根目录下,但工程有两个库。这些库不在工程的根目录下。你可以在settings.gradle文件中如下声明各个模块:

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

可以看到声明子目录中的模块也是很容易的。路径是相对于根目录(settings.gradle文件所在的目录)的。冒号用于替换路径中的斜杠。

在将一个子目录模块添加为另一个模块的依赖时,你应该从根目录引用它。也就是说,如果上例中的app模块依赖于library的话,app模块的builde.gradle文件应该这样写:

dependencies {
    compile project(':libraries:library1')
}

Gradle在构建工程依赖时,总是相对于根目录。

重温构建的生命周期

了解构建过程模型是如何构造的,可以更容易地理解多模块项目是如何组成的。我们已经在第一章提到过构建的生命周期,所以你已经有了基础,但是一些细节对于多模块构建来说也是很重要的。

在第一阶段,即initialization期间,Gradle首先找到settings.gradle文件。如果这个文件不存在,Gradle认为这是一个单模块的构建。如果你有多个模块,你需要在settings文件中定义包含每个模块的子目录。如果这些子目录有它们自己的build.gradle文件,Gradle将会自动处理,并将它们合并到构建过程模型中。这解释了为什么你需要在模块中用相对路径声明依赖。Gradle会尝试从根目录中找出依赖项。

一旦你了解了构建过程模型是如何组合在一起的,配置多模块构建的几种策略将变得非常清晰。

  • 你可以在根目录的build.gradle文件中配置所有模块。这会使浏览工程的整个构建配置变得很容易,但是结构会混乱,尤其是在各个模块需要不同的插件,每个插件拥有自己的DSL时。
  • 另一种方式是为每个模块单独配置build.gradle文件。这可以解耦各个模块。它还使跟踪构建更改变得容易,因为你不需要知道哪个模块应用了哪个更改。
  • 最后一个策略是混合使用。你可以在根目录的构建文件中定义所有模块通用的属性,在每个模块的构建文件中定义各自模块的配置。Android Studio使用了这个方式。它会在根目录创建一个build.gradle文件,并为各个模块创建自己的build.gradle文件。

模块任务

一旦你的工程中有了多个模块,你就需要在运行任务前多思考一下。当你在工程根目录下通过命令行窗口运行任务时,Gradle会找出所有模块中的同名任务,并运行它们。比如,你有一个app模块和一个Android Wear模块,运行gradlew assembleDebug将会为app模块和Android Wear模块都构建一个debug版本。如果你切换到模块的目录下,Gradle将只会运行该模块的任务,即使你在工程根目录中运行Gradle Wrapper。比如,在Android Wear模块的目录运行../gradlew assembleDebug,只会构建Android Wear模块。

切换目录的方式来运行模块的任务是比较繁琐的。另一个方式是可以使用模块名称来预处理任务名称,使运行任务只在那个模块上运行。比如只想构建Android Wear模块,可以使用gradlew :wear:assembleDebug命令。

为工程添加模块

Android Studio有向导引导你简单易懂的添加一个模块。向导也会为构建文件添加一些基本内容。某些情况下,添加一个模块会使Android Studio编辑app模块的构建文件。比如,在添加一个Android Wear模块的时候,IDE会认为你想在Android app中使用它,因此会在构建文件中添加对Android Wear模块的引用。

在接下来的部分,我们将会展示Android Studio中的工程可以添加的几个不同的模块,介绍它们的属性,以及它们如何影响构建过程。

添加一个Java库

在你添加一个新的Java库模块的时候,build.gradle文件如下:

apply plugin: 'java'

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

Java库模块使用了Java插件而不是我们经常看到的Android插件。这表示很多Android特有的属性和任务是无法使用的,而对于Java库来说也并不需要它们。

构建文件还建立了基本依赖管理,所以你可以在libs文件夹添加JAR包,而不需要任何特殊配置。你可以用第三章学到的知识添加更多的依赖。依赖配置不依赖于Android插件。

比如,为了给app模块添加一个名为javalib的Java库模块你只需要在app模块的构建文件中添加一行代码:

dependencies {
    compile project(':javalib')
}

这样Gradle就会在构建时导入一个名为javalib的模块。如果你在app模块添加了这个依赖,那么在app模块开始构建之前,javalib模块会先被构建。

添加一个Android库

我们在第三章简单提及了Android库,并称之为库工程。在文档和各种教程中,都用到了这两个名称。在这一部分内容中,我们使用Androidlibrary(Android库)这个名称,因为这是在Android Studio的New Module对话框中使用的名称。

Android库的build.gradle文件起始代码如下:

apply plugin: 'com.android.library'

添加Android库为依赖和添加Java库是一样的:

dependencies {
    compile project(':androidlib')
}

一个Android库不仅包含Java代码,还包括所有的Android资源,比如manifest文件、字符串和布局等。添加Android库为依赖后,你可以使用库中所有的类和资源。

集成Android Wear

如果你想在Android Wear中加入深度集成的应用程序,你需要添加Android Wear模块。Android Wear模块同样使用了Android application插件,所以所有的构建属性和任务都可以使用。

build.gradle文件唯一和常规Android app模块不同的部分是依赖配置:

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

每个Android Wear应用都依赖一些Google提供的Wear特有的库。为了在Android app中使用Android Wear app,你需要将它打包进app中。你可以在Android app中添加一个依赖:

dependencies {
    wearApp project(':wear')
}

wearApp配置确保Wear APK打包进最终的Android app的APK中,并为你做了必要的配置。

使用Google App Engine

Google App Engine是一个云计算平台,您可以使用它来托管web应用程序,而无需设置自己的服务器。在一定程度上它是免费的,这使它成为一个很好的实验环境。Google App Engine还提供一个称谓Cloud Endpoints(云终端)的服务,它可以用来创建基于REST的服务。使用Google App Engine和Cloud Endpoints可以很容易地为你的应用构建一个后台。Gradle App Engine插件通过为你的app生成一个客户端库使其变得更加简单,这意味着你不需要自己去写任何API相关的代码。这就使Google App Engine成为app后台的一个选择,所以结下来的部分我们会学习App Engine如何工作,以及如何使用Cloud Endpoints。

为了创建一个新的包含Cloud Endpoints的Google App Engine模块,你需要从File|New Module...菜单打开New Module对话框,然后选择Google Cloud Module。在模块设置中,你可以修改种类来包含Cloud Endpoints。然后,选择使用这个后台的客户端模块。

图1 Android Studio创建App Engine模块对话框

对Google App Engine和Cloud Endpoints进行透彻的讲解超出了本书的范畴,我们只会讲解Gradle对App Engine模块和客户端app模块的集成。

分析构建文件

这个模块的构建文件非常大,所以我们只关注几个部分,首先是构建脚本依赖:

buildscript {
    dependencies {
        classpath 'com.google.appengine:gradle-appengine-plugin:1.9.18'
    }
}

App Engine插件需要定义在构建脚本的类路径中。我们在之前添加Android插件时已经见到过。设置好之后,我们可以应用App Engine插件和另外两个插件:

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'appengine'

Java插件主要用来为Cloud Endpoints生成Jar文件。WAR插件用来运行和分发整个后台。WAR插件生成一个WAR文件,Java web应用在此分布。最后,App Engine插件添加一系列任务来构建、运行和配置整个后台。

下一个重要的块定义了App Engine模块的依赖:

dependencies {
    appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.18'
    compile 'com.google.appengine:appengine-endpoints:1.9.18'
    compile 'com.google.appengine:appengine-endpoints-deps:1.9.18'
    compile 'javax.servlet:servlet-api:2.5'
}

第一个依赖使用appengineSdk指明模块需要用到的SDK。Cloue Endpoints需要endpoints依赖才能运行。这些只在你使用Cloud Endpoints时才会添加。servlet依赖是每个Google App Engine模块所必需的。

appengine块中设置App Engine特有的配置:

appengine {
    downloadSdk = true

    appcfg {
        oauth2 = true
    }

    endpoints {
        getClientLibsOnBuild = true
        getDiscoveryDocsOnBuild = true
    }
}

downloadSdk属性设置为true,可以很容易地运行本地开发服务器,因为如果SDK不存在的话,它会自动下载SDK。如果你已经在自己的设备上配置好了Google App Engine SDK,你可以将downloadSdk设置为false

appcfg块用来配置App Engine SDK。在一个典型的Google App Engine安装中,你可以在命令行中使用appcfg来手动设置一些配置。使用appcfg块来代替命令行工具,使设置更加便捷,每个构建该模块的人都有同样的配置,从而不必去执行一些额外的命令。

endpoints块包含一些Cloud Endpoints特有的设置。

关于Google App Engine和Cloud Endpoints更详细的说明超出了本书的范畴。如果你想学习更多,可以访问https://cloud.google.com/appengine/docs

在app中使用后台

当你创建一个App Engine模块时,Android Studio会自动在Android app模块的构建文件中添加一个依赖。如下:

dependencies {
    compile project(path: ':backend', configuration: 'android-endpoints')
}

我们已经见过这种语法(在引用Java和Android库时),使用project来定义依赖,并带有两个参数。path参数是默认参数,我们之前就用过,但是没有指明它的名称。一个Google App Engine模块可以有不同种类的输出。你可以使用configuration参数来指明你想要的输出。我们需要App Engine模块生成Cloud Endpoints,所以设置为android-endpoints。在内部,这个配置会运行_appengineEndpointsAndroidArtifact任务。这个任务生成一个JAR文件,包含你可以在Android app模块中使用的类。这个JAR文件不仅包含Could Endpoints中使用的模型,还有API方法。像这样的集成使得多模块项目很好地工作,因为它缩短了开发时间。App Engine模块中的Gradle任务也使运行和部署后台变得更加容易。

自定义任务

App Engine插件添加了很多任务,用的最多的是appengineRunappengineUpdate

appengineRun任务用来启动一个本地开发服务器,在上传到Google App Engine之前,你可以用它来对整个后台进行本地测试。第一次启动这个任务可能会多消耗一点时间,因为Gradle需要下载App Engine SDK(我们之前设置了downloadSdk = true)。你可以使用appengineStop来停止服务器运行。

一旦你要将后台部署到Google App Engine并在生产环境使用它,你可以使用appengineUpdate。这个任务处理部署的所有细节。如果你设置了oauth2 = true,这个任务会打开一个浏览器窗口,你可以登录你的Google账户并拿到一个身份令牌。如果你不想每次部署时都需要登录,你可以用Google账户登录Android Studio并使用IDE部署后台。Android Studio会运行同样的任务,但是它会接管认证。

技巧和最佳实践

有一些技巧使处理多模块工程变得简单,在处理一些模块时,你需要牢记一些事情。了解这些可以使你省时又省心。

从Android Studio中运行模块任务

正如在第二章看到的,Android Studio可以直接运行Gradle任务。当你有多个模块时,Android Studio可以识别它们,并可以分模块预览可用的任务。

图2 Android Studio分模块展示任务

Gradle工具窗口可以很容易的运行模块特有的任务。窗口中并没有为所有模块同时运行任务的选项,所以如果你想这么做,还是需要使用命令行。

加快多模块的构建

在你构建一个多模块工程时,Gradle会串行处理所有的模块。在电脑有多核可用时,我们可以利用并行处理加速构建过程。Gradle已经有这个特性,但是默认没有开启。

如果你想开启并行处理,需要在工程根目录的gradle.properties文件中设置parallel属性:

org.gradle.parallel=true

Gradle会基于CPU的核心数量选择适当的线程数。为了避免同时执行一个模块的两个任务可能产生的问题,一个模块会在一个线程中执行。

并行构建执行是一个孵化特性。这意味着它在积极的发展,某个时候可能会改变。这个特性目前是Gradle的一部分,并被广泛使用。所以,它应该不会突然消失或者改变。

开启并行构建可能会省掉你的大笔时间。为了更有效率的执行,需要确保模块没有耦合。

模块耦合

如第二章所见,可以在顶级构建文件中,使用allprojects为所有的模块定义属性。在多模块工程中,你可以在任何模块中使用allprojects为工程中的所有模块应用属性。Gradle甚至使你可以在一个模块中引用另一个模块的属性。这个强大的特性可以使维护多模块构建变得简单,弊端就是会造成模块耦合。

一旦两个模块需要访问彼此的任务或者属性,则被认为是耦合的。这会造成几个后果。比如,你放弃了可移植性。如果你决定从项目中提取一个库,那么在复制所有项目范围的设置之前,你将无法构建。模块耦合也会影响并行构建。在任何模块使用allprojects块将使并行构建无效。当你在任何模块添加项目范围的属性时要牢记这些。

你可以通过不直接访问其他模块的任务或者属性来避免耦合。如果你需要这么做,你可以使用根模块作为中介,这样模块就会变成和根模块耦合,而不是相互耦合。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容