Is building from the terminal faster than Android Studio?

Nelson Osacky
Sep 28 · 5 min read

A couple people have sworn to me that building from command line is faster than building from Android Studio. I have also heard that Android Studio is faster than command line. Can this really be true?

This blog post dives in to a few reasons why builds might behave differently in Android Studio vs the terminal.

Injected Properties

Android Studio build is built with Gradle just like any other build. But it adds some additional information to each build by injecting some special project properties.

You can see all the properties that are injected by Android Studio by adding the following to your build.gradle.

for (def prop : project.properties) {
if (prop.key.startsWith('android.injected')) {
println(prop)
}
}

This will yield the following properties when invoked from the IDE.

android.injected.invoked.from.ide=true
android.injected.build.abi=x86
android.injected.build.density=420dpi
android.injected.build.api=28

android.injected.invoked.from.ide=true

This property is set to true when running a build from the IDE. The property does not exist when running from the terminal. You can use this to tag your build scans.

buildScan {
if (project.hasProperty("android.injected.invoked.from.ide")) {
tag 'AndroidStudio'
} else {
tag 'Cmd'
}
}

If you don’t use build scans, you can send this information to whatever tool you use to keep track of build speeds in order to determine if Android Studio builds are faster than terminal builds for your project.

The Android Gradle Plugin (AGP) looks at the other properties to try to avoid unnecessary work and optimize the build.

android.injected.build.abi=x86

If you have native libraries (C/C++), theExternalNativeBuildTask in AGP will read the injected ABI property to skip building unnecessary binaries. In this caseandroid.injected.build.abiis set to =x86 so we skip building binaries for ARM.

android.injected.build.api=28

Using the injected API property, in this case 28, the Android Gradle Plugin can make dexing optimizations for Instant Run. This property is used in InstantRunPatchingPolicy. AGP can use a faster patching policy if the target device is using ART instead of Dalvik.

Even if Instant Run isn’t running, AGP can upgrade from legacy multi-dex to native multidex in TaskManager if the project’s min sdk < 21 but the targeted device’s min SDK > 21.

// Upgrade from legacy multi-dex to native multi-dex if possible when using with a device
if (dexingType == DexingType.LEGACY_MULTIDEX) {
if (variantScope.getVariantConfiguration().isMultiDexEnabled()
&& variantScope
.getVariantConfiguration()
.getMinSdkVersionWithTargetDeviceApi()
.getFeatureLevel()
>= 21) {
dexingType = DexingType.NATIVE_MULTIDEX;
}
}

android.injected.build.density=420dpi

Using the density property, AGP find the right split APK by screen density in SplitOutputMatcher. A smaller APK will strip out unnecessary resources in order to reduce APK transfer and installation time.

Instant Run

Android Studio also injects a special property when invoking Instant Run.

android.optional.compilation=INSTANT_DEV

This property is defined in the enumOptionalCompilationStep. AGP will use the Instant Run mode optimized for the target device’s SDK version by looking at the android.injected.build.api injected property.

Instant Run disables Gradle’s build cache. Gradle’s build cache stores the outputs of tasks and builds to reuse them later and avoid rebuilding things which are already built. Instant run completely ignores the Gradle build cache. Because of this, building from the IDE and switching branches means you could end up spending a lot of time rebuilding.

I would dive deeper in to Instant Run, but I think it isn’t worth spending too much time on since it was removed in Android Gradle Plugin 3.5.0. You won’t have to worry about it slowing down your build anymore.

If you are using Android Gradle Plugin 3.4.0, you can ensure that your fellow developers are not plagued by slow or inconsitent Instant Run builds by adding the following code to your build.gradle:

def instantRun = project.getProperties().get("android.optional.compilation")?.contains("INSTANT_DEV")if (instantRun) {  throw new IllegalStateException("Disable Instant Run: Android Studio -> Preferences -> Build, Execution, Deployment -> Instant Run")}

Built-in JDK

Android Studio comes bundled with a JDK and uses it by default. Your terminal is likely pointed to a different JDK installation. A different JDK makes the Gradle Daemon incompatible between Android Studio and the terminal which breaks incremental compilation. If you notice really slow builds when switching back and forth it might be caused by this incompatibility.
To test for the incompatibility you can switch from Android Studio to the terminal. If you see the following message you may have an incompatibility.

Starting a Gradle Daemon, 1 incompatible and 0 stopped Daemons could not be reused, use — status for details

Svyatoslav Chatchenko’s post covers how to make your Daemon compatible.

The instructions have changed a bit for Android Studio 3.5.0. You need to open Project Structure -> SDK location.

You can ignore the message that says “To use the same Gradle daemon…select JAVA_HOME.” In Android Studio 3.5.0, this will not work.

JDK Location

When you open the dropdown, selecting the JAVA_HOME option will give you the same JDK location as the embedded JDK. This is a known issue in Android Studio.
Because of this bug, you’ll need to copy paste your JAVA_HOME in the setting. You can obtain your JAVA_HOME with the following command:

echo $(/usr/libexec/java_home)

Once you have entered your JAVA_HOME you shouldn’t get an error message indicating that the Gradle Daemons are incompatible when switching between Android Studio and the terminal. If you still get incompatibilities, you can find more information on how to debug it here.

Which is faster?

I didn’t really answer the question of which is faster. The thing is, depending on the project configuration, the size of the project, and many other factors one might be faster than the other. It’s important to minimize the differences to ensure consistent builds. It’s also important to measure build speeds. These are just a few differences between building from Android Studio and the terminal. There may be more and things have changed in Android Studio 3.5.

At SoundCloud, builds from the terminal and Android Studio for the assembleDebug task in our main app were both just about 58 seconds on average in the last 28 days.

Edit: A couple people have pointed out that this is a clickbait-y title and I didn’t provide sufficient metrics in this post. Thank you for challenging me. It made me realize that even the assembleDebug metrics I provided don’t capture the full picture. Ideally we should also measure app install time because Instant Run optimizes the installation process by stripping out unnecessary pieces from the APK. However we cannot compare installDebug in this case because Android Studio doesn’t use the installDebug task. It instead installs the APK by directly calling ADB. This makes it a bit more difficult to compare the two builds. If anyone has thoughts or ideas on how to better measure this, please let me know. I’ll keep digging.

Thanks to Tom Sammons for inspiring and proofreading this post.
Thanks to John Rodriguez for this gist to easily examine AGP sources and also proofreading this blog post.

Need some help improving Gradle build speeds? I’m available for help!
Did I help improve your build speed? Buy me a chocolate. nom nom

Nelson Osacky

Written by

Android @ SoundCloud in Berlin. Former Square. Lover of chocolate and running. osacky.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade