Optimize your build speed

Long build times slow down your development process. This page provides some techniques to help resolve build speed bottlenecks.

The general process of improving your app's build speed is as follows:

  1. Optimize your build configuration by taking a few steps that immediately benefit most Android Studio projects.
  2. Profile your build to identify and diagnose some of the trickier bottlenecks that may be specific to your project or workstation.

When developing your app, deploy to a device running Android 7.0 (API level 24) or higher whenever possible. Newer versions of the Android platform implement better mechanics for pushing updates to your app, such as the Android Runtime (ART) and native support for multiple DEX files.

Note: After your first clean build, you may notice that subsequent builds, both clean and incremental, perform much faster even without using any of the optimizations described on this page. This is because the Gradle daemon has a "warm-up" period of increasing performance—similar to other JVM processes.

Optimize your build configuration

Follow these tips to improve the build speed of your Android Studio project.

Keep your tools up to date

The Android tools receive build optimizations and new features with almost every update. Some tips on this page assume you're using the latest version. To take advantage of the latest optimizations, keep the following up to date:

Avoid compiling unnecessary resources

Avoid compiling and packaging resources that you aren't testing, such as additional language localizations and screen-density resources. Instead, only specify one language resource and screen density for your "dev" flavor, as shown in the following sample:

Groovy

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

Kotlin

android {
    ...
    productFlavors {
        create("dev") {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations("en", "xxhdpi")
        }
        ...
    }
}

Experiment with putting the Gradle Plugin Portal last

In Android, all plugins are found in the google() and mavenCentral() repositories. However, your build might need third-party plugins that are resolved using the gradlePluginPortal() service.

Gradle searches repositories in the order that they're declared, so build performance is improved if the repositories listed first contain most of the plugins. Therefore, experiment with the gradlePluginPortal() entry by putting it last in the repository block in your settings.gradle file. In most cases, this minimizes the number of redundant plugin searches and improves your build speed.

For more information about how Gradle navigates multiple repositories, see Declaring multiple repositories in the Gradle documentation.

Use static build config values with your debug build

Always use static values for properties that go in the manifest file or resource files for your debug build type.

Using dynamic version codes, version names, resources, or any other build logic that changes the manifest file requires a full app build every time you want to run a change, even if the actual change might otherwise require only a hot swap. If your build configuration requires such dynamic properties, then isolate those properties to your release build variants and keep the values static for your debug builds.

See the following example in a build.gradle file:

Groovy

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 app build and reinstallation, because the AndroidManifest.xml
        // must be updated.
        versionCode 1
        versionName "1.0"
        ...
    }

    // The defaultConfig values above are fixed, so your incremental builds don't
    // need to rebuild the manifest.
    // For release builds, it's OK. 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;
        }
    }
}

Kotlin

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

android {
    ...
    defaultConfig {
        // Making either of these two values dynamic in the defaultConfig will
        // require a full app build and reinstallation, because the AndroidManifest.xml
        // must be updated.
        versionCode = 1
        versionName = "1.0"
        ...
    }

    // The defaultConfig values above are fixed, so your incremental builds don't
    // need to rebuild the manifest.
    // For release builds, it's OK. The following script iterates through
    // all the known variants, finds those that are "release" build types, and
    // changes those properties to something dynamic.
    applicationVariants.forEach { variant ->
        if (variant.buildType.name == "release") {
            variant.mergedFlavor.versionCode = minutesSinceEpoch
            variant.mergedFlavor.versionName = minutesSinceEpoch + "-" + variant.flavorName
        }
    }
}

Use static dependency versions

When you declare dependencies in your build.gradle files, avoid using dynamic version numbers (those with a plus sign at the end, such as 'com.android.tools.build:gradle:2.+'). Using dynamic version numbers can cause unexpected version updates, difficulty resolving version differences, and slower builds caused by Gradle checking for updates. Use static version numbers instead.

Create library modules

Look for code in your app that you can convert into an Android library module. Modularizing your code this way allows the build system to compile only the modules you modify and cache those outputs for future builds. Modularization also makes parallel project execution more effective when you enable that optimization.

Create tasks for custom build logic

After you create a build profile, if the build profile shows that a relatively long portion of the build time is spent in the **Configuring Projects** phase, review your build.gradle scripts and look for code to include in a custom Gradle task. By moving some build logic into a task, you help ensure that the task runs only when required, results can be cached for subsequent builds, and that build logic becomes eligible to run in parallel if you enable parallel project execution. To learn more about taks for custom build logic, read the official Gradle documentation.

Tip: If your build includes a large number of custom tasks, you might want to declutter your build.gradle files by creating custom task classes. Add your classes to the project-root/buildSrc/src/main/groovy/ directory; Gradle automatically includes those classes in the classpath for all build.gradle files in your project.

Convert images to WebP

WebP is an image file format that provides lossy compression (like JPEG) as well as transparency (like PNG). WebP can provide better compression than either JPEG or PNG.

Reducing image file sizes without having to perform build-time compression can speed up your builds, especially if your app uses a lot of image resources. However, you may notice a small increase in device CPU usage while decompressing WebP images. Use Android Studio to easily convert your images to WebP.

Disable PNG crunching

If you don't convert your PNG images to WebP, you can still speed up your build by disabling automatic image compression every time you build your app.

If you're using Android Gradle plugin 3.0.0 or higher, PNG crunching is disabled by default for the "debug" build type. To disable this optimization for other build types, add the following to your build.gradle file:

Groovy

android {
    buildTypes {
        release {
            // Disables PNG crunching for the "release" build type.
            crunchPngs false
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Disables PNG crunching for the "release" build type.
            isCrunchPngs = false
        }
    }
}

Because build types or product flavors don't define this property, you need to manually set this property to true when building the release version of your app.

Experiment with the JVM parallel garbage collector

Build performance can be improved by configuring the optimal JVM garbage collector used by Gradle. While JDK 8 is configured to use the parallel garbage collector by default, JDK 9 and higher are configured to use the G1 garbage collector.

To potentially improve build performance, we recommend testing your Gradle builds with the parallel garbage collector. In gradle.properties set the following:

org.gradle.jvmargs=-XX:+UseParallelGC

If there are other options already set in this field, add a new option:

org.gradle.jvmargs=-Xmx1536m -XX:+UseParallelGC

To measure build speed with different configurations, see Profile your build.

Increase the JVM heap size

If you observe slow builds, and in particular the garbage collection takes more than 15% of the build time in your Build Analyzer results, then you should increase the Java Virtual Machine (JVM) heap size. In the gradle.properties file, set the limit to 4, 6, or 8 gigabytes as shown in the following example:

org.gradle.jvmargs=-Xmx6g

Then test for build speed improvement. The easiest way to determine the optimal heap size is to increase the limit by a small amount and then test for sufficient build speed improvement.

If you also use the JVM parallel garbage collector, then the entire line should look like:

org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g

You can analyze the JVM memory errors by turning the HeapDumpOnOutOfMemoryError flag on. By doing so, the JVM will generate a heap dump, when running out of memory.

Use non-transitive R classes

Use non-transitive R classes to have faster builds for apps with multiple modules. Doing so helps prevent resource duplication by ensuring that each module's R class only contains references to its own resources without pulling references from its dependencies. This leads to faster builds and the corresponding benefits of compilation avoidance. This is the default behavior in Android Gradle plugin 8.0.0 and higher.

Starting with Android Studio Bumblebee, non-transitive R classes are on by default for new projects. For projects created with earlier versions of Android Studio, update them to use non-transitive R classes by going to Refactor > Migrate to Non-Transitive R Classes.

To learn more about app resources and the R class, see App resources overview.

Disable the Jetifier flag

Since most projects use AndroidX libraries directly, you can remove the Jetifier flag for better build performance. To remove the Jetifier flag, set android.enableJetifier=false in your gradle.properties file.

The Build Analyzer can perform a check to see whether the flag can be safely removed to enable your project to have better build performance and migrate away from the unmaintained Android Support libraries. To learn more about the Build Analyzer, see Troubleshoot build performance.

Use configuration caching (experimental)

Configuration caching is an experimental feature that allows Gradle to record information about the build tasks graph and reuse it in subsequent builds, so it doesn't have to reconfigure the whole build again.

To enable configuration caching, follow these steps:

  1. Check that all project plugins are compatible.

    Use the Build Analyzer to check whether your project is compatible with configuration caching. The Build Analyzer runs a sequence of test builds to determine whether the feature can be turned on for the project. See issue #13490 for a list of plugins that are supported.

  2. Add the following code to the gradle.properties file:

      org.gradle.unsafe.configuration-cache=true
      # Use this flag carefully, in case some of the plugins are not fully compatible.
      org.gradle.unsafe.configuration-cache-problems=warn

When configuration caching is enabled, the first time you run your project the build output should say Calculating task graph as no configuration cache is available for tasks. During subsequent runs, the build output should say Reusing configuration cache.

To learn more about configuration caching, see the blog post Configuration caching deep dive and the Gradle documentation about configuration caching.