0%

Android Gradle最佳实践系列9:高级配置

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

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

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

首先我们来看下如何减小构建Apk的体积大小。

压缩Apk大小

在过去几年中,Android 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 Plugin可以在应用程序被打包时删除所有未使用到的资源文件。如果我们有忘记删除的旧资源,这就非常有用。还有一点是当导入具有大量资源文件的第三方库时,但是又只使用了其中的一小部分资源,通过启用缩减资源来解决此问题。当前有两种方式来缩减资源,分为自动或手动的方式。

自动压缩

自动压缩的方式很简单,就是在构建中配置 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中止可以使现有构建过程更容易迁移到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,管理依赖关系和配置多模块项目。你现在需要做的是在实际项目中应用你的新技能!