目前绝大多数的Android项目都是基于Grale了,因为Gradle确实给我们带来了很多便利,然而,在使用了Gradle后,最大的不满就是编译起来太慢了。解决慢的问题无非有两种方法

  • 提升硬件配置,选择CPU和内存和硬盘等更优的硬件
  • 在软件方面,减少不必要的耗时,充分利用现有机器的性能。

本文的主要经验围绕着如何减少不必要的耗时操作和如何充分利用机器性能展开。

开启daemon

通过开启守护进程,下一次构建的时候,将会连接这个守护进程进行构建,而不是重新fork一个gradle构建进程。Mac通过在~/.gradle加入以下配置,可以让所有项目在构建时都开启守护进程:

org.gradle.daemon=true

如果是windows下,则为用户目录下的.gradle文件夹,如果配置过 GRADLE\_USER\_HOME,则为该目录,编辑gradle.properties,如果没有则创建gradle.properties文件:

org.gradle.daemon=true

或者传递gradle参数

 ./gradlewtask --daemon

相比没有开启daemon,开启daemon有如下好处

  • 不需要每次启动gradle进程(JVM实例),减少了初始化相关的工作
  • daemon可以缓存项目结构,文件,task等,尽可能复用之前的编译成果,缩短编译过程

为了确保gradle配置生效,建议使用gradle —stop停止已有的daemon。

 ./gradlew --stop

再次执行gradle任务就可以应用daemon了,留意的话,可以看到类似这样的日志输出。

Starting a Gradle Daemon(subsequent builds will be faster)

并行构建

现在的工程往往使用了很多模块,默认情况下Gradle处理多模块时,往往是挨个按顺序处理。可以想象,这种编译起来会有多慢。好在Gradle提供了并行构建的功能,可以让我们充分利用机器的性能,减少编译构建的时间。

Mac在~/.gradle/gradle.properties中加入:

org.gradle.parallel=true

Windows修改gradle.properties文件

org.gradle.parallel=true

或向gradle传递参数

./gradlewtask --parallel

当我们配置完成,再次执行gradle task,会得到类似这样的信息,信息标明了开启Parallel以及每个task使用的线程信息。

./gradlew clean --info

Parallel execution is an incubating feature.
.......
:libs:x:clean (Thread[Task worker Thread 3,5,main]) completed. Took 0.005 secs.
:libs:xx:clean (Thread[Daemon worker Thread 3,5,main]) started.
:libs:xxx:clean (Thread[Task worker Thread 2,5,main]) completed. Took 0.003 secs.
:libs:xxxx:clean (Thread[Task worker Thread 3,5,main]) started.
:libs:xxxxx:clean (Thread[Task worker Thread 2,5,main]) started.
:libs:xxxxxx:clean (Thread[Task worker,5,main]) completed. Took 0.004 secs.
:libs:json-gson:clean (Thread[Task worker,5,main]) started.

org.gradle.daemon=true 就是让你让你编译时使用守护进程。

org.gradle.parallel=true 使用并行编译

org.gradle.jvmargs=-Xmx2048mJVM 最大允许分配的堆内存,按需分配

-XX:MaxPermSize=512mJVM 最大允许分配的非堆内存,按需分配

当然你也可以在下面的目录下面创建gradle.properties,来配置全局的属性,在所有的项目中起作用。

  • /home//.gradle/(Linux)
  • /Users//.gradle/(Mac)
  • C:\Users\\.gradle(Windows)

当然也可以修改xxx\android studio 安装目录\bin\studio64.exe.vmoptions文件来配置JVM 的相关的参数。

开发使用SDK=21

android {
    ...

    productFlavors {
        dev {
            minSdkVersion 21
        }

        release {
            minSdkVersion 14
        }
    }
}

我们都知道当API 不小于21,使用 ART,在 Build 时只做 class to dex,不做 mergeing dex,会省下大量的时间。

设置heap大小

为Gradle分配足够大的内存,则可以同样加速编译。如下修改文件gradle.properties

org.gradle.jvmargs=-Xmx5120m -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

由于Flipboard依赖繁多,且文件也多,并结合自身设备8G内存,这里为Gradle分配最大5G。效果目前看起来不错,大家可以根据自己的情况不断调整得到一个最优的值。

开启offline

开启offline之后,可以强制Gradle使用本地缓存的依赖,避免了网络读写操作,即使是需要从网络进行检查这些依赖。

./gradlew--offline taskName

如上使用时,加上—offline参数即可。

注意,如果是某个依赖在本地不存在,则会编译出错,解决方法,只需要暂时关闭offline,等依赖下载到本地后,在后续的执行中加入offline即可。

多modules工程优化

现在的一个Project往往有很多modules,导致我们的编译会变慢。使用—configure-on-demand会在执行任务时仅仅会配置相关的modules,而不是左右的modules。尤其是对于多模块的project来说,使用起来会有不小的提升。

./gradlew assembleChinaFastDebug --configure-on-demand

尝试停止已有的daemon

当我们开启了daemon有段时间后,会发现编译会变得慢了下来,这时候,我们可以尝试结束已有的daemon,确保后续的执行任务使用开启全新的daemon。如下停止已经存在的gradle daemon.

./gradlew --stop
Stopping Daemon(s)
1 Daemon stopped

debug构建关闭proguard

提到Proguard大家想到的都是代码混淆,其实除了代码混淆之外,Proguard还可以进行代码压缩,优化和预验证。这其中的代码优化可能会占据更多的时间。 比如一个开启了代码优化的配置如下

-optimizationpasses 5

这就意味着这个代码优化会经过5次,即上一次的优化输出结果作为下一次的优化的输入。反反复复的分析,知道完成配置的次数。

在Android中,我们可以配置debug禁用Proguard即可。

buildTypes {
    debug {
        minifyEnabled false
    }

    release {
        minifyEnabled true
    }
}

以Flipboard为例,当从设置optimizationpasses=5改成debug禁用proguard,编译时间减少了将近3分多钟。

进行profile分析

如果上面的所有配置可能到没有达到明显的效果,那么我们就应该使用profile功能来分析一下具体卡在哪里了。

gradle提供了性能分析的功能,就profile,使用很简单,执行任务时带上—profile参数即可。比如

./gradlew assembleChinaRelease --profile

待到执行完毕,在project根目录下的build/reports/profile目录有对应的结果文件,如profile-2017-04-08-23-06-37.html,使用浏览器打开,看到的效果是这样的

从上面的summary可知,上面的主要耗时表现在Task Execution上,于是我们切换到Task Execution这个tab

我们可以发现上面的:apps:droidyue:crashlyticsUploadDeobsChinaRelease居然耗费了4m26.26s,那么这是一个什么任务呢?

其实它是著名的bug收集工具crashlytics的上传混淆映射文件的操作,由于crashlytics的服务器在国外,导致这个网络操作会很慢。

解决方法是,我们可以选择性的应用crashlytics插件,具体可以参考Error-prone,Google出品的Java和Android Bug分析利器中关于选择开启error-prone的部分。

通过profile我们可以清晰地看出耗时的根源在哪里,并开始有的放矢地进行解决。

使用新版的gradle和Android gradle plugin

然而,在加上这两行之后,随着项目开发的逐渐复杂,引入的库也越来越多,项目本身也要增加一些productFlavor,速度又开始慢起来。后来Android Studio升级,Gradle升级,看到Gradle 2.4的升级说明上提到提高了gradle的构建速度,于是用了新版的Gradle,果然提高了些许速度。

编辑项目根目录下的build.gradle,把android gradle plugin更新到新版本:

classpath 'com.android.tools.build:gradle:X.X.X'

使用离线模式

我发现在构建过程中,除了修改了Java文件然后在multldex时有点耗时之外,最大的耗时都在于resolving dependencies,也就是解析依赖的这一步。而我的项目这时候的依赖应该都在本地中有缓存了。所以使用--offline,让它不去联网检查更新。

./gradlew aDR --offline

因为是构建过的,所以只需要3.846秒。而即使是clean,构建的时间也只需要47.175秒(并不是每一次都是这个时间,有时间会比较大,多构建几次会快一些)。

避免繁重的计算

通常情况下,我们可以避免大部分的gradle构建所做的繁重的工作。让我们看看demo,尝试去减少gradle构建时的IO输出。例如,你现在构建一个典型的APP为了持续集成,你需要去保存你构建的的一些信息。

这些信息仅仅是一些命令?在你的gradle.build文件中你可以看到:

def cmd = 'git rev-list HEAD --first-parent --count'
def gitVersion = cmd.execute().text.trim().toInteger()
android {
  defaultConfig {
    versionCode gitVersion
  }
}

上面的代码执行一个Git命令,并将结果以供以后使用变量。但是实际上,命令执行需要很多时间。为了您的开发环境的目的,你可能并不需要这些信息。幸运的是:gradle真的很灵活,这些配置只是纯的Groovy文件。所以,如果你改变上面的配置,就像下面的例子一样:

def gitVersion() {
  if (!System.getenv('CI_BUILD')) {
    // don't care
    return 1
  }
  def cmd = 'git rev-list HEAD --first-parent --count'
  cmd.execute().text.trim().toInteger()
}
android {
  defaultConfig {
    versionCode gitVersion()
  }
}

很轻松避免这样的计算,所以你应该注意将上面的代码保存下来。

如果需要使用命令行编译,可以配置

–daemon –parallel –offline

总结出加快构建速度的技巧如下:

开启守护进程

开启并行编译

使用新版本的android gradle plugin

使用新版本的Android Studio(它会使用新版本的Tools api去调用gradle)

使用命令行构建(如果你实在等不及Android Studio中还未执行完的其他任务)

构建时减少productFlavor

不要动态使用依赖:使用SNAPSHOT版本及使用SNAPSHOT仓库

使用离线模式(如果允许)

如果没必要则不需要在构建时进行clean

启用按需配置

避免做繁重的计算

并行编译


上面关于如果在不提升硬件的条件下进行优化,当我们所有的配置都已经应用,并且仍然感觉编译时间很长的话,那么我们也应该从硬件的角度去思考了。

关于提升编译速度的通常主要有三个主要的影响硬件

  • CPU不低于i5
  • 内存不少于8G
  • 硬盘为SSD

以上三者兼备的比较成熟的产品应该是MBP,如这个配置Apple MacBook Pro 15.4英寸笔记本电脑(Core i7 处理器/16GB内存/256GB SSD闪存/Retina屏)

这所谓工欲善其事必先利其器,当我们从硬件和软件上都下功夫进行了优化,我们的开发效率也会得到很大的提高。

引用

http://blog.csdn.net/maosidiaoxian/article/details/49583215

results matching ""

    No results matching ""