Gradle for Android(二) 基本的构建定制

上一章我们学习了Gralde的使用,创建和转换Android工程。本章我们将深入了解构建文件,学习一些有用的tasks,探索Gradle和Android插件。

本章我们关注以下内容:

  • 理解Gradle文件
  • 学习使用构建任务
  • 自定义构建

理解Gradle文件

在Android Studio中创建新工程的时候,默认生成3个Gradle文件:

MyApp
├── build.gradle
├── settings.gradle
└── app
    └── build.gradle

这三个文件各有用途,我们在以下面进行探索。

settings.gradle

新的工程只包含一个app模块,settings.gradle

include ':app'

settings.gradle文件在initialization期间执行,定义了构建过程需要包含的模块。上述代码表示需要包含app模块。对于单模块的工程,settings.gradle文件不是必需的。多模块的工程必须有该文件,否则Gradle不知道需要包含哪些模块。

Gradle会为每个settings.gradle文件创建一个Settings对象,并触发该对象相应的方法。

顶级build.gradle

在顶级build.gradle中,你可以对工程中所有的模块进行配置,它默认包含两个代码块:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

buildscript用来配置当前的构建。

repositories配置JCenter作为仓库。我们在app或者library中可以使用JCenter这个依赖源提供的可供下载的各种库。JCenter是一个广泛使用的Maven仓库。

dependencies代码块用来配置构建过程需要的依赖。这意味着你不应该在顶级配置文件中包含你的applications或者libraries需要的依赖。Gradle Android插件是仅有的默认包含的插件。这对每个Android模块都是必须的,因为该插件是运行Android相关任务的必要插件。

allprojects代码块可以用来定义需要应用到所有模块中的属性。你还可以在该代码块中定义任务。这些任务可以用于所有的模块。

一旦你使用了allprojects,modules就和project耦合到一起了。这意味着你不能脱离project的build.gradle文件单独构建每个模块。起初看起来不是问题,但随后你可能会把内部的library分离到单独的project中,这时你就需要重构构建文件了。

模块的build.gradle

模块级别的build.gradle文件包含应用于Android app模块的配置。它也可以覆写来自顶级build.gradle文件的配置。模块级别的build.gradle文件是这个样子的:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.gradleforandroid.gettingstarted"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
}

下面我们来分析3个主要的块

Plugin

第一行引入了Android应用插件,它在顶级构建文件中被配置为一个依赖。Android插件由Google的Android Tools团队开发和维护,提供了用于构建、测试和打包Android应用和库的所有任务。

Android

android代码块包含了整个Android特有的配置,由Android插件提供。必需的属性仅仅是compileSdkVersionbuildToolsVersion:

  • compileSdkVersion是Android的API版本
  • buildToolsVersion是build tools的版本

build tools包含命令行工具,比如aapt,zipalign,dx和renderscript。你可以通过SDK Manager来下载build tools。

defaultConfig代码块配置了app的核心属性。该块中的属性覆盖了AndroidManifest.xml文件中相应的属性:

defaultConfig {
    applicationId "com.gradleforandroid.gettingstarted"
    minSdkVersion 14
    targetSdkVersion 22
    versionCode 1
    versionName "1.0"
}

applicationId覆写了manifest文件中的package(包名),但是两者又有些不同。在Gradle作为默认的Android构建系统之前,AndroidManifest.xml中的包名有两个功能:

  • 作为app的唯一标识
  • 作为R.java文件的包名

Gradle利用构建变量使创建不同版本的app变得简单了许多。比如你可以非常简单的创建一个free版本和一个paid版本。这两个版本需要有不同的app标识,这样它们才能同时存在于Google应用商店,同时被安装到手机上。然而源代码和生成的R.java文件必须时刻保持同样的包名。否则你所有的源代码都需要改变。这就是Android Tools团队将包名这两个功能分离开的原因。manifest文件中的package继续作为R.java文件的包名,而设备和Google Play所需的app标识则由applicationId替代。在我们体验build types之后,applicationId将更加有趣。

defaultConfig接下来的两个属性是minSdkVersiontargetSdkVersion。这两个属性应该比较熟悉了,它们定义在manifest文件的<uses-sdk>元素中。minSdkVersion用来配置运行app的最低的API。targetSdkVersion告知测试app需要的特定Android版本,这样操作系统不需要启用任何正向兼容性行为。这与我们之前看到的compileSdkVersion无关。

versionCodeversionName也与manifest中的一致。

构建文件的所有值都会替换manifest文件的值。所以如果你在build.gradle文件定义了这些值,就没有必要在manifest文件再次定义。只有当构建文件没有定义这些值的时候,manifest中的值才会起作用。

buildTypes块是你定义如何构建和打包不同app构建类型的地方。我们将在第四章详细学习构建类型。

dependencies

dependencies块是Gradle标准配置的一部分(这就是它为什么放在了android块的外部的原因),定义了app或者library所需的全部依赖。Android app默认将libs文件夹作为全部JAR依赖文件的目录。取决于你在新工程向导中勾选的选项,它也可能依赖AppCompat库。我们将在第三章讨论依赖。

开始学习任务

为了得知工程中有多少可用任务,你可以运行gradlew tasks命令。在一个新创建的工程中,这些可用任务包含Android tasks,build tasks,build setup tasks,help tasks,install tasks,verification tasks和其他tasks。如果你还想看到任务间的依赖关系,可以运行gradlew tasks --all命令。演示一个任务也是可以的,它可以打印出执行一项任务所需的所有步骤。演示不会真正去执行这些步骤,它是一种查看运行特定任务结果的安全方式。你可以通过添加参数-m或者--dry-run来演示任务。

基础任务

Gradle Android插件使用了Java基础插件,Java基础插件又使用了基础插件。这些添加了标准生命周期任务和一些通用属性。基础插件定义了assembleclean任务,Java基础插件定义了checkbuild任务。这些任务并没有在基础插件中实现,不包含任何的actions;它们用来定义添加执行作业的实际tasks的规则。
这些任务的规则有:

  • assemble收集工程的输出
  • clean清理工程的输出
  • check运行所有的检查,通常是单元测试和设备测试
  • build运行assemblecheck

Java基础插件也添加了source sets的概念。Android插件建立在这些约定之上,从而暴露了那些有经验的Gradle用户习惯于看到的任务。在这些基础任务之上,Android插件也添加了许多Android特有的任务。

Android任务

Android插件扩展、实现了基础任务。下面是在Android环境中这些任务的功能:

  • assemble为每一个build type 创建一个APK
  • clean删除所有构建结果,包括APK文件
  • check运行Lint检查,并在查到问题时停止构建
  • build运行assemble和check

assemble任务默认依赖于assembleDebugassembleRelease,在你添加更多build type时,它会依赖于更多任务。这意味着运行assembe将构建每一个build type。

除了扩展这些任务,Android插件也添加了一些任务。下面是最重要的一些任务:

  • connectedCheck在连接的真机或者虚拟器上运行测试
  • deviceCheck为其他插件在远程设备运行测试提供的接口
  • installDebug、installRelease在设备或者虚拟器上安装特定版本
  • uninstall每一个install任务有对应的uninstall任务

build任务依赖于check,但不依赖connectedCheckdeviceCheck。这是为了确保常规检查不要求有连接的设备或者运行的模拟器。运行check任务会生成一个Lint报告,包含所有的warnings和errors,并有相应的解释和文档链接。这个报告在app/build/outputs目录,名为lint-results.html

在你assemble release时,Lint会检查引起app崩溃的问题。在它发现问题时,它将停止构建,并在命令行中打印错误。Lint也会在app/build/outputs目录生成一个名为lint-results-release-fatal.html的报告。如果你有多个问题,浏览HTML报告比查看命令行更加方便。报告提供的链接也很有用,它们会指向问题的细节。

Inside Android Studio

使用gradle窗口查看任务。本节略过。

自定义构建

Android Studio的构建。

操作manifest文件的条目

我们前面已经见过在构建文件中配置applicationId, minSdkVersion,targetSdkVersion,versionCode和versionName。你还可以操作一些其他的属性:

  • testApplicationId设备测试APK的applicationId
  • testInstrumentationRunner运行测试所用到的JUnit测试器的名称(见第六章,运行测试)
  • signingConfig(见第四章,创建构建变体)
  • proguardFile proguardFiles(见第九章,高级自定义构建)

Inside Android Studio

使用窗口配置工程,本节略过。

BuildConfig和resources

自SDK tools R17以来,build tools便会生成一个BuildConfig类,它包含了根据build type设置的DEBUG常量。当你只想在debug下运行一些代码时,这将会很有用,比如打印日志等。Gradle允许你扩展该文件,这样你就可以在debug和release版本拥有不同的常量了。

这些常量在开关某些特性或者设置服务器地址时,非常有用,比如:

android {
    buildTypes {
        debug {
            buildConfigField "String", "API_URL","\"http://test.example.com/api\""
            buildConfigField "boolean", "LOG_HTTP_CALLS", "true"}
        release {
            buildConfigField "String", "API_URL","\"http://example.com/api\""
            buildConfigField "boolean", "LOG_HTTP_CALLS", "false"
        }
    }
}

\"中的转义字符是必需的,这样才能生成实际的字符串。添加了buildConfigField之后,你就可以在Java代码中使用BuildConfig.API_URLBuildConfig.LOG_HTTP这两个值了。

最近,Android Tools团队还添加了配置resources资源的特性:

android {
    buildTypes {
        debug {
            resValue "string", "app_name", "Example DEBUG"
        }
        release {
            resValue "string", "app_name", "Example"
        }
    }
}

这里不需要为Example DEBUGExample两个值添加\"转义的双引号,因为资源值默认用value=""包装。

工程范围的设置

如果你的工程中有多个模块,为所有模块添加统一配置而非手动去配置每一个模块会显得非常有用。我们已经见过如何在顶级构建文件中使用allprojects块来定义仓库,你可以用同样的策略来应用Android特有的设置:

allprojects {
    apply plugin: 'com.android.application'
    android {
        compileSdkVersion 22
        buildToolsVersion "22.0.1"
    }
}

这只在你的所有模块都是Android工程的时候才会起作用,因为你需要应用Android插件来进行Android特有的设置。一个更好的方法是在顶级构建文件中定义相应的值,然后在每个模块中引用这些值。Gradle可以在Project对象中添加额外的属性。这意味着每个build.gradle文件都可以定义额外的属性,这些属性在ext块中。

你可以在顶级构建文件中使用ext块:

ext {
    compileSdkVersion = 22
    buildToolsVersion = "22.0.1"
}

可以在模块级构建文件中通过rootProject调用这些属性:

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
}

工程属性

上例中的ext块是定义额外属性的一种方式。你可以利用属性自定义构建过程,我们将在第七章讲述如何利用它们来自定义任务。有几种方式可以定义属性,这里我们着重介绍如下三种:

  • ext
  • gradle.properties文件
  • -P命令行参数

下面这个build.gradle文件包含了这三种方式:

ext {
    local = 'Hello from build.gradle'
}

task printProperties << {
    println local // Local extra property
    println propertiesFile // Property from file
    if (project.hasProperty('cmd')) {
        println cmd // Command line property
    }
}

下面是相应的gradle.properties文件:

propertiesFile = Hello from gradle.properties

本例中我们创建了一个新任务。我们将在第七章详解任务语法

在命令行中运行printProperties任务,得到如下结果:

$ gradlew printProperties -P cmd='Hello from the command line'
:printProperties
Hello from build.gradle
Hello from gradle.properties
Hello from the command line

有了自定义属性,修改构建配置就和修改单个属性一样简单。

在顶级构建文件和模块级构建文件中定义属性都是可以的。模块级构建文件中的同名属性会覆盖顶级构建文件的属性

默认任务

如果你只是运行Gradle,并不明确指定某个任务,它将会打印一些如何使用Gradle的信息。这是因为help任务是Gradle的默认任务。你也可以重写默认任务,指定一个或者多个任务为默认任务,这样就可以只运行Gradle而不指定具体任务了。

为了指定默认任务,你需要在顶级构建文件中添加如下代码:

defaultTasks 'clean', 'assembleDebug'

现在当你运行Gradle Wrapper而不指定具体任务时,将会运行cleanassembleDebug任务。你也可以输入如下代码查看当前的默认任务:

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

推荐阅读更多精彩内容