Optimize your build speed

Stay organized with collections Save and categorize content based on your preferences.

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

The general process of improving your 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, you should 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—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, and 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). You can do that by only specifying 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/hard-coded values for properties that go in the manifest file or resource files for your debug build type.

For example, 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 though the actual change might otherwise require only a hot swap. If your build configuration requires such dynamic properties, then isolate them to your release build variants and keep the values static for your debug builds. For an example, see , as shown in the build.gradle file below.

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 (and therefore the whole app, 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;
        }
    }
}

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 (and therefore the whole app, 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.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, you should avoid using version numbers 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. You should use static/hard-coded 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. It 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 it 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 that you can include in a custom Gradle task. By moving some build logic into a task, it is run 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, read the official Gradle documentation.

Tip: If your build includes a large number of custom tasks, you may want to declutter your build.gradle files by creating custom task classes. Add you classes to the project-root/buildSrc/src/main/groovy/ directory and Gradle automatically includes them 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) but 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. Using Android Studio, you can easily convert your images to WebP.

Disable PNG crunching

If you can't (or don't want to) 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 plugin 3.0.0 or higher, PNG crunching is disabled by default for only 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.

Configure the JVM 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.

Use non-transitive R classes

You should use non-transitive R classes to have faster builds for applications 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.

Starting with Android Studio Bumblebee, non-transitive R classes are on by default for new projects. For projects created with earlier versions of Studio, you can 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

Most projects use AndroidX libraries directly, so you can probably remove the Jetifier flag for better build performance. To remove the Jetifier check, set android.enableJetifier=false in your gradle.properties file. The Build Analyzer can perform a check to see if 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 resuse 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. It runs a sequence of tests builds to determine if the feature can be turned on for the project. You can also 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 sparingly, in case some of the plugins are not fully compatible
      org.gradle.unsafe.configuration-cache-problems=warn

  3. 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.