Gradle For Android系列9:高级配置

现在你知道了Gradle如何工作,如何创建自己的任务和插件,如何运行测试,以及如何设置持续集成,你几乎可以称自己为Gradle专家。本章包含了我们在前面几章中没有提到的一些优化技巧,这些技巧使你可以更轻松地使用Gradle构建,开发和部署Android项目。

在本章中,你将学习到以下主题:

  • 压缩Apk大小
  • 加快Build速度
  • 忽略Lint检查
  • 在Gradle中使用Ant
  • 应用发布的高级技巧

首先你将看到如何来减小构建输入Apk的体积大小。

压缩Apk大小

在过去几年中,APK文件的大小一直在急剧增加。有几个主要原因:在项目中引入了更多的第三方公共库;需要适配更多的屏幕类型;再就是随着需求的增多功能越来越多。

尽可能的缩小Apk大小在开发过程中是有必要的,因为Google Play本身要求APK文件限制在50MB以内,并且较小的APK可以让用户更快地下载和安装应用程序,占用更少的手机存储空间。

在这一小节中,你将看到Gradle构建配置文件中的一些属性,我们可以通过这些属性来缩小APK文件。

ProGuard

ProGuard是一个Java工具,它不仅可以缩小程序大小,还可以在编译时对代码进行优化,模糊处理和预校验。 它会遍历应用程序中的所有代码路径,查找出未使用的代码并将其删除。ProGuard还重命名代码中的类和字段。这个过程就是混淆代码,使代码更难被逆向。

Gradle的Android插件在buildType上有一个名为minifyEnabled的布尔属性,你需要将其设置为true以启用ProGuard:

1
2
3
4
5
6
7
8
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

当你将minifyEnabled设置为true时,Gradle将会执行proguardRelease任务,并在构建过程中调用ProGuard程序进行混淆。

在启用ProGuard之后编译出来的Apk需要再次进行测试,因为
它可能会删除一些你可能还需要的代码。因为这个问题,使得很多开发人员厌倦ProGuard。要解决此问题,你可以定义ProGuard的规则,以排除某些类被删除或混淆。 proguardFiles属性用于指定包含ProGuard规则的文件。例如,要保留一个类,可以添加如下的简单规则:

1
-keep public class <MyClass>

getDefaultProguardFile(‘proguard-android.txt’)方法从一个名为proguard-android.txt的文件中获取默认的ProGuard设置,该文件在Android SDK/tools/proguard下。默认情况下,Android Studio会将proguard-rules.pro文件添加到每一个Android模块中,你可以在该文件中添加特定于该模块的混淆规则。

ProGuard规则对于你构建的每个应用程序或Library都是不同的,所以我们在本书中不会涉及太多细节。如果你想了解有关ProGuard和ProGuard规则的更多信息,请查看官方Android ProGuard文档

除了压缩Java代码,缩减使用的资源也是可以很好帮助减小Apk大小。

压缩Resource文件

Gradle和Gradle的Android插件可以在应用程序被打包时删除所有未使用到的资源文件。如果你有忘记删除的旧资源,这可能非常有用。还有一点是当你导入具有大量资源文件的第三方库时,但是又只使用了其中的一小部分资源,通过启用缩减资源来解决此问题。当前有两种方式来缩减资源。自动或手动的方式。

自动压缩

自动压缩的方式很简单,就是在你的构建中配置shrinkResources属性。如果将此属性设置为true,Android构建工具将自动检测哪些资源未被使用,并且不会将它们包括在APK中。

使用此功能有一个要求,必须启用ProGuard。这是因为资源缩减的工作方式,Android构建工具只有在引用这些资源的代码已被删除的情况下才能找出哪些资源未被使用。

以下代码段显示了如何在某个buildType上配置自动缩减资源:

1
2
3
4
5
6
7
8
android {
buildTypes {
release {
minifyEnabled = true
shrinkResources = true
}
}
}

如果要查看在启用自动缩减资源后APK的确切大小,您可以运行shrinkReleaseResources任务。 这个任务打印出它减少了包的大小:

1
2
:app:shrinkReleaseResources
Removed unused resources: Binary resource data reduced from 433KB to 354KB: Removed 18%

你可以通过在build命令中添加–info标志来详细了解从APK中删除的资源:

1
$ gradlew clean assembleRelease --info

当你使用此标志时,Gradle会打印出大量关于构建过程的详细信息,包括最终构建输出中被移除的每个资源。

自动资源缩减的一个问题是它可能删除太多的资源。特别是动态使用的资源可能被意外地剥离。为了防止这种情况,您可以在放置在res/raw/中的名为keep.xml的文件中定义异常(不存在需要自己创建)。一个简单的keep.xml文件如下所示:

1
2
3
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/keep_me,@layout/also_used_*"/>

keep.xml文件本身不会包括在最终的apk中。

手动压缩

缩减资源比较温和的方式就是只去除某些分辨率下的某些语言文件或图像。 某些Library(例如Google Play服务)包含许多语言,如果你的应用只支持一种或两种语言,将最终APK中包含这些库中的所有语言文件都没有意义。你可以使用resConfigs属性配置要保留的资源,然后其余的将被抛出。

例如:如果只想保留英语,丹麦语和荷兰语字符串,可以使用resConfigs,如下所示:

1
2
3
4
5
android {
defaultConfig {
resConfigs "en", "da", "nl"
}
}

设置保留指定分辨率资源,可以如下设置:

1
2
3
4
5
android {
defaultConfig {
resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
}

语言和分辨率资源设置可以同时设置。事实上,每种类型的资源都可以使用此属性进行限制。

如果你很难设置ProGuard,或者你只是想要摆脱你的应用程序不支持的语言或density资源,那么使用resConfigs是一个很好的方法来做资源缩减。

加快Build速度

许多使用Gradle的Android开发者都抱怨编译时间过长。其构建可能比使用Ant的时间更长,因为Gradle在构建生命周期中有三个阶段,每次执行任务时都需要经历着三个阶段。这虽然使得整个构建过程在每一个环节都可配置,但也导致了构建非常缓慢。幸运的是,有几种方法可以加快Gradle的构建。

Gradle优化

调整Gradle构建速度的一种方法是更改一些默认设置。在第5章“管理多模块构建”中并行构建执行中提到过,但还有一些其他设置可以调整。

简要回顾一下,你可以通过在gradle中设置parallel属性来启用并行构建。properties文件放置在项目的根目录中。你需要添加以下行:

1
org.gradle.parallel=true

另一个修改是启用Gradle守护进程,当你第一次运行构建时启动后台进程。任何后续构建将随后重用该后台进程,从而减小启动成本。只要使用Gradle,该进程就一直存在,在空闲时间三小时后会终止。当你在短时间内使用Gradle多次时,使用守护程序特别有用。你可以在gradle.properties文件中启用守护程序,如下所示:

1
org.gradle.daemon=true

在Android Studio中,Gradle守护程序默认情况下处于开启状态。这意味着在从IDE内部进行第一次构建之后,下一个构建会更快。但是,如果从命令行界面构建,Gradle守护程序是禁用的,除非你在设置文件中启用它。

要加快编译速度,还可以调整Java虚拟机(JVM)上的参数。 有一个称为jvmargs的Gradle属性,它允许你为JVM的内存分配池设置不同的值。对你的构建速度有直接影响的两个参数是Xms和Xmx。Xms参数用于设置JVM要使用的初始内存量,而Xmx参数用于设置JVM内存使用的最大值。你可以在gradle.properties文件中手动设置这些值,如下所示:

1
org.gradle.jvmargs=-Xms256m -Xmx1024m

你需要设置正确数量和单位,可以是k(千字节),m(兆字节)和g(千兆字节)。默认情况下,最大内存分配(Xmx)设置为256 MB,并且未设置起始内存分配(Xms)。最佳设置取决于计算机的功能。

最后一个可以配置来加快构建速度的属性是org.gradle.configureondemand。如果你有包含多个模块的复杂项目,此属性特别有用,因为它尝试通过为正在执行的任务跳过不需要的模块来限制在配置阶段花费的时间。如果将此属性设置为true,Gradle将在运行配置阶段之前尝试找出哪些模块具有配置更改,哪些没有配置更改。如果你的项目中只有一个Android应用程序和一个库,这个功能将不会非常有用。如果你有很多模块松耦合,这个功能可以节省你大量的构建时间。

系统级Gradle属性
如果要将这些属性应用于所有基于Gradle的项目,你可以在用户录中的.gradle目录下创建一个gradle.properties文件。 在Windows上,此目录的完整路径为%UserProfile%\.gradle,在Linux和Mac OS X是〜/.gradle。 在用户目录中设置这些属性,而不是在项目级别做的原因是,你CI服务器上需要保证不能太高的内存消耗,相对来说构建时间不太重要,所以在开发PC上设置而不再项目里设置,避免CI服务器也使用优化配置。

Android Studio设置

你可以在Android Studio设置中对Gradle属性进行相应配置来加快编译过程。要查找编译器设置,打开Settings对话框,然后导航到BuildExecutionDeployment | Compiler,在该项目下,你可以找到并行构建,JVM选项,按需配置等设置像。这些设置只在基于Gradle的Android模块显示。可以参考下面截图:

从Android Studio配置比在构建配置文件中手动配置这些设置要更容易,并且设置界面便于查找各个构建属性。

Build分析

如果你想知道构建的哪些部分减慢了构建过程,你可以对整个构建过程进行配置。你可以通过在执行Gradle任务时添加–profile标志来实现。当使用此标志时,Gradle会创建一个概要分析报告,它可以告诉你构建过程的哪些部分是最耗时的 一旦知道瓶颈在哪里,就可以进行必要的更改。分析报告将作为HTML文件保存在您的模块的build/reports/profile目录下。

如下是在多模块项目上执行构建任务后生成的报告:

如上图,Summary栏显示执行任务期间在每个阶段中花费的时间的概览。Configuration栏显示配置阶段花费了多少时间。Dependency Resolution部分显示每个模块解析依赖所需的时间。最后,Task Execution部分包含非常详细的任务执行时间,这个部分具有每个任务的执行时序,按执行时间从高到低排序。

Jack and Jill

你可以启用Jack and Jill来加快构建。Jack(Java Android Compiler Kit)是一个新的Android构建工具包,它将Java源代码直接编译为Android Dalvik可执行文件(dex)格式。它有自己的打包和资源压缩流程并生成.jack格式的中间库。Jill(Jack Intermediate Library Linker)是一个可以将.aar和.jar文件转换为.jack库的工具。这些工具将减少构建时间和简化Android构建过程。

要能够使用Jack和Jill,您需要使用版本21.1.1或更高版本的build tool,以及Gradle版本1.0.0或更高版本的Android插件。 启用Jack和Jill只需要在build.gradle文件的defaultConfig块中设置一个属性:

1
2
3
4
5
6
android {
buildToolsRevision '22.0.1'
defaultConfig {
useJack = true
}
}

你还可以在特定build variant上启用Jack和Jill。这样,你就可以在特定构建版本上使用jack and jill,别的版本还是走正常构建

1
2
3
4
5
6
7
8
9
10
android {
productFlavors {
regular {
useJack = false
}
experimental {
useJack = true
}
}
}

一旦将useJack设置为true,压缩资源和混淆将不再通过ProGuard,但你仍然可以使用ProGuard规则语法来指定某些规则和异常给jack使用。

忽略Lint检查

当你使用Gradle执行构建时,Android构建任务将会对代码执行Lint检查。Lint是一个静态代码分析工具,用于标记布局和Java代码中的潜在错误。在某些情况下,Lint一旦报错,构建就会停止。如果你之前没有在项目中使用Lint,并且你想迁移到Gradle,Lint可能会出现很多错误。为了使构建工作不会因为Lint检查而中断,你可以配置Gradle来忽略Lint错误,并通过禁用abortOnError来阻止它们中止构建。这应该只是一个临时解决方案,因为忽略Lint检查可能会产生像丢失翻译类似这样的潜在错误,这可能会导致应用程序崩溃的问题。为了防止Lint阻塞构建过程,请像这样禁用abortOnError:

1
2
3
4
5
android {
lintOptions {
abortOnError false
}
}

暂时禁用Lint中止可以使现有Ant构建过程更容易迁移到Gradle。过渡更平滑的另一种方法是直接从Gradle执行Ant任务。

在Gradle中使用Ant

如果你投入了大量的时间在使用Ant来进行项目构建,切换到Gradle可能听起来可怕。不过不要担心,Gradle不仅可以执行Ant任务,还可以扩展它们。这意味着你可以以较小的步骤从Ant迁移到Gradle,而不是花费几天时间来转换整个构建配置。

Gradle使用Groovy的AntBuilder进行Ant集成。AntBuilder允许你执行任何标准的Ant任务,包括自定义Ant任务和整个Ant构建。Gradle还支持在构建配置中定义Ant属性。

在Gradle中运行Ant任务

从Gradle运行标准Ant任务很简单。 你只需要通过ant引用任务名称即可。 例如,要创建archive任务,您可以使用如下代码:

1
2
3
4
5
6
task archive << {
ant.echo 'Ant is archiving...'
ant.zip(destfile: 'archive.zip') {
fileset(dir: 'zipme')
}
}

基于Gradle的archive任务更简洁,更容易理解。因为它不需要通过AntBuilder,它也比使用Ant任务稍快。

导入完整的Ant脚本

如果你已经创建了一个Ant脚本来构建应用程序,你可以使用ant.importBuild导入整个构建配置。然后,所有Ant targets都将自动转换为Gradle任务,您可以按原始名称访问这些任务。

例如,如下Ant构建文件:

1
2
3
4
5
<project>
<target name="hello">
<echo>Hello, Ant</echo>
</target>
</project>

您可以将此构建文件导入到Gradle构建中,如下所示:

1
ant.importBuild 'build.xml'

以上设置可以将ant的hello任务暴露到你的Gradle构建中,所以你可以像一个普通的Gradle任务一样执行这个ant任务,它会打印出Hello,Ant

1
2
3
$ gradlew hello
:hello
[ant:echo] Hello, Ant

因为Ant任务转换为了Gradle任务,您还可以使用doFirstdoLast块或<<标志的方式来扩展它。 如,您可以打印另一行到控制台:

1
2
3
hello << {
println 'Hello, Ant. It\'s me, Gradle'
}

执行hello任务,会得到如下结果

1
2
3
4
$ gradlew hello
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle

你也可以依赖从Ant导入的任务。 例如,如果要创建一个依赖于hello任务的新任务,可以这样做:

1
2
3
task hi(dependsOn: hello) << {
println 'Hi!'
}

使用depends On确保hello任务在执行时触发
hi任务:

1
2
3
4
5
6
$ gradlew intro
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
:hi
Hi!

如果你需要,你甚至可以使一个Ant任务依赖于一个Gradle任务。使用这个,你只需要在build.xml文件中将depends属性添加到任务,如下所示:

1
2
3
<target name="hi" depends="intro">
<echo>Hi</echo>
</target>

如果你的Ant构建文件非常庞大,并且要确保所有任务名称都不重叠,则可以使用以下代码片段重命名所有导入时的Ant任务:

1
2
3
ant.importBuild('build.xml') { antTargetName ->
'ant-' + antTargetName
}

请注意,重命名所有Ant任务时,如果你有一个依赖于Gradle任务的Ant任务,那么该Gradle任务也需要加上前缀。否则,Gradle将无法找到它并抛出一个UnknownTaskException。

Properties

Gradle和Ant不仅可以共享任务,还可以在Gradle中定义可以在Ant构建文件中使用的属性。如下Ant示例中,target打印出一个名为version的属性:

1
2
3
<target name="appVersion">
<echo>${version}</echo>
</target>

就像定义任务一样,你可以在Gradle构建配置中通过在属性名前加上ant来定义version属性。如下定义Ant属性的示例:

1
ant.version = '1.0'

上面的代码中,Groovy其实藏了很多具体实现。如果你写完整的属性定义,它看起来像这样:

1
ant.properties['version'] = '1.0'

跟预想的一样执行version,即将“1.0”打印到控制台:

1
2
3
$ gradlew appVersion
:appVersion
[ant:echo] 1.0

Ant已经深入集成到Gradle中,者使得从基于Ant的构建过渡到Gradle很容易,并且你完全可以按照自己喜欢的步调进行转换。

应用发布的高级技巧

第4章“创建构建变体”中,你学习到了使用buildType和productFlavor来创建同一个应用程序的多个版本。但是,在某些情况下,使用更具体的技术(如APK拆分)可能更容易。

Apk拆分

每一个build variant都可以看作是单独的版本,每个版本可以有自己的代码,资源和manifest文件。另一方面,APK拆分只影响应用程序的打包。 编译,缩小,混淆等仍然是共享的。 此机制允许您基于density或application binary interface(ABI)拆分APK。

您可以通过在Android配置块中定义splits块来配置apk拆分。要配置density分割,请在splits块中创建一个density块。如果要设置ABI拆分,请使用abi块。

1
2
3
4
5
6
7
8
9
android {
splits {
density {
enable true
exclude 'ldpi', 'mdpi'
compatibleScreens 'normal', 'large', 'xlarge'
}
}
}

如果你的应用只需要支持几个屏幕密度,则可以使用include创建密度白名单。 要使用include,你首先需要使用reset()属性,它将包含的density列表重置为空字符串。

上述代码段中的compatibleScreens属性是可选的,配置后gradle会自动在manifest文件中插入对应的节点。示例中的配置适用于支持常规到超大屏幕的应用,不包括具有小屏幕的设备。

基于ABI的拆分APK的与根据屏幕密度拆分的工作方式大致相同。ABI拆分与屏幕大小无关,所以没有属性称为compatibleScreens,所以除了compatibleScreens之外,其他属性与密度拆分的属性都相同。

设置密度拆分后执行构建,Gradle会生成一个通用APK和几个特定屏幕密度的APK。这意味着你会得到一个APK的集合,如下所示:

1
2
3
4
5
app-hdpi-release.apk
app-universal-release.apk
app-xhdpi-release.apk
app-xxhdpi-release.apk
app-xxxhdpi-release.apk

使用APK拆分需要注意的一点是,如果你要将多个APK推送到Google Play,需要确保每个APK都有不同的version code。也就是说每个拆分都应该有一个唯一的version code。你可以通过查看applicationVariants属性在Gradle中做到这一点

1
2
3
4
5
6
7
8
9
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
}
}

以上代码会检查build variant中使用的ABI,然后对version code做乘法处理,以确保每个变体都有唯一的版本代码。

总结

阅读本章后,你知道了如何减小构建输出APK的大小,以及如何通过配置Gradle和JVM来加快构建速度。迁移Ant项目对你来说是小意思了。你还学到了一些小技巧,使开发和部署更容易。

截止到本章,Gradle For Android的内容都介绍完了!现在,你知道Gradle的各个特性,你可以调整和自定义Android项目的构建过程,自动化到你除了执行任务,再也不需要做任何手动工作。你可以配置build variant,管理依赖关系和配置多模块项目。你现在需要做的是在实际项目中应用你的新技能!

写得好,就打赏一下吧!