这本书绝对是Gradle入门的最佳指导手册。在网上可以找到英文版的,但是没有中文版的。然后中文版的据说很薄但是很贵,所以就自己翻译了一下。
全书一共9章,因为前7章有位大神已经翻译了,在此放链接 前7章翻译,第8章涉及到的工具没有用过,跳过,所以直接翻译的第9章。大神翻译的比较简练,我有时间也会都翻译一遍的。
第9章 定制高级构建
以上几章已经告诉我们Gradle是如何工作的,如何创建自己的task和plugin,如何进行测试,如何建立连续继承,学习之后我们都可以称自己为Gradle专家了。本章内容包含了之前几章没有提过的一些建议,方便我们使用gradle构建、开发和配置Android工程。
在本章,我们将会阐述以下话题:
l 减小APK文件的大小
l 加速build过程
l 忽略Lint
l 在gradle中使用ant
l App配置进阶
减小APK文件大小
最近几年,APK文件的大小急剧上升,这是由几个原因造成的:对于Android开发者而言,可利用的library越来越多,应用的总体功能也越来越复杂。
尽量保持APK文件的大小是很好的做法。不仅仅因为在Google Play上限制APK文件大小在50MB,还因为更小的APK文件意味着用户能够下载安装的更快,也能减小内存。在本段,将带领大家学习几个使用Gradle构建配置文件的特性来压缩APK文件。
ProGuard
ProGuard是一个Java工具,不仅能够用来减小apk大小,还能够优化、混淆,以及在编译的时候验证你的代码、它会遍历你app工程中的所有路径来查询哪些代码没有使用,然后删除掉。ProGuard还会重命名你的类和域名。这一过程能够减少应用内存,是代码更难反编译。
Build.gradle文件中有一个属性,称为minifyEnabled,如果你想使用ProGuard,你需要设置为true。
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile ('proguard-android.txt'),'proguard-rules.pro'
}
}
}
当你把minifyEnabled设置为true的时候,proguardRelease 任务会执行,并且在构建的过程中调用ProGuard。
在设置ProGuard可用之后,最好重新测试一下整个应用,因为可能会移除一些你仍然在使用的代码、这也是造成很多开发者厌烦ProGuard的原因。为了解决这个问题,你可以自定义一些规则,保证某些类不被移除活混淆。proguardFiles property被用来定义ProGuard 规则。比如,如果我想保持一个类,我会定义如下规则:
-keep public class<MyClass>
getDefaultProguardFile 方法从proguard-android.txt文件中获得默认ProGuard设置,该文件与Android SDK在tools/proguard文件夹一起出现。默认情况下,Android Studio会将 proguard-rules.pro文件添加到新的Android Module中,因此你可以把对应的规则只添加到module相应的文件中。
Tips:对于不同的app和library,proGuard规则有多不同。因为本书当中不涉及太多细节,如果你想知道更多,请查阅官方Android ProGuard 文件:http://developer.android.com/tools/help/proguard.html.
除了清理Java代码。ProGuard还能够清理使用的资源。
清理资源
在app打包时,Gradle和利用Gradle的插件,能够在build的过程中删除所有的未使用的资源。这在你忘记删除旧的资源的时候很有用。更一个使用的案例是当你引入一个有很多资源文件的library时,你可以只用其中的一部分。只要是打开资源减少这个功能你就能够修复这些问题。有两种方法可以实现资源清理:自动和手动。
自动清理
最简单的方式是在你的build文件中配置shrinkResources属性。当你把属性设置为true之后,Android的build工具就会自动决定哪些资源没有用,不需要添加到APK文件中。
想使用这个属性有一个前提条件,就是你也需要设置ProGuard为true。这是源于资源清理的工作原理,只有当调用这些资源的代码被清除之后,才能够清除这些没有使用的资源文件。
下面的代码片段显示如何设置自动清理。
android {
buildTypes {
release {
minifyEnabled = true
shrinkResources = true
}
}
}
如果你想看自己的APK在自动清理后缩小多少,你可以执行shrinkReleaseResources任务。这个任务会打印出它已经缩小的package的大小:
:app:shrinkReleaseResources
Removedunused resources: Binary resource data reduced from 433KB to 354KB: Removed 18%
通过添加 –info标记到你的build 命令行,你还能够得到详细的信息查看是哪些资源被清除:
$ gradlewclean assembleRelease --info
当你用这个标记的时候,gradle会打印出关于build过程的很多额外信息,包括每个资源是否包括在最终的build输出当中。
自动清理存在的一个问题是,这个过程可能会移走太多的而资源,特别是动态调用的一些资源可能会被误删。为了防止这种状况,你可以定义一个keep.xml文件,放在res/raw/。一个简单的keep.xml文件定义如下:
<?xml version="1.0" encoding="utf-8"?>
<resourcesxmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/keep_me,@layout/also_used_*"/>
当然,在最终的结果当中,这个keep.xml文件也会被删除。
手动清理
手动清理是一个稍微更温和的剔除资源文件的方法,比如只删除某个特定的语言的文件或者是特定分辨率的图片。一个library,比如Google Play Service ,包含了很多语言。如果你的app只支持一两种语言,把所有的语言文件放到最终的apk里是不合适的。你可以使用resConfigs 属性来配置你想保持的资源,然后把其余的提出。
如果你想保持英语、丹麦语和荷兰 string,你可以如下使用resConfig:
android {
defaultConfig {
resConfigs "en", "da", "nl"
}
}
对于图片的分辨率文件夹,你可以如下定义:
android {
defaultConfig {
resConfigs "hdpi", "xhdpi","xxhdpi", "xxxhdpi"
}
}
甚至把语言和分辨率联合起来都可以。事实上,每种类型的资源都可以限制使用这个属性。
如果你使用ProGuard的时候比较困难,或者你只是想删除你的应用无需支持的语言和图片资源,使用resConfig是一个开始资源清理的好方法。
加速构建
许多Android开发者在开始使用gradle的时候抱怨其过长的编译时间。相比于Ant,因为gradle在build生命周期有三个阶段,因此花费时间比较长。你执行的每一个任务都需要经过这三个生命周期。这让整个过程可配置,但是相当慢。幸运的是,有一些方法可以加速Gradle的构建。
Gradle Properties
一种加速Gradle构建的方法是改变一些默认设置。我们在第5章已经提到了并行构建过程,管理多模块构建。这里还有一些其他的设置可以配置。使用并行构建的方法,是在根目录的gradle.properties文件中添加:
org.gradle.parallel=true
另一个简单的方法是使用Gradle后台程序,即当你第一次构建的时候允许开启后台线程。其余随后的构建都会重复利用这个线程,以此来缩短时间。这个线程会在你使用gradle的整个期间存活,会在闲置三小时后关闭。当你在短时间内多次使用Gradle时,后台进程会比较有用。设置如下:
org.gradle.daemon=true
在Android Studio中,Gradle后台进程是默认开启的。这就意味着,在IDE中,你第一次构建之后,下一次构建会快一些。但是如果你是使用命令行接口开始构建的,gradle后台进程默认是关闭的,除非你在properties中进行了设置。
为了加速编译的过程,你可以扩大JVN虚拟机的内存。在Gradle property中有称为jvmargs的参数,你可以设置JVM的内存配置。另外两个直接影响你构建速度的参数是Xms和Xmx。Xms参数是用来设置可供使用的最初的内存大小,Xmx参数是用来设置最大值。你可以在gradle.properties文件中手动设置这些参数:
org.gradle.jvmargs=-Xms256m-Xmx1024m
你需要设置期待的数值和单位,k代表千字节,m代表兆字节,g代表千兆字节。默认情况下,Xmx为256MB,Xms不做设置。该设置取决于你电脑的性能。
最后一个要讲的你可以设置的能够影响build速度的参数是org.gradle.configureondemand.当你的项目包含多个module时,这个属性会特别有用,因为他可以跳过不需要执行该项任务的模块。如果你设置这个属性为true,在配置阶段,Gradle会识别出哪些module的配置更改了,哪些没有。如果你的工程中只包含一个library,这项设置可能不太有用。如果你的项目包含很多module,而且是松耦合,这个特性会帮你节省很多构建时间。
Tips:
系统级别的 Gradle 属性:
如果你想把这些系统级别的属性设置到你所有的基于gradle的工程中,你可以在.gradle文件夹的根目录下创建一个gradle.properties文件。在Windows系统中,该目录的全路径是%UserProfile%\.gradle ,在Linux和Mac OSX 中是~/.gradle。相比于在项目级别进行设置,在你的根目录下设置这些属性更适合。因为在实际当中,你想降低内存,而构建时间反而没那么重要。
AndroidStudio
Gradle的设置也可以在Android Studio的设置中配置。为了找到编译设置,打开Setting 对话框,展开Build,Execution,Deployment—Compiler.展开之后,你可以找到设置parallel builds(并行构建)、JVM options(JVM选项), configure on demand(后台进程配置)等。这些设置只会在基于Gradle的Android module出现。
在AS中配置这些参数比手动在文件中配置简单。而且在设置对话框中你能够很明显的看出哪些属性影响构建过程。
性能报告
如果你想找出是构建过程中的哪一部分减慢了构建进程,你可以对整个过程进行性能分析。当你执行Gradle 任务的时候,你可以添加—profile 标识,这个时候Gradle会创建一个性能报告,告诉你构建进行的哪一部分最花费时间。一旦你知道瓶颈在哪里,你可以进行必要的修改。该报告会在你模块的build/reports/profile路径下,以HTML文件的形式保存。
下面这个报告就是在一个多module工程执行完build任务之后生成的:
该性能报告包含了在执行任务的过程中,每个阶段的耗时概览。在总结下面,是每个模块的耗时概览。除了截图表现的部分,还有两部分没有显示。一个是Dependency Resolution部分来显示每个模块耗时多长来完成依赖。最后的TaskExecution 部分包含了执行过程的详细信息,包含每个任务执行的时间,按照耗时从高到底的顺序排列。
Jack and Jill
如果你想使用实验性工具,你可以是设置Jackand Jill来加速构建。 Jack(JavaAndroid Compiler Kit)是一个能够将Java源码直接翻译成Android Dalvik可执行格式(dex)的新的Android 构建工具链。它有自己的.jack库格式,能够用于打包和缩小apk。Jill(JackIntermediate Library Linker)是一个工具能够将.aar和.jar文件转换成.jack 库。这些工具还处于实验阶段,但是他们能够节约构建时间,简化构建过程。不建议你将Jack和Jill用于正式版本,但是他们确实值得一试。
为了使用 Jack and Jill,你需要将build toolsversion提高到21.1.1以及以后,Gradle Android plugin版本在1.0.0及以上。在defaultConfing做以下设置即可:
android {
buildToolsRevision '22.0.1'
defaultConfig {
useJack = true
}
}
你也可以将Jack and Jill用于某一个版本。比如正式版本使用常规工具目测是版本可以使用实验性工具:
android{
productFlavor{
regular{
useJack=false
}
experimental{
useJack=true
}
}
}
一旦你开始使用Jack工具,就不需要ProGuard来优化和混淆APK了,但是你仍然可以使用ProGuard来制定特定的规则。
忽略Lint
当你用gradle来执行release构建的时候,会对你的代码进行Lint检查。Lint是一个静态代码检查工具能够标记你的布局文件和代码中的潜在问题。在一些情况下,他甚至可以截断build进行。如果你之前在项目中应用过Lint,当你想迁移到Gradle的时候,Lint可能会出现很多错误。为了使build能够完成,你可以设置Gradle忽略Lint错误,取消abortOnError,阻止Lint错误终止build过程。当然这只是暂时的解决方法,因为忽略Lint错误可能会导致丢失转义文件,导致app崩溃。
为了防止Lint阻塞构建线程,做如下设置:
android {
lintOptions {
abortOnError false
}
}
暂时的设置Lint失效能够使迁移已有的Ant构建进程到Gradle 容易一些。另外一种使得转义更平稳的方法是在Gradle中直接执行Ant任务。
从Gradle中使用Ant
如果你已经在配置Ant构建的时候花费了很多时间,那么将其转为Gradle可能听起来更恐怖。在这种情况下,Gradle不仅能够执行Ant任务,还能够扩展它们。这就意味着你可以一小步一小步的把Ant迁移到Gradle,而不是花费几天的时间转化整个build配置。
Gradle利用Groovy的AntBuilder来集成Ant。AntBuilder能够使你可以执行任何标准的Ant任务,你自定义的Ant任务和整个Ant的侯建。当然你也可以在你的Gradle 构建配置中定义Ant属性。
用Gradle执行Ant 任务
在Gradle执行标准的Ant任务很直接。你只需要为task的名字添加ant前缀,比如你想创建archive,你可以这么写:
task archive << {
ant.echo 'Ant is archiving...'
ant.zip(destfile: 'archive.zip') {
fileset(dir: 'zipme')
}
}
该任务使用Gradle定义,但是利用了两个ant任务:echo和zip。
当然你应该考虑用Gradle来替代ant执行任务。就像前面创建archive的例子,你可以使用Gradle任务如下创建:
task gradleArchive(type:Zip) << {
from 'zipme/'
archiveName 'grarchive.zip'
}
这个基于Gradle的archive 更准确,更容易理解。因为它不需要经过AntBuilder,会比使用Ant任务更快。
引入Ant脚本
如果你已经创建了一个ant脚本来构建你的app,你可以引入整个build配置,使用 ant.importBuild。所有的Ant目标就会自动转换成Gradle任务,你可以从他们的原始命名识别出来。
比如,以下面的Ant构建文件为例:
<project>
<target name="hello">
<echo>Hello, Ant</echo>
</target>
</project>
你可以用以下方式把这个构建文件引入进你的Gradle:
ant.importBuild'build.xml'
这将会把这个hello 任务暴露给你的Gradle构建,因此你可以像执行一个常规的Gradle任务一样执行它:
$ gradlew hello
:hello
[ant:echo] Hello, Ant
因为这个Ant任务被转成Gradle任务,你也可以用doFirst和doLast块或者是<<shortcut来扩展它。比如,你可以这么打印另一行:
hello << {
println 'Hello, Ant. It\'s me, Gradle'
}
如果你执行这个hello任务,它会看起来像这样:
$ gradlew hello
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
你也可以依赖从Ant引入的任务,就像你通常做的那样。比如,如果你想依赖这个hello任务创建一个新任务,你可以这么写:
task hi(dependsOn: hello) << {
println 'Hi!'
}
当你执行hi任务的时候,利用dependsOn来确定hello任务已经被触发:
$ gradlew intro
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
:hi
Hi!
如果有必要,你甚至可以创建一个依赖gradle的Ant任务。为了完成这个,你需要在build.xml文件中添加depends属性,像这样:
<target name="hi" depends="intro">
<echo>Hi</echo>
</target>
如果你的Ant构建文件很大,你想确定任何任务都没有跳过,你可以在引入的时候重命名所有的Ant任务,用以下代码:
ant.importBuild('build.xml') { antTargetName ->
'ant-' + antTargetName
}
如果你觉得重命名所有的Ant任务,记住,如果其中一个Ant 任务依赖于Gradle 任务的话,改Gradle任务也需要加前缀。然而,Gradle就找不到这个任务了,会抛出一个异常UnknownTaskException.。
属性(Properties)
Gradle和Ant不只是共享任务,你也可以在Gradle中定义可用于Ant build文件的属性。比如打印出version属性:
<target name="appVersion">
<echo>${version}</echo>
</target>
你可以通过假设这个属性的名字带有ant.来在Gradle 构建配置中定义这个属性,就像任务一样。下面就是定义一个Ant属性的最简便方法:
ant.version= '1.0'
Groovy在这里隐藏了很多继承。如果你完整定义这个属性,应该是这样的:
ant.properties['version']= '1.0'
执行这个version任务会像你期待的那样,在console中打印出1.0:
$ gradlew appVersion
:appVersion
[ant:echo] 1.0
在Gradle中对Ant深度继承使得将Ant构建转变成Gradle构建非常简单,你可以按照你自己的节奏慢慢适应。
App部署进阶
在第四章中,我们阐述了几个可以为同一个app创建多版本的方法,我们适应build type和product flavor。但是,在某些情况下,使用一个特定的技术可能更简单,比如APK Split技术。
SplitAPK
构建变体可以被看成是独立实体,他们可以有自己的代码、资源文件和manifest文件。APK splits,在另一方面,只会影响app的打包。整个编译、压缩、混淆等过程还是共享的。这种机制可以使得你基于分辨率或者ABI(application binary interface)来分离APK。
你可以通过在android设置块里面定义一个splits块来配置分包。为了设定根据分辨率配置分包,在split块里面创建一个density块。如果是依赖ABI来分包,你可以使用abi块。
如果你使用了按照分辨率分包,Gradle会为每个分辨率创建一个APK文件。如果你不需要,你可以手动排除一些分辨率,来加速构建构成。下面这个例子就是展示如何使用density分包,并且排除低分辨率的设备:
android {
splits {
density {
enable true
exclude 'ldpi', 'mdpi'
compatibleScreens'normal', 'large', 'xlarge'
}
}
}
如果你只支持某些分辨率,你可以使用“include”来创建分辨率的白名单。使用include方法,你需要使用reset()属性,该属性会把包含的分辨率列表重置成空字符串(which resets the list of included densities to an empty string. 这句话我也不知道什么意思,没有实践过)
在前面的代码段中,compatibleScreens属性是可选的,并且注入到manifext的相应节点中。例子中的配置是用来支持大屏设备,排除了小屏的设备。
基于ABI的分包也是同样的方法,所有的属性和利用分辨率分包的方法一样,除了compatibleScreens属性。ABI分包与屏幕大小没有关系,因此不包含这个属性。
在配置完splits之后,执行结果会创建一个总APK,和其他根据分辨率的特定APK。这意味着你得到一系列APK文件:
app-hdpi-release.apk
app-universal-release.apk
app-xhdpi-release.apk
app-xxhdpi-release.apk
app-xxxhdpi-release.apk
但是这里有一个警告。如果你想把多个APK文件上传到Google Play,你需要保证每个APK有不同的版本号。就是说每个分APK应该有一个独特的版本号。幸运的是,你先可以通过添加applicationVariants属性在Gradle文件中来实现。
下面这个代码片段来自于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
}
}
这个片段检查了在构建版本中使用了哪个ABI,然后用一个数去乘版本号来确保每个变体都有独特的版本号。
总结
在读完本章之后,你知道如何削减build输出的大小,如何通过配置Gradle和JVM来加速构建过程。你现在应该不害怕移植大型的项目了。你也学到了一些使开发和部署更简单的方法。