Android Studio编译慢、卡死和狂占内存怎么破?

做者:Ailurus
连接:https://www.zhihu.com/question/27953288/answer/118031242
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。
 html

至于加快编译速度,有一句说一句,我觉着一些答主的答案适用性都并不强,其实仍是应该从 gradle 入手,讲的有什么不合适的地方,还请轻喷,有什么问题也能够留言。java

如下我讲到的全部步骤,推荐都在终端里执行。在终端里执行编译有不少好处:android

  1. 能够观察到整个编译过程,有助于理解 gradle 构建流程;
  2. 能够看到编译过程当中哪些任务比较耗时,能够对编译慢的问题对症下药;
  3. 能够随时终止编译,若是被卡在某个阶段,ctrl + c 能够随时终止编译,在 Android Studio 里也终止编译,可是基本上十次有九次都会失败;
  4. 由于是在终端里,对 Android Studio 影响极小,基本不会形成 Android Studio 卡顿;
  5. 不会遇到 Android Studio 的各类 bug 。


先说一下 gradle 的生命周期吧,gradle 构建一个工程主要分为三部分(彻底掌握了下面这张图,整个 gradle 的构建过程能了解个十之七八了):git

  1. 初始化阶段:主要是解析 setting.gradle 文件(所以有人提到减小 setting.gradle 的 module 数量,是颇有道理的,可是实际操做过程限制颇多,缘由最后会大体说一下);
  2. 读取配置阶段:主要是解析全部的 projects 下的 build.gradle 文件,包括 rootProject 和其余的 subprojects(子项目),检查语法,肯定 tasks 依赖以创建 task 的有向无循环图,检查 task 里引用的文件目录是否存在等(这一步也进一步验证了减小 setting.gradle 里的 module 数量能够加快编译速度,由于减小一个 module ,须要解析的 build.gradle 文件就减小一个,第 3 步里就不会执行本属于这个 module 的任务了,可是仍是 1 里面说的问题,限制颇多);
  3. 执行阶段:按照 2 中创建的有向无循环图来执行每个 task ,整个编译过程当中,这一步基本会占去 9 成以上的时间,尤为是对于 Android 项目来说,将 java 转为 class
     
    compileDebugJavaWithJavac/compileReleaseJavaWithJavac
    和 将 class 合并成 dex
     
    transformClassesWithDexForDebug/transformClassesWithDexForRelease
    这两步很耗时,第一步还好,第二步会耗时很是久。首先在 gradle.properties 里设置
     
    org.gradle.jvmargs=-Xmx4096m //越大越好
    ,而后在工程的 build.gradle 里的 android 结点下增长 dexOptions 配置,以下:
dexOptions {
    dexInProcess true
    preDexLibraries true
    javaMaxHeapSize "4g"//越大越好
    incremental true
}


明确了 gradle 的生命周期,那么就能够看到加快编译速度的关键就是从第三步入手,固然,减小 setting.gradle 里的 modules 数量这一步也是必须的。下面说说咱们公司的实践吧。github

  1. 项目插件化改造,每位业务上的同窗只须要编译一个模块便可,这一点基本上从根本上解决了编译慢的问题(对于大多数没有插件化需求的朋友们能够看下面的一些实践),首先 setting.gradle 里的 module 只有本身开发的模块了,而对应的执行阶段的任务也只有这一个 module 的任务了。
  2. 执行一次 gradle build ,咱们就会发现,在这个过程当中,实际上是执行了屡次打包任务的,在 buildTypes 里配置了多个编译打包类型,默认有 debug 和 release ,咱们还能够手动配置其余的类型,并且还有 productFlavor 里的多渠道,这样就会执行屡次编译打包,而正常开发过程当中,只须要打 debug 包去调试,所以使用 gradle assembleDebug 便可,等发版的时候使用其余方式去打多渠道的包(如美团的方案http://tech.meituan.com/mt-apk-packaging.html);
  3. 既然编译主要时间都集中在 gradle 生命周期的第三步执行 task 任务里,那么咱们就能够把一些可有可无的任务给禁用掉,好比各类 Test ,各类 lint 等,恰好在 gradle 里有这样的指令 -x lint 能够临时禁掉 lint 任务,-x test 能够禁掉 test 任务,事实上对于一个稍微大一点的项目,lint 也是很耗时的,固然也能够经过 gradle 脚本完全禁用 lint 和 test 任务,我也在一些微信群里分享过相关代码,可是不太建议这么作,由于有时候 lint 和 test 也是挺有用的;
  4. gradle 自己提供了一些指令参数能够加快编译,好比 --daemon ,开启守护进程,--parallel ,开启并行编译等,这个也能够在 gradle.propertites 里配置(编译使用的 jvm 内存也能够在这里配置)。
  5. 定制 gradle 编译流程,利用官方提供的 API 彻底能够定制一个适合本身的编译流程,能够参考一下携程的 DynamicAPK/sub-project-build.gradle at master · CtripMobile/DynamicAPK · GitHub,里面有携程他们本身整个完整的编译流程,脚本自己很简单,一共只有两三百行代码。

上面讲到的几点,现有环境就能够作到的大概是这样(有一点要特别注意,若是工程里有交叉依赖,必定不要使用 --parallel 参数):微信

gradle assembleDebug --daemon --parallel -x lint -x test

,若是是要直接安装到设备上的话,就把 assembleDebug 换成 installDebug ,assembleDebug 能够简写为 asD ,installDebug 能够简写为 iD 。jvm

最后讲一下,为何减小 setting.gradle 里的 module 数量,确实能够加快编译,可是却限制颇多呢?
首先,咱们想一下整个编译过程,先去解析 gradle 配置,创建 tasks 依赖有向图,而后再去执行每个 module 的 task ,若是咱们经过 maven 依赖,使用 aar 替掉了 module(单指 android library),若是咱们要改这个 module 里的文件,岂不是每次都要修改上传再下载,这其实还好,可是有一个致命的问题:不修改版本号的话,SNAPSHOT 在 IDEA 里常常会很差使这样就致使修改的东西会不生效,去解决这个问题是很是耗费时间的。不过有一种方式,能够必定程度上解决问题,增长下面的脚本:maven

project.configurations.all(new Action<Configuration>() {
@Override
    void execute(Configuration files) {
        files.resolutionStrategy.cacheDynamicVersionsFor(5, TimeUnit.MINUTES)
        files.resolutionStrategy.cacheChangingModulesFor(0, TimeUnit.SECONDS)
    }
})

那有人会问,插件化里,每一个人开发一个模块,对于每一个模块的维护不也是要打包上传到 maven ,每次一有修改,哪怕是很是微小的修改,也要作一次上传,一样会遇到 SNAPSHOT 很差使的问题。嘿嘿,这个问题嘛,我司本身维护了一个 gradle 插件,已经解决了,至于解决方案,是公司机密,我是不会讲的。
而后,还有一点,我相信大部分开发者日常开发都是单 module 的,多 module 的状况并很少,所以大多数依赖基本也都是 aar 或者 jar ,根本就不存在所谓的将 library 转成 aar 上传的状况,所以一些答主说的根本毫无心义,这也是为何我会说影响编译速度的状况主要集中在 gradle 生命周期的第三个阶段,至于第三个阶段的优化,看我上面的答案就行了。ide

相关文章
相关标签/搜索