58APP Android构建工具升级历程

背景

由于58APP是一个比较庞大的工程,涉及业务线较多,除非特殊情况58APP每年只会有一次统一升级各SDK的计划,包括第三方SDK,构建环境等。本次就介绍一下升级Android构建工具的历程。

Android APP的构建一般涉及了三个工具,Android plugin、Gradle、Build Tools。58APP也不例外,在依赖于这三个工具同时我们也有自定义的打包插件,自定义插件用于在构建时对构建过程进行自定义,构建过程中运行一段我们的代码,从而达到我们的目的。8App中这三个工具的版本分别如下:

58APP Android构建工具升级历程_第1张图片

其中最主要的版本号为Android plugin,其他两个工具的版本号根据Android plugin的版本号的依赖而决定。Android plugin只规定了Gradle和Build Tools的最低版本,因此理论上我们可以直接升级Gradle和Build Tools的版本号。而对打包效率功能影响最大的是Android plugin,因此不能随意升级,必须经过详细调研。

目前Android plugin版本为3.0,该版本包含了各种解决大型项目性能问题的更改,具体变更此处不再赘述。

Android plugin版本变化梳理

最新的Android plugin版本为3.3.1(笔者升级时为最新版本,目前已更新至3.4.x),Android plugin版本号命名的规则大致如下:

  1. 版本号一共为3位
  2. 当发生重大升级时更新第一位版本号
  3. 当发生较大升级时更新第二位版本号
  4. 当修复bug或者其他的一些补丁时更新第三位版本号

因此3.3.1为3.3.0版本的小版本升级,基本与3.3.0一致。由于3.0到3.3.0只发生了3个较大版本升级,因此我们来分别分析一些这三个较大版本升级的变化。

3.1.0 (2018.3)

依赖情况:

CCCF276C-CE71-4AAC-A7EA-4C1A7FD123AB.png

新的Dex编译器D8

默认情况下,Android Studio现在使用名为D8的新DEX编译器。 DEX编译是将.class字节码转换为Android Runtime(或Dalvik)的.dex字节码的过程。 与之前的编译器(称为DX)相比,D8编译速度更快,输出更小的DEX文件,同时具有相同或更好的应用运行时性能。

D8不会影响正常的开发流程,如果不需要可以配置以下代码关闭D8编译器:

android.enableD8=false

其他行为变化

  • 在构建多个平台不同ABI的APK时,默认情况下,插件不再为以下ABI生成APK:mips,mips64和armeabi。如果要构建以这些ABI为目标的APK,则必须使用NDK r16b或更低版本并在build.gradle文件中指定ABI,如下所示:

58APP Android构建工具升级历程_第2张图片

  • 在为Android Instant App构建配置APK时,语言配置拆分现在默认按根语言分组。 例如,如果您的应用包含zh-TW或zh-CN语言环境的资源,Gradle将以zh语言配置拆分打包这些资源。 您可以使用include属性定义自己的组来覆盖此行为,如下所示:

58APP Android构建工具升级历程_第3张图片

  • Android插件的构建缓存现在去除超过30天的缓存条目。
  • 将“auto”传递给resConfig不再自动选择要打包到APK中的字符串资源。 如果继续使用“auto”,插件将打包您的应用及其依赖项提供的所有字符串资源。 因此,您应该指定希望插件打包到APK中的每个区域设置。
  • 由于本地模块不能依赖于应用程序的测试APK,因此使用androidTestApi配置而不是androidTestImplementation将依赖项添加到已检测的测试中会导致Gradle发出以下警告:

BB8831A3-C715-4747-B928-C264E423099B.png

Bug修复

  • 修复了Android Studio无法正确识别复合构建中的依赖项的问题。
  • 修复了在单个构建中多次加载Android插件时出现项目同步错误的问题 - 例如,当多个子项目在其buildscript类路径中包含Android插件时。

3.2.0 (2018.9)

依赖情况:

A64F9B24-CB82-41F4-9FB8-3CC84E74A917.png

新功能

  • 支持构建Android应用程序包:应用程序包是一种新的上传格式,包括所有应用程序的已编译代码和资源,同时推迟APK生成并签名到Google Play商店。 您不再需要构建,签名和管理多个APK,并且用户可以获得针对其设备进行优化的较小下载。 要了解更多信息,请阅读关于Android App Bundles
  • 使用注解处理器时支持提高增量构建速度:AnnotationProcessorOptions DSL现在扩展了CommandLineArgumentProvider,它使您或注释处理器作者能够使用增量构建属性类型注释来注释处理器的参数。 使用这些注释可以提高增量和缓存清理构建的正确性和性能。 要了解更多信息,请阅读注释处理器的Pass参数
  • AndroidX的迁移工具:当使用Android Gradle插件3.2.0和Android 3.2及更高版本时,您可以通过从菜单栏中选择Refactor> Migrate to AndroidX来迁移项目的本地和Maven依赖项以使用新的AndroidX库。 使用此迁移工具还会在gradle.properties文件中将以下标志设置为true:

    • android.useAndroidX:设置为true时,Android插件使用相应的AndroidX库而不是支持库。 如果未指定此标志,则默认情况下插件会将其设置为false。
    • android.enableJetifier:当设置为true时,Android插件会自动迁移现有的第三方库,通过重写其二进制文件来使用AndroidX。 如果未指定此标志,则默认情况下插件会将其设置为false。 只有当android.useAndroidX也设置为true时,才能将此标志设置为true,否则会出现构建错误。
  • 新的代码缩减器,R8:R8是一种用于代码缩减和混淆的新工具,它取代了ProGuard。 您可以通过在项目的gradle.properties文件中包含以下内容来开始使用R8的预览版本:

    android.enableR8 = true

其他行为变化

  • 现在默认情况下启用D8 Desugaring。
  • AAPT2现在在Google的Maven仓库中。 要使用AAPT2,请确保build.gradle文件中包含google()依赖项,如下所示:

58APP Android构建工具升级历程_第4张图片

  • 现在默认启用本机multidex。 在将应用程序的调试版本部署到运行Android API级别21或更高级别的设备时,以前版本的Android Studio启用了本机multidex。 现在,无论您是部署到设备还是构建APK以进行发布,Android Gradle插件都可以为设置minSdkVersion = 21或更高版本的所有模块启用原生multidex。
  • 该插件现在强制执行依赖protobuf插件(0.8.6),Kotlin插件(1.2.50)和Crashlytics插件(1.25.4)的最低版本。
  • 功能模块插件com.android.feature现在在指定模块名称时仅强制使用字母,数字和下划线。 例如,如果您的功能模块名称包含破折号,则会出现构建错误。 此行为与动态要素模块插件的行为相匹配。

Bug 修复

  • JavaCompile现在可以在具有数据绑定的项目中缓存。
  • 更好地编译避免库模块使用数据绑定。
  • 如果由于某些不可预测的构建错误而在早期版本中禁用了按需配置,则现在可以重新启用按需配置。(3.0.x or 3.1.x在Gradle 4.6及以上时按需配置不能使用)

3.3.0 (2019.2)

依赖情况:

07C8228E-808A-4F48-B229-01B4C60DF8F5.png

新功能

  • 改进的类路径同步:在解析运行时和编译时类路径的依赖关系时,Android Gradle插件会尝试修复出现在多个类路径中的依赖关系的某些下游版本冲突。

    例如,如果运行时类路径包含库A版本2.0并且编译类路径包含库A版本1.0,则插件会自动将编译类路径的依赖性更新为库A版本2.0以避免错误。

    但是,如果运行时类路径包含库A版本1.0并且编译包含库A版本2.0,则插件不会将编译类路径上的依赖项降级为库A版本1.0,并且您将收到错误。 要了解更多信息,请参阅修复类路径之间的冲突。

  • 使用注解处理器时改进了增量Java编译:此更新通过在使用注释处理器时改进对增量Java编译的支持来减少构建时间。

    • 对于使用Kapt(大多数仅限Kotlin项目和Kotlin-Java混合项目)的项目:即使使用数据绑定或retro-lambda插件,也会启用增量Java编译。 Kapt任务的注释处理尚未增量。
    • 对于不使用Kapt的项目(仅Java项目):如果您使用的注释处理器都支持增量注释处理,则默认情况下会启用增量Java编译。 要监控增量注释处理器的采用情况,请观看Gradle issue 5277。

      但是,如果一个或多个注释处理器不支持增量构建,则不会启用增量Java编译。 相反,您可以在gradle.properties文件中包含以下标志:

      android.enableSeparateAnnotationProcessing=true

      当您包含此标志时,Android Gradle插件会在单独的任务中执行注释处理器,并允许Java编译任务以递增方式运行。

  • 使用过时的API时更好的调试信息:当插件检测到您使用的API不再受支持时,它现在可以提供更详细的信息,以帮助您确定API的使用位置。 要查看其他信息,您需要在项目的gradle.properties文件中包含以下内容:

    android.debug.obsoleteApi=true

    您还可以通过从命令行传递-Pandroid.debug.obsoleteApi = true来启用该标志。

  • 您可以从命令行对动态要素模块运行检测测试。

其他行为变化

  • 延迟任务配置:该插件现在使用Gradle的新任务创建API来避免初始化和配置完成当前构建不需要的任务(或不在执行任务图上的任务)。 例如,如果您有多个构建变体,例如“发布”和“调试”构建变体,并且您正在构建应用程序的“调试”版本,则该插件可避免初始化和配置“发布”版本的任务。

    在Variants API中调用某些旧方法(例如variant.getJavaCompile())可能仍会强制执行任务配置。 要确保您的构建针对延迟任务配置进行了优化,请调用新方法,而不是返回TaskProvider对象,例如variant.getJavaCompileProvider()。

    如果您执行自定义构建任务,请了解如何适应Gradle的新任务创建API。

  • 对于给定的构建类型,当设置useProguard为false时,插件现在使用R8而不是ProGuard来缩小和混淆应用程序的代码和资源。 要了解有关R8的更多信息,请阅读Android Developer's Blog中的此博客文章
  • 库项目的R类生成更快:以前,Android Gradle插件会为每个项目的依赖项生成一个R.java文件,然后与应用程序的其他类并行编译这些R类。 该插件现在直接生成包含应用程序编译的R类的JAR,而无需先构建中间的R.java类。 此优化可以显着提高包含许多库子项目和依赖项的项目的构建性能,并提高Android Studio中的索引速度。
  • 该插件强制指定某些第三方插件的最低版本。
  • 单变量项目同步:将项目与构建配置同步是让Android Studio了解项目结构的重要一步。 但是,对于大型项目而言,此过程可能非常耗时。 如果您的项目使用多个构建变体,您现在可以通过将项目同步限制为仅当前选择的变体来优化项目同步。

    您需要在Android Gradle Plugin 3.3.0或更高版本中使用Android Studio 3.3或更高版本来启用此优化。 满足这些要求后,IDE会在您同步项目时提示您启用此优化。 默认情况下,新项目也会启用优化。

    要手动启用此优化,请单击文件>设置>实验> Gradle(Android Studio>首选项>实验> Mac上的Gradle),然后选中Only sync the active variant复选框。

  • 自动下载缺少的SDK包:此功能已扩展为支持NDK。 要了解更多信息,请阅读使用Gradle自动下载缺少的软件包

Bug修复

  • 尽管Jetifier已启用,但构建过程调用android.support.v8.renderscript.RenderScript而不是AndroidX版本
  • 由androidx-rs.jar引起的冲突包括静态捆绑的annotation.AnyRes
  • 使用RenderScript时,您不再需要在build.gradle文件中手动设置Build Tools版本

升级动力

Dex编译器D8

3.1.0版本新增了名为D8的Dex编译器,据说D8编译速度更快,输出更小的DEX文件,同时具有相同或更好的应用运行时性能。

代码缩减和混淆的新工具R8

R8是一种用于代码缩减和混淆的新工具,它取代了ProGuard。目前处于预览版,默认不启用。

根据官方测试数据,启用R8与D8后,编译速度有所提升,但是dex文件减小并不明显,详细见下图所示:

58APP Android构建工具升级历程_第5张图片

58APP Android构建工具升级历程_第6张图片

58APP Android构建工具升级历程_第7张图片

由于R8处于预览版,而且对减少包大小的效果并不明显因此本次升级并不启用R8。

其他性能优化

  • 使用注解处理器时支持提高增量构建速度。
  • 延迟任务配置,避免初始化和配置完成当前构建不需要的任务。
  • 库项目的R类生成更快,3.3版本插件直接生成包含应用程序编译的R类的JAR,而无需先构建中间的R.java类。
  • 单变量项目同步:Android Studio 3.3与3.3版本插件配合时优化了项目同步功能,项目使用多个构建变体时,可以通过将项目同步限制为仅当前选择的变体来优化项目同步速度。

开始升级

gradle语法变更

  1. 错误:插件3.1版本开始instrumentTest修改为androidTest,影响所有包含instrumentTest语法的库
  2. 警告:android.enableAapt2=true属性已失效,默认使用aapt2
  3. 警告:android.enableDesugar=false属性已失效,默认true
  4. 依赖语法变更:

    58APP Android构建工具升级历程_第8张图片

multidex问题

  1. 生成的maindexlist文件目录以及名称修改,导致自定义multidex.gradle中的hook失效,提示 multiDexBuildMainlistTask can not find the maindexlist.txt file。由于代码中通过名称获取目录,因此将判断的名称由maindexlist.txt改为mainDexList.txt解决此问题,代码如下:

    58APP Android构建工具升级历程_第9张图片

  2. 打包时提示maindex field超出,使用dex-member-list工具分析,发现结果如下:

    58APP Android构建工具升级历程_第10张图片

API问题:

  1. junit.framework.Assert类不存在,由于使用的地方都是断言,因此进行类似以下修改:

    58APP Android构建工具升级历程_第11张图片

  2. canvas.save(Canvas.ALL_SAVE_FLAG);方法不存在,仅存在无参方法canvas.save(),因此修改为canvas.save()
  3. canvas类中除ALL_SAVE_FLAG以外的其他FLAG都不可用了,因此涉及到canvas.saveLayer方法中的FLAG参数大部分是错误的,修改为ALL_SAVE_FLAG,源码的实现中此方法的saveFlags也会被替换为ALL_SAVE_FLAG

    58APP Android构建工具升级历程_第12张图片

  4. java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/message/BasicNameValuePair httpclient相关类找不到,google发现新增一个配置在manifest文件
    3234CF61-8D4A-4052-AA5F-AF2EAAC9F377.png
  5. Permission Denial: startForeground from pid=2381, uid=10351 requires android.permission.FOREGROUND_SERVICE 新增权限android.permission.FOREGROUND_SERVICE。由于项目中只有一处com.wuba.debug.floatball.FloatBallService中使用startForeground,而且此类已不再使用,因此删除此类。

jar包资源问题

一些不规范的jar包中存在res资源目录(如百度地图sdk),在Android plugin 3.0以前是可以将jar包中的res资源打包到apk中,从Android plugin 3.0以后就无法打包进apk。由于58APP中包含res的jar包还有不少,对于这种情况我们的解决办法是自定义构建插件在构建过程中将jar包中的res复制到生成目录。此次升级至3.3.1后自定义的plugin由于版本升级后一些task变化目前已失效,因此重新编写插件代码来实现此功能,主要代码如下:

58APP Android构建工具升级历程_第13张图片

遗留问题

目前项目同步时会出现以下警告(虽然不影响编译,但是看起来还是不舒服):

58APP Android构建工具升级历程_第14张图片

前两个警告,提示我们getJavaCompiler会在2019年底删除,但是getJavaCompileProvider的返回值与getJavaCompiler不一致,目前暂没有特别好的办法,后续有好的办法再解决。

最后一个警告是提示我们将compile替换为implementation或者api,但是我们项目中都已替换,警告排查是我们android-aspectjx插件语法导致,后续android-aspectjx升级后即可解决。

升级前后速度对比

经过测试升级后编译速度提升了大约33%,测试方式为clean后的全源码编译,下面是编译数据:

ADE37DFA-60CD-4386-9186-5AF27859650E.png

你可能感兴趣的