Android 优化APP 构建速度的17条建议

本文同步简书:Android 优化APP 构建速度的17条建议html

较长的构建时间将会减缓项目的开发进度,特别是对于大型的项目,app的构建时间长则十几分钟,短则几分钟,长的构建时间已经成了开发瓶颈,本篇文章根据Google官方文档,加上本身的一些理解提供一些提高app构建速度的优化建议。java

1,为开发环境建立一个变体

有许多配置是你在准备app的release 版本的时候须要,可是当你开发app的时候是不须要的,开启没必要要的构建进程会使你的增量构建或者clean构建变得很慢,所以须要构建一个只保留开发时须要配置的变体,以下例子建立了一个devprod变体(prod 为release 版本的配置)。android

android {
  ...
  defaultConfig {...}
  buildTypes {...}
  productFlavors {
    // When building a variant that uses this flavor, the following configurations
    // override those in the defaultConfig block.
    dev {
      // To avoid using legacy multidex, set minSdkVersion to 21 or higher.
      minSdkVersion 21
      versionNameSuffix "-dev"
      applicationIdSuffix '.dev'
    }

    prod {
      // If you've configured the defaultConfig block for the release version of
      // your app, you can leave this block empty and Gradle uses configurations in
      // the defaultConfig block instead. You still need to create this flavor.
      // Otherwise, all variants use the "dev" flavor configurations.
    }
  }
}复制代码

2, 避免编译没必要要的资源

避免编译和包含你没有测试的资源(好比添加的一个本地的语言和屏幕密度资源),你能够只在你的’dev’ flavor下指定一种语言和一个屏幕密度,以下:web

android {
  ...
  productFlavors {
    dev {
      ...
      // The following configuration limits the "dev" flavor to using
      // English stringresources and xxhdpi screen-density resources.
      resConfigs "en", "xxhdpi"
    }
    ...
  }
}复制代码

上面的配置将会限制dev 变体只使用 english string 资源和 xxhdpi 屏幕密度资源。android-studio

3,配置debug 构建的Crushlytics为不可用状态

在debug 构建状态下,若是你不须要运行崩溃上报,你能够将这个插件设置为不可用状态来提高你的构建速度,以下:浏览器

android {
  ...
  buildTypes {
    debug {
      ext.enableCrashlytics = false
    }
}复制代码

上面只是举个例子,Crushlytics 为崩溃上报分析工具,在开发的时候咱们可能不须要,所以不须要打开,在咱们实际开发中,像崩溃上报SDK,数据统计SDK等(如 友盟统计、GrowingIO、百度统计)在开发阶段都设置为不可用,来提高构建速度。缓存

4, 用静态的构建配置值来构建你的Debug版

通常地,在你的debug 构建时,为manifest文件或者资源文件配置使用静态/硬编码的值。若是你的manifest或者资源文件的值每次构建都须要动态更新,那么Instant Run 没法执行代码交换-它必须从新构建和安装新的APK。服务器

例如,使用动态的version codes ,version names ,resources或者其余更改manifest文件的构建逻辑,每次你想执行一个修改都会构建所有APK,即便实际的修改可能仅仅只须要热交换。若是这些构建配置是须要动态配置的,那么将它们从你的release 构建变体中分离出来,而且在你的debug 构建中保留它们的静态值。像下面build.gradle 文件显示的这样:app

int MILLIS_IN_MINUTE = 1000 * 60
int minutesSinceEpoch = System.currentTimeMillis() / MILLIS_IN_MINUTE

android {
    ...
    defaultConfig {
        // Making either of these two values dynamic in the defaultConfig will
        // require a full APK build and reinstallation because the AndroidManifest.xml
        // must be updated (which is not supported by Instant Run).
        versionCode 1
        versionName "1.0"
        ...
    }

    // The defaultConfig values above are fixed, so your incremental builds don't
    // need to rebuild the manifest (and therefore the whole APK, slowing build times).
    // But for release builds, it's okay. So the following script iterates through
    // all the known variants, finds those that are "release" build types, and
    // changes those properties to something dynamic.
    applicationVariants.all { variant ->
        if (variant.buildType.name == "release") {
            variant.mergedFlavor.versionCode = minutesSinceEpoch;
            variant.mergedFlavor.versionName = minutesSinceEpoch + "-" + variant.flavorName;
        }
    }
}复制代码

5,用静态的版本依赖

当你在build.gradle文件中声明依赖的时候,你应该避免在版本号结束的地方使用+号,好比:com.android.tools.build:gradle:2.+ 由于Gradle的检查更新,用动态的版本号会致使未知的版本更新、使解决版本的差别变得困难和更慢的构建。你应该使用静态或者硬编码版本号来代替。如:com.android.tools.build:gradle:2.2.2jvm

6,使 on demand 配置为enable 状态

为了让Gradle可以确切的知道该如何构建你的APP,在每次构建以前,构建系统配置工程的全部modules和其余依赖(即便你只想构建或者测试一个modules),这使得大型的多module 工程的构建速度变得很慢。告诉Gradle仅仅配置你想要构建的Modules,用以下步骤使 on demand 配置可用

(1) 在菜单栏上选择 File -> Settings(若是是Mac上 ,选择 Android Studio ->Preferences)

(2) 导航到 Build,Execution,Deployment -> Compiler

(3) check Configure on demand 复选框

(4) 点击 OK

如图:

on_demand.png

7,建立 library 模块

检查你app中的代码,将可模块化的代码抽取一个Android Library module,经过这种方式模块化你的代码将容许构建系统仅仅只编译那些有改动的模块,并将其构建结果缓存下来以被后面的构建使用。一样的配置了 on demand 和 parallel project execution (project 并行执行) 将更加高效(当你打开这些特性时)。

8 为自定义构建逻辑建立Tasks

在你建立了 build profile (build profile 后文会讲)以后,若是显示构建时间相对长的一部分时间花在“configure project(配置工程)阶段,那么请 review 你的build.gradle 脚本,而且查找可包含到自定义Gradle Task中的代码,经过将一些构建逻辑移动到一个task 中,当须要的时候才运行,结果能被缓存用于后续的构建,而且这个构建逻辑能够并行执行(若是你开启了 并行执行project),更多详细信息请阅读Gradle官方文档。 official Gradle documentation

小提示:
若是你的构建包含了大量的自定义任务tasks,你可能想清理你的build.gradle文件,经过自定义task classes (也就是自定义Gradle 插件啦),将你的classes 添加到 project-root/buildSrc/src/main/groovy/目录下,Gradle将自动包含它们到class path ,为项目的全部build.gradle文件。

9,配置 dexOptions 和 开启 library pre-dexing(dex预处理)

先补充一个知识点:Dex-in-process:新发布的Android Studio 2.1增长了一个新的特性:Dex In Process,能够极大的加快从新编译的速度,一样也能提升Instant Run的性能。(第10条优化建议会说到)
详情请看Faster Android Studio Builds with Dex In Process

Android 插件提供了 dexOptions script block ,所以你能够配置相应的 DEX 构建特性,它们能够提升构建速度:

(1)preDexLibraaies : 声明是否对依赖的库进行dex 预处理来使你的增量构建更快速,由于这个特性可能会使你的clean 构建变慢,所以在你的持续集成服务器上你可能想关闭这个特性。

(2) maxProcessCount : 设置最大的线程数量使用当运行 dex-in-process时,默认值是4。

(3)javaMaxHeapSize: 为DEX 编译器 设置最大的堆大小,相对于设置这个属性,你应该增长 Gradle的 堆大小(这个堆大小dex-in-process可用的时候对DEX 编译器有效)

例子:

android {
  ...
  dexOptions {
    preDexLibraries true
    maxProcessCount 8
    // Instead of setting the heap size for the DEX process, increase Gradle's
    // heap size to enable dex-in-process. To learm more, read the next section.
    // javaMaxHeapSize "2048m"
  }
}复制代码

你应该增长它们的值来测试一下这些设置,而后经过profile观察效果,当你为这个进程分配太多资源的时候,可能会获得一个负面的影响。

10. 增长Gradle的堆大小 和开启 dex-in-process

Dex-in-process 容许多个DEX 进程运行在一个单独的VM 中,这使得增量构建和清理构建变得更快。默认状况下,经过Android Studio2.1 或者更高版本建立的新项目分配了足够的内存来开启这个特性,若是你没有使用Android Studio 2.1 或者更高的版本建立项目,你须要给Gradle后台驻扎程序设置至少1536MB 的堆大小内存。默认以下图:

gradle_heap.png

下面的例子在gradle.properties中将Gradle 堆内存大小设置为 2048MB:

org.gradle.jvmargs = -Xmx2048m //设置Gradle 堆大小 2G复制代码

在一些大型的项目上,为Gradle堆分配更多的内存固然更有利,然而,若是你用的是一个小内存的机器,你可能须要给IDE配置更少的内存,想知道如何改变分配给IDE资源的数量和Gradle 对构建表现的影响,请看profiling your build这一条。

若是在你的Module build.gradle 文件中为android.dexOptions.javaMaxHeapSize定义了一个值,那么你须要给Gradle的堆大小设置 的值为比javaMaxHeapSize多512MB,而且知足至少为1536MB。举个例子:在build.gradle中设置javaMaxHeapSize `为1280MB,那么你就要给Gradle堆大小设置 至少1792MB(1280 + 512),固然了,设置大一点更佳。
build.gradle:

dexOptions {
        javaMaxHeapSize "1280m"
    }复制代码

gradle.properties:

org.gradle.jvmargs = -Xmx1792m复制代码

11, 将图片转为 WebP格式

WebP是一种图片文件格式,它提供了像JPEG同样的有损压缩和像PNG同样的透明支持,可是同时它的压缩质量比JPEG或者PNG任何一个都更好,减少Image文件的大小,而不用在构建时作压缩,所以它能提升构建速度,尤为是你的APP使用了大量的图片资源。可是有一点,在解压WebP格式的图片的时候,你的设备的CPU使用将小幅度增长。 用Android Studio 能够很方便的转WebP格式,详情请看convert your images to WebP.

小提示:此外,将工程里面的图片转为webP格式也是优化APK体积的一个方向,webp是Android 原生4.0就开始支持的,它能提供和JPEG和PNG相同质量的图片可是size 更小。没有任何适配问题。

12, 禁止使用 PNG crunching

若是你不能(或者不想)转换你的PNG格式图片为WebP,你仍然能够经过禁止每次构建app都自动压缩图片来提高构建速度,要禁止这项优化,在build.gradle 的添加以下代码:

android {
  ...
  aaptOptions {
    cruncherEnabled false
  }
}复制代码

13, 使用 Instant Run

Instant Run显著的减小了更新app的时间,它经过推送肯定的代码、资源变动而不用构建一个新的app ,而且在一些状况下,甚至不用重启当前的activity,在代码变动后,使用Instant Run 经过点击Apply Changes(黄色⚡️图标)。当你作了以下几步,它会默认打开:

  • 用debug 构建变体来构建你的app
  • Gradle插件的版本 2.3.0或者更高
  • 在module层级的build.gradle中设置minSdkVersion为15或者更高
  • 发布你的app 在Android 5.0(API level 21) 或者更高 点击 Run
    .

14, 使用构建缓存

在构建你的工程的时候,构建缓存存储了Android Gradle插件生成的肯定的产物(如 AAR包和远程依赖的 pre-dexed)。当你使用缓存的时候,你的清理构建更快是由于构建系统后续构建可以简单地重用它们的缓存而不用从新建立。

新的工程使用Android Gradle 插件2.3.0或者更高版本默认就开启了构建缓存(除非你手动关闭了),了解更多请阅读Accelerate clean builds with build cache.

15, 禁止使用注解处理器

Gradle 2.1后能够增量构建Java,当使用注解处理器时增量构建将不可用,若是能够,避免使用注解处理器,让你从只构建更改的类来获益。(提高编译时间)

16, 分析你的构建(Profile your build)

在大型的项目中(或者实现了大量自定义构建逻辑),可能须要更加深刻的了解构建进程来寻找瓶颈,你能够经过分析构建生命周期的各个阶段 每一个gradle task 执行了多长时间。例如:若是你的构建资料显示Gradle 花了大量的时间来配置你的工程,这建议你须要将自定义构建逻辑放在配置阶段以外。另外,若是mergeDevDebugResources 任务 消费了大量的的构建时间,这代表你须要将图片转换为WebP格式或者禁止PNG Crunching(第11,12 条优化建议)

经过构建分析来提高你的构建速度一般须要在分析打开的状况下运行你的构建,屡次修改构建配置,分析和观察结果的变化。

生成和查看 build profile ,执行下面步骤:
1,用Android Studio打开项目,选择 View -> Tool Windows -> Terminal 打开命令行

2,执行clean build 输入下面的命令,当你分析你的构建时,每次构建之间须要执行一个 clean build 操做,由于Gradle会跳过输入没有 改变的tasks,所以,第二个没有改变输入的构建一般会运行得更快由于tasks 没有从新运行,所以在构建之间运行一个cleantask 保证你分析了所有的构建进程。

//若是在Mac 或者 Linux上 用 ./gradlew
gradlew clean复制代码

3,选择其中一个产品风味(product flavor) 执行debug 构建,好比:dev flavor,以下:

gradlew --profile --recompile-scripts --offline --rerun-tasks assembleFlavorDebug复制代码

命令分析:

  • --profile: 开启profiling
  • --recompile-scripts: 强制脚本从新编译跳过cache
  • --offline:禁止 Gradle获取离线依赖,这是确保任何的延迟都是Gradle试图更新依赖而致使,不会误导你的分析数据。你应该先准备好构建一次工程确保Gradle 已经下载好而且缓存依赖。
  • --rerun-tasks:强制Gradle返回全部task 而且忽略任何task 优化。

4,构建结束后,project-root/build/reports/profile/ 目录下:

profile_build.png

5右键点击profile_timestamp.html,选择在浏览器中打开,你就会看到下面这张图,你能够观察报告中的每个tab来了解你的构建,好比Tasks Execution 显示了每个task执行的时间。

profile_in_brower.png

task Execution 显示每一个task的执行时长,以下图:

task_execution.png

6, 可选项:在Project 或者构建配置作出任何修改以前,重复几回步骤3,可是去掉--rerun-tasks标志,因为Gradle 试图节省时间而不会从新执行那些输入没有任何修改的task(它们被标志为UP-TO-DATE 在Task Execution tab下,以下图:),你能够识别哪些任务没有被执行,例如,若是:app:processDevUniversalDebugManifest没有被标记成UP-TO-DATE,那么它表示你的构建配置是每一次构建都动态更新Manifest文件的。然而,有一些task仍是须要每次都执行的,例如::app:checkDevDebugManifest

profile_up_to_date.png

如今,你已经有了一个构建分析报告,你能够开始经过观察构建报告的每个tab来寻找优化时机,一些构建配置是须要试验的,由于在不一样的项目或者工做空间中它们的获益不同。好比,基于大量代码的大型工程,它们可能获益于使用混淆、清除无用代码和缩减APK体积。然而,小型工程可能获益于关闭混淆(混淆仍是挺耗时的)。此外,在第内存的机器上增减Gradle 的堆大小,也有可能起到反面做用。

17,项目组件化

对于大型的项目,可能上面这些优化建议有必定的效果,可是构建速度仍是有些慢,那么就能够考虑组建化了,将项目拆分红一个个单独的组件,开发环境每一个module 都是一个APK,发布的时候,每一个module都是一个lib 给主工程使用。篇幅有效,这里就再也不详细介绍组件化,如今组件化是一个趋势,若是有精力或者有实力,组件化是一个很不错的选择。

最后

以上就是一些解决app构建速度慢的优化建议,若是你以为你的工程构建速度慢,你能够试一下这些优化项。若有问题,欢迎评论区留言。若是你还有什么更好优化建议,也能够在下面留言,我会追加文章后面。

参考

Optimize Your Build Speed
Gradle 官方文档

相关文章
相关标签/搜索