Android Gradle v3.0新特性介绍

为什么要更新Android Gradle Plugin?

Android Gradle Plugin在v3.0版本中进行了大量调整和优化,其中官方对一个包含130个modules的工程在不同版本上进行了对比,其中v3.0版本性能明显优于此前的版本。

20180711153130265218831.png

如何更新Android Gradle Plugin?

在项目的顶级build.gradle文件中,升级android gradle plugin到v3.0及以上,添加google()仓库。

1
2
3
4
5
6
7
8
9
10
11
12
buildscript {
repositories {
// Gradle 4.1 and higher include support for Google's Maven repo using
// the google() method. And you need to include this repo to download
// Android plugin 3.0.0 or higher.
google()
...
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.0'
}
}

这里Google建议明确指定gradle plugin的版本,不建议使用com.android.tools.build:gradle:3.+的形式,这样会导致版本管理的混乱,android gradle plugin的版本与Gradle版本存在对应的关系,这样写也不方便维护这样的对应关系。android gradle plugin与Gradle版本对应关系如下图:
20180712153136097754655.png

这里升级plugin到v3.1.0版本,需要对应升级Gradle版本到4.4以上,升级Gradle,需要在gradle/wrapper/gradle-wrapper.properties文件中更新Gradle版本。

1
distributionUrl = https\://services.gradle.org/distributions/gradle-4.4-all.zip

修改完Plugin和Gradle的版本后,构建工程,多数会出现构建失败或者构建时间比较长的情况,其中很大的原因是Android Studio在下载Plugin和Gradle,这种情况通常与网络状况有关,有时需要设置代理。

设置代理

由于android gradle plugin需要从google()仓库下载,虽然开启了翻墙软件,仍然可能会出现构建失败的情况,这时需要在AndroidStudio中设置代理,修改文件位置gradle/wrapper/gradle-wrapper.properties,这里设置的代理地址127.0.0.1是一个回路地址,意味着Androidstudio会使用本机的翻墙软件配置,需要注意端口设置与翻墙软件中配置的端口保持一致。如Shadowsocks的socks配置为:
20180712153136216422036.png

对应在gradle-wrapper.properties文件中添加socks的配置

1
2
3
systemProp.socksProxyHost=127.0.0.1
systemProp.socksProxyPort=1086
systemprop.socksProxyVersion=5

同样的,Shadowsocks的http配置为
20180712153136225681889.png
对应修改gradle-wrapper.properties文件中Http和Https的配置

1
2
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=1087

配置Https

1
2
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=1087

Android Gradle Plugin3.0以上的一些新特性

目前最新的Android Gradle Plguin最新版本是3.1.3。该版本以上的都还是beta版本,还不是正式版本,这里主要介绍plugin3.0.0及plugin3.1.0的一些新特性。

Android Gradle Plugin3.0.0

Android Gradle Plugin3.0.0的正式版本是在2017年10月发布,该版本对应Gradle4.1到Gradle4.4以下版本。

plugin3.0的优化

Android Gradel Plugin 3.0版本中进行了一些优化,使得项目的构建速度大大提升,主要体现在以下几个方面:

1.通过对Gradle Task Graph的优化,从而能够更好的实现多Module的并行编译。

2.通过使用plugin3.0中新加入的依赖方式 implementation, api, compileOnly,及 runtimeOnly. 使得在修改依赖时(implementation声明的依赖),gradle不会再重新编译modules,加快编译速度。说明:plugin3.0中新添加的配置api与原来的compile等同,implementation只作用于当前module,使用implementation依赖的库只对当前的module起作用。

新配置 已过时配置 行为
implementation compile 依赖项在编译时对模块可用,并且仅在运行时对模块的消费者可用。 对于大型多项目构建,使用 implementation 而不是 api/compile 可以显著缩短构建时间,因为它可以减少构建系统需要重新编译的项目量。 大多数应用和测试模块都应使用此配置。
api compile 依赖项在编译时对模块可用,并且在编译时和运行时还对模块的消费者可用。 此配置的行为类似于 compile(现在已弃用),一般情况下,您应当仅在库模块中使用它。 应用模块应使用 implementation,除非您想要将其 API 公开给单独的测试模块。
compileOnly provided 依赖项仅在编译时对模块可用,并且在编译或运行时对其消费者不可用。 此配置的行为类似于 provided(现在已弃用)。
runtimeOnly apk 依赖项仅在运行时对模块及其消费者可用。 此配置的行为类似于 apk(现在已弃用)。

3.在plugin3.0中,每个class文件都会被构建成dex文件,只有当class文件发生变化时才会重新进行构建,这样只对变化的class文件进行重新构建,加快了项目的构建速度。对minSdkVersion设置为20及以下的app,可以通过分包来加快构建。

4.通过设置Build cache来加快构建速度,设置Build cache可以通过以下方式:

1
2
//在命令行中添加--build-cach打开缓存,--no-build-cache取消缓存
./gradlew build --build-cach

或者在gradle.properties文件中,添加

1
org.gradle.caching=true

5.在plugin3.0中会默认使用AAPT2来对资源文件的处理,如果要禁用AAPT2,可以在gradle.properties文件中设置

1
android.enableAapt2=false

并且在命令行中暂停gradle的进程。

1
./gradlew --stop

plugin3.0的新特性

1.plugin3.0 中变体对依赖能够自动感知,当构建项目的某个变体时,plugin会自动匹配与该变体相关的本地module。比如构建app的debug的变体时,会自动匹配library的debug变体,构建app的freeDebug变体时,会自动匹配library的freeDebug变体。当我们在plugin3.0中构建plugin3.0以下的项目时,会收到一下的错误提示:

1
2
Error:All flavors must now belong to a named flavor dimension.
The flavor 'flavor_name' is not assigned to a flavor dimension.

这是因为在plugin3.0新添加了一个属性flavorDimensions。这个属性在原来flavor的基础上增加了维度的概念。怎样理解这个属性呢?比如下面的build.gradle文件。

1
2
3
4
5
6
7
8
9
productFlavors {
leading {}
common {}
}

buildTypes {
release {}
debug {}
}

按照编译原则,

1
Build variant:[Leading, Common][Debug, Release]

会编译出4个变体,在添加了flavorDimensions属性后,扩展了flavor的维度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
android {
...
buildTypes {
debug {...}
release {...}
}

flavorDimensions "api", "mode"

productFlavors {
leading {
dimension "mode"
...
}

common {
dimension "mode"
...
}

minApi24 {
dimension "api"
...
}

minApi23 {
dimension "api"
...
}

minApi21 {
dimension "api"
...
}
}
}

按照编译原则

1
2
Build variant: [minApi24, minApi23, minApi21][Leading, Common][Debug, Release]
Corresponding APK: app-[minApi24, minApi23, minApi21]-[leading, common]-[debug, release].apk

会编译出12个变种的apk文件。理解了flavor dimensions后,为了能够增加变体依赖的准确度,需要对所有的product flavor声明flavor dimensions,即使flavor 只有一个维度。

有时在app的build.gradle文件中新加一种buildType,而library的build.gradle文件中没有与之对应的buildType。
这时就会出现类似以下的编译错误,这时需要使用matchingFallbacks为给定的构建类型指定备用匹配项。

1
2
3
4
5
6
7
8
9
Error:Could not resolve all dependencies for configuration ':bankOK:betaNewApiInnerTestRuntimeClasspath'.
> Unable to find a matching configuration in project :abChat:
- Configuration 'debugApiElements':
- Required apiLvl 'ProductFlavorAttr{name=newApi}' but no value provided.
- Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'AndroidTypeAttr{name=Aar}' and found compatible value 'AndroidTypeAttr{name=Aar}'.
- Required com.android.build.gradle.internal.dependency.BuildTypeAttr 'BuildTypeAttr{name=innerTest}' and found incompatible value 'BuildTypeAttr{name=debug}'.
- Found com.android.build.gradle.internal.dependency.VariantAttr 'VariantAttr{name=debug}' but wasn't required.
- Required org.gradle.api.attributes.Usage 'for runtime' and found incompatible value 'for compile'.
- Required releaseType 'ProductFlavorAttr{name=beta}' but no value provided.

如app的build.gradle中的buildType中新增一种 staging类型,而library中没有与之对应的buildType,这是需要使用matchingFallbacks进行制定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android {
buildTypes {
debug {}
release {}
staging {
// Specifies a sorted list of fallback build types that the
// plugin should try to use when a dependency does not include a
// "staging" build type. You may specify as many fallbacks as you
// like, and the plugin selects the first build type that's
// available in the dependency.
matchingFallbacks = ['debug', 'qa', 'release']
}
}
}

2.plugin3.0支持Android Instant App 及Android Instant Apps SDK。

3.plugin3.0内置使用Java8的库及特性。

在Java8中默认工具链(desugar)对javac输出的字节码进行转换,java8的一些新的特性都是在desugar中完成的,原来的jack将不再支持。
20180716153173050821984.png
为了使用默认工具链(desugar)内置的java8的支持,需要先停用对jack的使用。在build.gradle文件中移除jackOptions块。同时需要声明对java8的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
android {
...
defaultConfig {
...
// Remove this block.
jackOptions {
enabled true
...
}
}

// Keep the following configuration in order to target Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

由于java8中内置了对lambda表达式的支持,所以需要移除build.gradle文件中对lambda库的依赖。

1
2
3
4
5
6
7
buildscript {
...
dependencies {
// Remove the following dependency.
classpath 'me.tatarka:gradle-retrolambda:<version_number>'
}
}

同时移除每个模块 build.gradle 文件中的 Retrolambda 插件和 retrolambda 块。

1
2
3
4
5
6
7
8
9
10
// Remove the following plugin.
apply plugin: 'me.tatarka.retrolambda'
...
// Remove this block after migrating useful configurations.
retrolambda {
...
// If you have arguments for the Java VM you want to keep,
// move them to your project's gradle.properties file.
jvmArgs '-Xmx2048m'
}

如果停用对 Java 8 语言功能的支持,需要在gradle.properties文件中设置:

1
android.enableDesugar=false

4.plugin3.0添加了JunitRunner Orchestrator的支持;

5.plugin3.0添加了testOptions.unitTests.includeAndroidResources属性,解决单元测试时对一些资源文件的依赖。当设置该属性为true时,gradle会在单元测试执行之前完成resource,asset,manifest文件的合并。

6.plugin3.0支持字体文件作为一种资源。

7.支持Android Instant Apps SDK 1.1中对特定语言apk的支持;

8.通过buildStagingDirectory修改外部的native工程。

1
2
3
4
5
6
7
8
9
10
11
12
13
android {
...
externalNativeBuild {
// For ndk-build, instead use the ndkBuild block.
cmake {
...
// Specifies a relative path for outputs from external native
// builds. You can specify any path that's not a subdirectory
// of your project's temporary build/ directory.
buildStagingDirectory "./outputs/cmake"
}
}
}

9.可以在AndroidStudio中使用CMake 3.7及以上版本编译native工程。

10.支持lintChecks这种依赖方式来按照自定义的lint规则编译jar文件,并且打包到aar及apk中,自定义的lint 规则必须是一个单独的工程来输出jar,并且该工程中的所有依赖方式只能为compileOnly。其它module可按照如下的方式添加依赖。

1
2
3
4
5
6
7
dependencies {
// This tells the Gradle plugin to build ':lint-checks' into a lint.jar file
// and package it with your module. If the module is an Android library,
// other projects that depend on it automatically use the lint checks.
// If the module is an app, lint includes these rules when analyzing the app.
lintChecks project(':lint-checks')
}

plugin3.0的行为变更

1. Android Gradle Plugin3.0中移除了一些核心的api,这会导致升级到v3.0之后,会出编译失败的情况,同时会引入一些新api来替代这些被移除的api,比如variant outputs和manifestOutputFile:

在Variant api中使用all()替代了each()来遍历variant对象。each()只能遍历存在配置期的variant对象,对在执行期添加的variant对象是无法访问的。另外,由于plugin3.0版本对配置期的Task进行了优化,一些variant的task将不会再被创建,使得在配置期中无法预先确定会有哪些outputFile输出,因此不能通过api对outputFile对象进行访问。

1
2
3
4
5
6
7
8
9
10
11
// If you use each() to iterate through the variant objects,
// you need to start using all(). That's because each() iterates
// through only the objects that already exist during configuration time—
// but those object don't exist at configuration time with the new model.
// However, all() adapts to the new model by picking up object as they are
// added during execution.
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "${variant.name}-${variant.versionName}.apk"
}
}

processManifest.manifestOutputFile()该方法将不能被调用,否则会出现以下错误提示:

1
2
3
A problem occurred configuring project ':myapp'.
Could not get unknown property 'manifestOutputFile' for task ':myapp:processDebugManifest'
of type com.android.build.gradle.tasks.ProcessManifest.

可以调用processManifest.manifestOutputDirectory()得到一个包含manifests文件的路径,进而找到manifests文件进行一些逻辑处理,比如修改android:versionCode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android.applicationVariants.all { variant ->
variant.outputs.all { output ->
output.processManifest.doLast {
// Stores the path to the maifest.
String manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
// Stores the contents of the manifest.
def manifestContent = file(manifestPath).getText()
// Changes the version code in the stored text.
manifestContent = manifestContent.replace('android:versionCode="1"',
String.format('android:versionCode="%s"', generatedCode))
// Overwrites the manifest with the new text.
file(manifestPath).write(manifestContent)
}
}
}

2. 在android gradle plugin3.0以后不需要再指定android.buildToolsVersion,默认情况下plugin会使用与plugin版本对应的最低版本。android gradle plugin3.0对应Android SDK Build Tools 26.0.2版本,android gradle plugin3.1对应Android SDK Build Tools 27.0.3 版本。

3.从plugin3.0开始crunchPngs属性对除debug之外的构建,默认是打开的,该属性打开时,会增加png文件的编译时间。如果要加快编译时间,可以关闭该属性,或者把png文件转换为WebP。

1
2
3
4
5
6
7
8
android {
buildTypes {
release {
// Disables PNG crunching for the release build type.
crunchPngs false
}
}
}

4.从plugin3.0开始gradle会自动构建cmake文件中配置的外部工程。

5.从plugin3.0开始,要将annotation processors添加到classpath中,否则会编译失败。在plugin3.0之前的版本,如果你的项目中依赖的工程中涉及到annotation processor,这些annotation processor会被自动添加到compile classpath中,这就会导致大量无效的依赖添加到编译中,严重影响了编译效率。

在plugin3.0.0开始,这些annotation processor将不会被自动添加到compile classpath中,需要使用annotationProcessor进行添加。比如:

1
2
3
4
dependencies {
...
annotationProcessor 'com.google.dagger:dagger-compiler:<version-number>'
}

那什么样的依赖需要使用annotationProcessor进行声明呢?如果依赖的jar包中包含META-INF/services/javax.annotation.processing.Processor文件,就需要使用annotationProcessor声明。如果项目需要添加该jar包到complie classpath中,这时需要使用compile进行第二次声明。
20180713153147512654858.png

还有一种解决方案,就是通过设置属性includeCompileClasspath为true来保留一些plugin2.3.0中的特性,该方案官方是不推荐使用的。

1
2
3
4
5
6
7
8
9
10
11
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath true
}
}
}
}

6.从plugin3.0开始,对已经废弃的ndkCompile提出了更多的限制,推荐迁移到CMake或者ndk-build项目来编译native代码。

Android Gradle Plguin3.1.0

Android Gradle Plguin3.1.0的正式版本是2018年3月发布,该版本对应Gradle4.4及以上版本。

在最新版本的Androidstudio3.2中默认使用已一种新的Dex 编译器,称为D8,区别于原有的Dx编译器,编译速度更快,将.class文件编译成的.dex文件更小。如果要禁用D8,可以在gradle.properties文件中设置:

1
android.enableD8=false

在plugin 3.1.0中的一些行为变更:

1.在plugin3.1.0中将不再默认针对不同平台(如mips, mips64,armeabi)打包多个apk,如果要继续为不同平台打包多个apk,需要在build.gradle文件中进行如下声明,同时需要将ndk的版本修改为r16b及以下版本。

1
2
3
4
5
6
splits {
abi {
include 'armeabi', 'mips', 'mips64'
...
}
}

2.在为Android Instant App构建配置APK时,plugin3.1.0默认会对语言配置的按根语言分组。比如如果app中包含zh-CN,zh-TW的语言环境,Gradle会打包这些资源文件到zh语言配置的split中。可以通过include关键字进行重写。

1
2
3
4
5
6
7
8
9
10
11
splits {
language {
enable true
// Each string defines a group of locales that
// Gradle should package together.
include "in,id",
"iw,he",
"fil,tl,tgl",
"yue,zh,zh-TW,zh-CN"
}
}

3.plugin会删除那些超过30天的build cache

4.在resConfig中设置‘auto’将不再自动选择打包到apk中的字符串资源包,如果还设置为auto,plugin会打包所有字符串资源到apk中,因此需要针对不同的语言环境进行设置。

1
2
3
4
5
6
7
android {
defaultConfig {
...
// Keeps language resources for only the locale specified below.
resConfig "en"
}
}

5.androidTestApi已经被废弃,需要使用androidTestImplementation替代。

迁移到android gradle plugin3.0以上

参考文献

  1. https://developer.android.com/studio/releases/gradle-plugin#3-1-0
  2. https://developer.android.google.cn/studio/build/gradle-plugin-3-0-0-migration
  3. https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration?utm_source=android-studio

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×