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:

Create a build variant for development

Many of the configurations you need when preparing your app for release are not
required while developing your app. Enabling unnecessary build processes
slows down your incremental and clean builds, so configure a build variant that keeps
only the build configurations you need while developing your app. The
following sample creates a "dev" flavor and a "prod" flavor (for your release
version configurations):

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 when building from the command line,
// set minSdkVersion to 21 or higher. When using Android Studio 2.3 or higher,
// the build automatically avoids legacy multidex when deploying to a device running
// API level 21 or higher—regardless of what you set as your minSdkVersion.
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.
}
}
}

If your build configuration already uses product flavors to create different
versions of your app, you can combine the "dev" and "prod" configurations
with those flavors by using flavor
dimensions. For example, if you already configure a "demo" and "full"
flavor, you can use the following sample configuration to create combined
flavors, such as "devDemo" and "prodFull":

Enable single-variant project sync

Syncing your project with your build configuration is an
important step in letting Android Studio understand how your project is structured. However, this
process can be time-consuming for large projects. If your project uses multiple build variants,
you can now optimize project syncs by limiting them to only the variant you have currently
selected.

You need to use Android Studio 3.3 or higher with Android Gradle Plugin 3.3.0 or higher
to enable this optimization. The optimization is enabled by default on all projects.

Note: This optimization fully supports projects that include Java and C++ languages, and
has some support for Kotlin. When enabling the optimization for projects with Kotlin content,
Gradle sync falls back to using full variants internally.

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:

Disable automatic build ID generation

If you want to use Crashlytics with your debug builds, you can still speed up
incremental builds by preventing Crashlytics from updating app resources with
its own unique build ID during every build. Because this build ID is stored in
a resource file that is referenced by the manifest, disabling automatic build
ID generation also allows you to
use Apply Changes alongside Crashlytics
for your debug builds.

To prevent Crashlytics from automatically updating its build ID, add
the following to your build.gradle file:

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 APK 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, as shown in the
build.gradle file below.

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.

Enable offline mode

If you are on a slow network connection, your build times may suffer when Gradle
attempts to use network resources to resolve dependencies. You can tell Gradle to
avoid using network resources by using only the artifacts that it has cached locally.

To use Gradle offline when building with Android Studio, proceed as follows:

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:

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.

Enable the build cache

Build cache stores certain outputs that the Android plugin for Gradle
generates when building your project (such as unpackaged AARs and pre-dexed
remote dependencies). Your clean builds are much faster while using the cache
because the build system can simply reuse those cached files during
subsequent builds, instead of recreating them.

To get started, see the following list of popular annotation processors that support
incremental annotation processing. For a more complete list, see
State of support in popular annotation processors. Some of the annotation processors
may require additional steps to enable the optimization, so make sure to read documentation for
each annotation processor.

Additionally, if you use Kotlin in your app, you need to use
kapt 1.3.30 and higher to support incremental annotation processors for your Kotlin code.
Make sure to read the official documentation on whether you need to manually enable this behavior.

Keep in mind, if you have to use one or more annotation processors that don't support incremental
builds, annotation processing won't be incremental. However, if your project is using kapt, Java
compilation is still incremental.

Profile your build

Larger projects, or those that implement a lot of custom build logic, may
require you to take a deeper look into the build process to find bottlenecks.
You can do that by profiling how long Gradle takes to execute each phase of
the build lifecycle and each build task. For example, if your build profile
shows that Gradle is spending too much time configuring your project, it may
suggest that you need to move custom build
logic out of the configuration phase. Additionally, if the
mergeDevDebugResources task consumes a large amount of the build
time, it may indicate that you need to either convert
your images to WebP or disable PNG
crunching.

If you're using
Android Studio 4.0 or higher, the best way to investigate build performance issues is by
using the Build Analyzer.

Profile your build from the command line

If you're not using Android Studio, troubleshooting your build speed typically involves running
your build from the command line with profiling enabled, making some tweaks to your build
configuration, and profiling some more to observe the results of your changes.

To generate and view a build profile, perform the following steps:

Open a commandline terminal at the root of your project.

Perform a clean build by entering the following command. As you profile
your build, you should perform a clean build between each build you profile
because Gradle skips tasks when inputs to a task (such as source code) don't
change. Thus, a second build with no input changes always runs faster because
tasks are not being re-run. So running the clean task between
your builds ensures that you profile the full build process.

// On Mac or Linux, run the Gradle wrapper using "./gradlew".
gradlew clean

Execute a debug build of one of your product flavors, such as the "dev" flavor,
with the following flags:

gradlew --profile --offline --rerun-tasks assembleFlavorDebug

--profile: Enables profiling.

--offline: Disables Gradle from fetching online
dependencies. This makes sure than any delays caused by Gradle
attempting to update your dependencies don't interfere with your
profiling data. You should have already built your project once to make
sure Gradle has already downloaded and cached your dependencies.

--rerun-tasks: Forces Gradle to rerun all tasks and ignore
any task optimizations.

Figure 1. Project view indicating the location of
profile reports.

After the build completes, use the Project window navigate to the
project-root/build/reports/profile/ directory (as
shown in figure 1).

Right-click the profile-timestamp.html file and select
Open in Browser > Default. The report should look similar to the one
shown in figure 2. You can inspect each tab in the report to learn about your
build, such as the Task Execution tab which shows how long Gradle took
to execute each build task.

Figure 2. Viewing a report in a browser.

Optional: Before making any changes to your project or build
configuration, repeat the command in step 3, but omit the
--rerun-tasks flag. Because Gradle attempts to save time by not
re-executing tasks whose inputs haven't changed (these are indicated as
UP-TO-DATE in the Task Execution tab of the report, as shown
in figure 3), you can identify which tasks are performing work when they
shouldn't be. For example, if the
:app:processDevUniversalDebugManifest is not marked as
UP-TO-DATE, it may suggest that your build configuration is
dynamically updating the manifest with every build. However, some tasks need to
run during each build, such as :app:checkDevDebugManifest.

Figure 3. Viewing task execution results.

Now that you have a build profile report, you can begin looking for
optimization opportunities by inspecting the information in each tab of the
report. Some build settings require experimentation because the benefits may
differ between projects and workstations. For example, projects with a large
codebase may benefit from code shrinking
to remove unused code and shrink the APK size. However, smaller
projects may benefit more from disabling code shrinking altogether. Additionally,
increasing the Gradle heap size (using
org.gradle.jvmargs) might negatively impact performance on low-memory machines.

After making a change to your build configuration, observe the results of
your changes by repeating the steps above and generating a new build profile.
For example, figure 4 shows a report for the same sample app after applying
some of the basic optimizations described in this page.