Gradle for Android-高级Build和自定义
既然你已了解Gradle是如何工作、如何创建task和plugin、如何建立持续集成,你可以称自己为专家了。本章包含了一些我们之前没有提到过的提示和技巧,可以使得使用Gradle构建、开发和部署Android项目更容易。
本章,我们将会学习以下主题:
- 减少APK文件大小
- 加速构建
- 忽略Lint
- 从Gradle中使用Ant
- 高级App部署
减少APK文件大小
APK文件的大小最近几年显著的增长了。对此,一般来说有如下几个原因原因——对于开发者来说,有更多的库可用、增加了更多密度以及app具有更多的功能。
保持APK尽可能的小是个不错的想法。不仅仅因为Google Play对APK文件有50MB的限制,而且更小的APK也意味着用户可以更快的下载和安装,并降低内存占用。
本节,我们将了解可用于压缩APK文件的Gradle build配置文件中的一些属性。
混淆器
ProGuard是一个Java工具,不仅可以压缩,也可以优化、混淆和预验证代码在编译时。它检查app中所有代码路径发现没被使用的代码并删除。ProGuard也重命名类和字段。该过程使得app的内存占用降低,而且使得代码的****更加困难。
Gradle的android plugin中的build type中有个boolean属性叫做minifyEnabled,你需要设置成true启动混淆:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile
('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
当设置minifyEnabled为true时,在build过程中,proguardRelease task会被执行并驱动混淆。
启动混淆后,最好能重新测试整个应用,因为可能会移除一些仍然需要的代码。这个问题已经使得很多开发者厌倦混淆了。为了解决该问题,可以定义混淆规则从已移除或混淆的代码中把特定的类排除在外。proguardFiles属性被用于定义包含了混淆规则的文件。例如,为了保持一个类,可以添加如下简单规则:
-keep public class <MyClass>
- 1
- 1
getDefaultProguardFile(‘proguard-android.txt’)方法从proguard-android.txt文件中取用默认的混淆设置,该文件是SDK所带,在tools/proguard目录下。proguard-rules.pro文件默认被AS添加到新的Android模块,所以你可以在该文件中针对特定模块添加规则。
混淆规则对你所构建的每个app或library都是不同的,所以本书中我们不会探究过多细节。如果想了解更多关于Proguard和Proguard rule,可以查看官方文档
(http://developer.android.com/tools/help/proguard.html)
除了压缩代码之外,压缩被使用的资源也是个不错想法。
压缩资源
Gradle和Android plugin在构建阶段可以摆脱所有无用的资源,当app被打包时。如果有老旧的忘了移除的资源的话,这将非常有用。另一个用处就是当导入包含大量资源的库,而你只使用它们的一小部分时。你可以通过启动资源压缩修复该问题。关于资源压缩有两种方式,自动和手动。
自动压缩
最容易的方式就是在build中配置shrinkResources属性。如果设置该属性为true,Android build工具将自动地尝试判定哪些资源没被使用,并不添加到APK中。
使用该特性有个要求,那就是ProGuard也要启动。这是由于资源压缩的工作方式,因为Android build工具无法计算哪些资源未被使用直到引用这些资源的代码已被移出。
以下片段展示了如何在一个确定的build type中配置自动化资源压缩:
android {
buildTypes {
release {
minifyEnabled = true
shrinkResources = true
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
如果想详细的看到自动化资源压缩后APK变得多小,可以运行shrinkReleaseResource task。该task将打印它多大程度的减少了安装包的大小:
:app:shrinkReleaseResources
Removed unused resources: Binary resource data reduced from 433KB
to 354KB: Removed 18%
- 1
- 2
- 3
- 1
- 2
- 3
通过添加–info标签到build命令中,可以得到被从APK中剥除的资源的概览:
$ gradlew clean assembleRelease --info
- 1
- 1
当使用flag时,Gradle打印大量关于build进程的额外信息,包括在最终的build输出中没有被添加的每个资源。
自动化资源压缩的一个问题就是可能移除过多资源。尤其被动态使用的资源可能意外地被剥除。为了防止该问题,可以在你放到res/raw/.目录下的keep.xml文件中定义异常。简单的keep.xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/keep_me,@layout/also_used_*"/>
- 1
- 2
- 3
- 1
- 2
- 3
keep.xml文件自身也会被从最终的结果中剥除。
手动压缩
一个剥除资源的更缓和的方式就是针对特定分辨率摆出确定的语言文件或图片。一些库,例如Google Play Service,包含了大量的语言。如果你的app仅支持一到两种语言,没必要把所有的语言文件都加入到最终的APK中。可以使用resConfigs属性配置你想保留的资源,然后剩下的将会被舍弃。
如果仅想保留英语,西班牙语和荷兰语strings,可以如下使用resConfigs:
android {
defaultConfig {
resConfigs "en", "da", "nl"
}
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
对于分辨率篮子亦是如此,如下:
android {
defaultConfig {
resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
组合语言和分辨率是可能的。事实上,每种资源类型都可被控制使用该属性。
如果建立混淆很困难或仅想摆脱你的app不支持的语言或分辨率资源,使用resConfigs是个不错的资源压缩的注意。
加速构建
很多开始使用Gradle的Android开发者都抱怨超长的编译时间。build较之于它们使用的Ant,要花费更长时间。因为Gradle在每次执行一个task都要经历的build生命周期中有三个阶段。这使得整个进程非常可配置,但也非常缓慢。幸运的是,有几种可以加速Gradle构建的方式。
Gradle属性
一个加速Gradle构建的方式就是修改一些默认配置。我们在第五章的并行build执行已经提到,但还有一些你可以调整的设置。
简单扼要的说,可以启动并行构建通过在根目录下的gradle.proerties文件中设置一个属性。你所需要做的就是添加如下:
org.gradle.parallel=true
- 1
- 1
另一个方式就是启动Gradle daemon,它启动了一个后台进程当第一次运行build时。任何后来的build都将重用该后台进程,因此缩减了启动成本。该进程只要使用Gradle将会一直存在,并在闲置3小时候将会中止。使用daemon尤其有用当你在短时间多次使用Gradle时。可以在gradle.properties文件中如下启动gradle.properties。
org.gradle.daemon=true
- 1
- 1
在AS中,Gradle daemon默认可用。这意味着在IDE内部第一次build之后,接下来的build将会更快一些。如果从命令行接口中build,然而,Gradle daemon是不可用的,除非在属性中使之可用。
为了加速编译,可以设置Java虚拟机中的参数。有个Gradle属性叫做jvmargs可使得对JVM的内存分配池设置不同的值。Xms和Xmx两个属性可直接影响到build速度。Xms参数用于设置初始化的被使用的内存数量,Xmx参数用于设置最大值。可以在gradle.properties文件中手动设置这些值,如下:
org.gradle.jvmargs=-Xms256m -Xmx1024m
- 1
- 1
你需要设置想要的数量和单位,千字节为k,百万字节为m,千兆自己为g。默认情况下,最大的内存分配(Xmx)是256MB,开始的内存(Xms)不被设置。可选的设置依赖于你的电脑的能力。
最后一个可以配置影响build速度的属性就是org.gradle.configureondemand。这个属性格外有用在具有好几个模块的项目中,因为它尝试显示配置阶段的花费,通过忽略被执行的task不要求的模块。如果设置该属性为true,在运行配置阶段之前,gradle将计算哪些模块的配置改变和哪些没有改变。该功能不会非常有用,如果项目中只有个Android app和一个library。如果有大量松耦合的模块,该特性将节约大量的build时间。
系统范围Gradle属性
如果想应用这些属性到所有基于Gradle的项目中,可以在主目录的.gradle目录下创建一个gradle.properties文件。windows中,完整的路径是%UserProfile%.gradle,Linux和Mac OS X中是~/.gradle。在主目录下而非项目层设置这些属性是个不错的做法。原因在于你通常想保持build server上内存销毁较低,而且build时间是相对次要的。
Android Studio
可以改变用来加速编译进程的Gradle属性也可以在AS的settings中配置。为了发现编译器设置,打开Settings对话框,然后导向到Build、Executiong、Deployment | Compiler。在屏幕上,可以找到并行build、JVM选项、命令配置等设置。这些设置仅展示了基于Gradle的Android模块,如下截图:
从AS中配置这些设置要比手动的在build配置文件中配置更加容易,而且设置对话框也使得更容易发现影响build进程的属性。
性能分析
如果想找到build的哪部分拖慢了build进程,可以分析整个build进程。可以通过添加–profile标签实现无论何时执行一个Gradle task时。当你提供该flag时,Gradle创建一个性能分析报告,它可以告诉你build进程的哪部分消耗了最多的时间。一旦知道了瓶颈所在,就可以做必要的改变了。报告以HTML的格式保存到模块的build/reports/profile目录下。
下面是在一个多模块的项目中执行build task后生成的报告:
性能分析报告展示了执行task时,每个阶段的时间花费。summary下就是Gradle在配置阶段花费了多少时间的概述。Dependency Resolution端显示了每个模块花费了多长时间在解决依赖上。最后,Task Execution端包含了一个非常详细的task执行概述。这个概览博啊哈每个task的时间,并按执行时间从高到低排序。
Jack和Jill
如果愿意使用实验工具,可以启动Jack和Jill加速构建。Jack(Java Android Compiler Kit)是一个新的Android build工具链。可以把Java源码直接编译成Android Dalvik字节码(dex)。它有自己的.jack库格式而且也关注打包和压缩。Jill(Jack Intermediate Library Linker)是一个把.aar和.jar文件转换成.jack库的工具。这些工具目前仍然处于试验阶段,但它们已经改善了build时间和简化了Android build进程。不建议在生产版本中使用Jack和Jill,但是它们是可得的以便你可以测试它们。
为了能够使用Jack和Jill,需要使用build tools版本21.1.1或更高,而且Android plugin版本1.0.0或更高。使得Jack和Jill就像在defaultConfig块中设置一个属性一样容易。
android {
buildToolsRevision '22.0.1'
defaultConfig {
useJack = true
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
也可以在特定的build type或product flavor中使用Jack或Jill。这种方式,可以继续使用regular build工具链,并且另外有个experimental build:
android {
productFlavors {
regular {
useJack = false
}
experimental {
useJack = true
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
只要设置userJack为true,minification和obfscation将不会再通过ProGuard,但仍然可以使用ProGuard规则语法指明确定的规则和异常。使用我们之前提到过的相同的proguardFiles方法,当讨论到ProGuard时。
忽略Lint
使用Gradle执行一个release build时,在代码中将会执行Lint检查。Lint是一个静态代码分析工具,可以标记layout或Java代码中潜在的bug。一些情况下,甚至可能阻塞build进程。如果之前没有在项目中使用过Lint,并想迁移到Gradle中,Lint可能会出现很多错误。为了至少使得build工作,可以配置Gradle忽略Lint错误并阻止它们放弃build,通过使得abortOnError不可用。这仅是一个临时解决方案,因为忽略Lint错误可以导致如缺失翻译的问题,可以导致app崩溃。为了避免Lint阻塞build进程,使得abortOnError不可用,如下:
android {
lintOptions {
abortOnError false
}
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
临时的使得Lint abort不可用可以使移植一个已存在的build进程到Gradle中更容易。另外一个使得过渡期更平缓的方式就是直接从Gradle中执行Ant task。
从Gradle中使用Ant
如果已经投入过多时间在使用Ant建立build上,切换到Gradle上听起来有些吓人。如果这样的话,Gradle不能仅仅执行Ant task,也要扩展它们。这意味着你可以以更小的步骤从Ant迁移到Gradle中,代替花费好几天转换整个的build配置。
Gradle使用Groovy的AntBuilder用于Ant的集成。AntBuilder使得你能执行任何标准的Ant task和整个的Ant build。它也使得在Gradle build配置中定义Ant属性成为可能。
从Gradle运行Ant task
从Gradle中运行一个标准的Ant task是很简单的。只需要预先考虑用ant. 给task命名而且所有事情立即可用。例如,创建一个archive,可使用如下task:
task archive << {
ant.echo 'Ant is archiving...'
ant.zip(destfile: 'archive.zip') {
fileset(dir: 'zipme')
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
该task在Gradle中被定义,但利用了两个Ant task echo和zip。
当然你应该总是考虑Gradle提到标准的Ant task。为了创建一个如之前的例子的archive,可定义一个Gradle task为你处理:
task gradleArchive(type:Zip) << {
from 'zipme/'
archiveName 'grarchive.zip'
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
这个task对于基于Gradle的archive是更加简明和容易理解的。因为它不需要通过AntBuilder,也是略微更快的比使用Ant task。
导入一个完整的Ant脚本
如果已经创建一个Ant脚本构建app,可以使用ant.importBuild导入整个build配置。所有的Ant目标都会自动转换成可以通过它们的原始名称访问的Gradle task。
例如,采用如下Ant构建文件:
<project>
<target name="hello">
<echo>Hello, Ant</echo>
</target>
</project>
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
可以把这个构建文件导入到你的Gradle build,如下:
ant.importBuild 'build.xml'
- 1
- 1
这将暴露hello task给Gradle build,所以你可以执行它就像一个常规的Gradle task,并将打印”Hello,Ant:”
$ gradlew hello
:hello
[ant:echo] Hello, Ant
- 1
- 2
- 3
- 1
- 2
- 3
因为Ant task被转换成Gradle task,也可以使用doFirst和doLast块或<<快捷方式扩展它。例如,可以打印另一行道控制台:
hello << {
println 'Hello, Ant. It\'s me, Gradle'
}
- 1
- 2
- 3
- 1
- 2
- 3
也可依赖于从Ant导入的task,就像平时一样。例如,如果想创建一个依赖于hello task的新的task,可以简单得如下做法:
task hi(dependsOn: hello) << {
println 'Hi!'
}
- 1
- 2
- 3
- 1
- 2
- 3
使用dependsOn确保hello task被触发的那个执行hi task时:
$ gradlew intro
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
:hi
Hi!
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
如果需要的话,甚至可以使一个Ant task依赖Gradle task。为了实现,需要添加depends属性到build.xml文件中的task,如下:
<target name="hi" depends="intro">
<echo>Hi</echo>
</target>
- 1
- 2
- 3
- 1
- 2
- 3
如果有个大的Ant build文件,而且你想确保没有task名称重叠,可以重命名所有的Ant task在导入的时候,使用如下代码片段:
ant.importBuild('build.xml') { antTargetName ->
'ant-' + antTargetName
}
- 1
- 2
- 3
- 1
- 2
- 3
如果决定重命名所有的Ant task,记住如果有Ant task依赖Gradle task,Gradle task也需要添加前缀。否则的话,Gradle无法找到它并抛出UnknownTaskException。
属性
Gradle和Ant不能不能仅共享task,也要在Gradle中定义可用于Ant build文件的属性。考虑这个Ant target,它打印了一个叫做version的属性:
<target name="appVersion">
<echo>${version}</echo>
</target>
- 1
- 2
- 3
- 1
- 2
- 3
可以在Gradle build配置中通过预先考虑使用ant.命名属性定义version属性,就像task一样。这是定义一个Ant属性的最快捷的方式:
ant.version = '1.0'
- 1
- 1
Groovy于此隐藏了大量实现。如果完成属性属性定义,如下所观:
ant.properties['version'] = '1.0'
- 1
- 1
执行version task将会做如你所期待的,即打印1.0到控制台:
$ gradlew appVersion
:appVersion
[ant:echo] 1.0
- 1
- 2
- 3
- 1
- 2
- 3
在Gradle中有很深的Ant集成经验使得从基于Ant build过滤到Gradle很容易,而且你可以很容易的一小步就是想。
高级app部署
在第四章,我们了解了使用build type和product flavor创建相同app不同版本的集中方式。然而,有些情况下,使用更特定的技术可能更容易,例如APK分离。
分离APK
build变体可被看作独立实体,每一个都可以有自己的代码、资源和manifest文件。APK分离,换句话说,仅影响到app的打包。编译、压缩、混淆等仍是共享。该机制允许基于分辨率或应用二进制接口(ABI)分离APK。
可以通过在android配置块中定义一个splits块定配置分离。为了配置分辨率分离,在splits块中创建一个density块。如果想要建立ABI分离,使用一个abi块。
如果启用分辨率分离,Gradle为每种分辨率创建一个APK。你可以手动剔除不需要的分辨率,以便加速build进程。下例展示了如何启用分辨率分离和剔除低分辨率设备:
android {
splits {
density {
enable true
exclude 'ldpi', 'mdpi'
compatibleScreens 'normal', 'large', 'xlarge'
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果仅支持几种分辨率,可以使用include创建一个分辨率白名单。为了使用include,首先需要使用reset()属性,它把已添加的分辨率列表置成空字符串。
上面片段中的compatibleScreens属性是可选的,而且在manifest文件中注入了匹配的代码。例子中的配置对于app来说是针对支持正常到大屏幕的,剔除了低分辨率的设备。
基于ABI分离APK也是相同方式,而且所有的属性都和分辨率分离相同,除了compatibleScreens外。ABI分离没有对屏幕大小做任何事情,所以没有叫做compatibleScreens的属性。
配置完分辨率分离后执行build的结果就是Gradle创建一个通用的APK和好几个特定分辨率APK。这意味着你将以APK结合结束。如下:
app-hdpi-release.apk
app-universal-release.apk
app-xhdpi-release.apk
app-xxhdpi-release.apk
app-xxxhdpi-release.apk
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
使用APK分离时有个警告。如果想push多个APK到Google Play,将需要确保每个APK都有不同的版本号。这意味着每个分离都应该有个唯一的版本号。幸运的是,目前为止,在Gradle中能够做到通过了解applicationVariants属性。
以下片段直接源于Android Plugin for Gradle文档,并展示了如何为每个APK生产不同的版本号:
ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000000 +android.defaultConfig.versionCode
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
该片段检测哪个ABI是用到了build变体,然后应用乘法到版本号确保每个变体都有个唯一的版本号。
总结
阅读完本章,知道了如何减少build输出的大小和如何加速build通过配置Gradle和JVM。大迁移项目不该再令你恐惧了。你也学习了一些使得开发和部署更容易的技巧。
于是,已经到了书的末尾!既然已经了解了Gradle的发展潜力,可以调整和自定义Android项目的build进程,不需要做任何手动操作,处理剔除task之外。可以配置build变体,管理依赖和配置多模块项目。Gradle DSL对你很用意义因为你理解了Groovy语法,而且很轻松的挂钩Android plugin。你甚至可以创建task或plugin并共享它们,帮助他人自动化它们的构建。现在你需要做的就是应用你的新技能了!