相信很多Android开发者每天都会被Android的构建速度影响到,很多人说我只改了一行代码,又要编译2分钟,还是有人说我什么都没动,却还要等待这么久,严重影响工作效率。好消息是Google IO大会上Android 推出了插件3.0.0版本,并且专门做了一次分享,介绍了新的插件特色,并给他家分享了提升Android构建速度的一些使用tips,按照大神给的tips我专门对我们的项目做了一次优化,结果让我非常惊喜,构建速度提升了10倍以上,本篇文章,就要跟大家分享我的优化之路,以及每一步优化所带来的实际效果。

  在我们谈论优化措施之前,我们先来看看优化之前项目的build性能

  ./gradlew clean app:assembleDebug --profile

  我们使用这个命令执行full-build(从头创建)的过程,来衡量build的性能。

  


   从profile报告里可以看到,没有优化之前我们项目执行一次full-build的时间是2分26秒,不是每次都是这么多,但是相差不大。接下来开始我们的优化之旅。

  1.升级Android Gradle Plugin 至3.0.0

  Android gradle plugin 3.0.0版本更新了很多新的功能,已解决了很多性能问题。升级3.0.0时需要同时升级gradle版本和添加一个maven库.

  首先升级gradle-wrapper.properties中的distributionUrl

  distributionUrl=\

  https\://services.gradle.org/distributions/gradle-4.0-milestone-1-all.zip

  同时添加google提供的maven库地址

  buildscript {

  repositories {

  ...

  // You need to add the following repository to download the

  // new plugin.

  maven {

  url 'https://maven.google.com'

  }

  }

  dependencies {

  classpath 'com.android.tools.build:gradle:3.0.0-alpha1'

  }

  }

  当然升级过程并不仅仅只有这两步,剩下的过程中你需要解决你在sync当中遇到的问题,因为gradle升级了大版本,有一些新的功能使用变化。我就说说我所解决的问题:

  第一个问题是依赖使用方式的变化,3.0.0中引入了implementation 和api两种新的依赖方式,至于这两种方式的区别后面会讲,新的插件会强制依赖本地其他module时使用implementation方式

  第二个问题是新的插件和最新的Butterknife插件8.7+版本会冲突,编译时会报错

  Unable to find method 'com.android.build.gradle.api.BaseVariant.getOutputs()Ljava/util/List

  github上已经有了这个issue,暂时的解决办法是降至8.4版本

  第三个问题,我们项目中有用到BlockCanaryExt插件,这个插件也会编译报错,先注释掉,等待作者解决。并且我们观察到BlockCanary插件会严重影响我们的编译效率,光是插件任务的耗时就是在30s以上,因此我们如果能去掉或者进行条件编译的话,应该会优化不小。

  


  来看下,在升级Android Gradle plugin ,以及移除了BlockCanayEx插件之后,我们的full-build性能:

  


 

  可以看到编译时间下降了几乎有一分钟。这其中就包括去除BlockCanary插件之后优化的时间

  


 

  2. 避免Legacy Multidex

  Google针对64k方法数的问题推出了MultiDex的解决方法,但是不同的api版本上,multidex的做法是不一样的,在api21以上,因为Android采用了新的运行时ART,会在安装的时候将所有的classesN.dex合并成一个.oat包。你需要做的就是在编译脚本中加一行 multiDexEnabled true。但是在api以下,你需要引入multidex support library. 而且在编译过程中,编译脚本要话很长时间决定哪些class要放入primary dex中,哪些放入secondary dex中。在api21以下,这叫做legacy multidex。

  开发中,我们可以避免legacy multidex带来的编译性能消耗

  

  我们引入新的product flavor: development.因此我们的编译方式就变成了:

  ./gradlew clean app:assembleDevelopmentDebug --profile

  


   编译时间又下降了接近40s,形势喜人。

  3. 尽量少的引入资源

  


 

  这样我们在开发环境下只引入英文资源和xxhdpi下的资源,减小打包时间,从下面的profile中可以看到,full-build时间减少了1秒,还是有一定效果的。

  


  

  4. 禁用Png cruncher

  aapt会对你的png做处理,让他们的size变得更小,这一步叫做png-cruncher,减小apk的体积。但是在开发模式下,这些并不重要,因此在开发模式下我们可以禁止png cruncher

  


  devBuild这个project属性需要我们在build的时候穿进去

  ./gradlew clean app:assembleDevelopmentDebug -PdebBuild --profile

  如果是通过Android studio 进行编译,可以在preferences里面指定

  


  

  优化之后的build时间几乎没变,但是略微降了1秒

  


 

  5. 避免 crashLytics 的alwaysUpdateBuildId

  James还提到了crashLytics潜在的影响编译时间的问题,说在应用插件fabric后,每次编译crashLytics都会生成新的buildId.导致编译变慢,我们的项目也用到了crashLytics, 因此有必要进行优化

  buildTypes {

  debug {

  signingConfig signingConfigs.debug

  ext.alwaysUpdateBuildId = false

  }

  }

  优化之后的效果很明显,full-build时间下降了6s左右, 看来CrashLytics确实影响了编译性能

  


 

  6. 使用gradle.cache

  gradle3.5版本之后,推出了新的cache机制,和Android studio2.3版本引入的BuildCache不一样,这个新的cache机制会缓存每个任务的输出,而build cache只会pre-dexed的外部库。Google暂时还没有对这个机制做过多的优化,但是建议我们先开启这个机制。

  org.gradle.caching = true

  来看看开启了cache机制的profile

  


  

  在开启了cache之后,我发现我们的build时间确实减少了,但是好像没有想象中的多,难道cache机制没有生效吗?

  7. 禁用动态的BuildConfig

  至此,能做的优化措施大部分都做了,我们也取得了相当不错的效果,full-build时间从2m25s下降为34s,超过4x的速度提升。但同时我发现一个现象,如果我改一行代码或者资源,执行一次增量build

  ./gradlew app:assembleDevelopmentDebug -PdevBuild --profile

  耗时也要16s时间,这就很奇怪了,理论上来说,不该一行代码,应该秒编啊,为了搞清楚这个问题,我们加入--info编译选项,找到更多的信息

  ./gradlew app:assembleDeveopmentDebug -PdevBuild --info | grep -A 3 Executing

  加上--info选项时,如果执行了某个task,会解释其原因,为什么会执行,我们可以根据Executing关键字来找到编译慢的原因

  


  

  发现generateXXXBuildConfig 和XXXjavaWithjavac任务被执行,原因是buildConfig的itemValues发生了变化。目光转向build.gradle

  buildConfigField "long", "BUILD_TIMESTAMP", getTimeStamp() + "L"

  我们对BuildConfig中的一项输入做了动态变化,getTimeStamp会获取当前的时间,导致每次build时这个值都会发生变化。当然在release环境下,这个值确实有用,但是在开发中,就没必要了。它会导致重新生成BuildConfig.java, 继而导致 javac, dex, package,sign等一系列的任务重新执行,需要对其优化

  buildConfigField "long", "BUILD_TIMESTAMP",

  project.hasProperty("devBuild")?"000000000":getTimeStamp() + "L"

  这样如果是开发环境,每次这个值都是一样的。再来看看优化之后的profile

  


  full-build的时间居然达到了惊人的12s, 这个时候再来运行增量编译,不改一行代码。

  


  1.8s,这才是我们想要的、正常的效果。

  因此要避免在开发环境下使用动态的BuildConfig选项,因为那样会影响的编译速度和开发效率,不仅仅是BuildConfig,还有versionCode,动态的versionCode会引起AndroidManifest文件的修改,继而引起资源文件的重新打包,影响编译效率。

  简直神乎其技,通过7步优化,我们将full-build的时间从146s降低到了12s,优化之后速度提升了12倍,飞一般的构建感觉即将到来,就是这个feel,倍儿爽!

  


 Google IO 分享上还有提到其他的优化tips,包括禁用multi-APK,使用instant run等, multi-APK我们并没有用到,instant run也没有用,就没有给大家详细阐述。感兴趣的可以去youtube上学习。

  本文转自简书,作者:呆萌狗和求疵喵