At Google I/O 2013 Google announced some new developer tools including the new Android Studio IDE, and a new build system based around Gradle. In this series we’ll have a look at the Gradle build system, and look at how to convert existing projects to use the new build system.
Previously on Styling Android we’ve looked at using Maven for automating your builds, and we saw how Maven’s dependency management system can make life much easier. When using Maven, it can be advantageous in many cases to have your own maven repository management system (such as Nexus) to which you can deploy build artifacts so that they can be used across multiple projects. Gradle supports Maven repositories for retrieving dependencies (as well as other mechanisms such as Ivy) which allows much easier migration from other build systems.
Similarly to Maven and Ant, the Gradle build system requires of some additional config files which provide the build definition. It is entirely possible to have any combination of Ant, Maven, and Gradle builds configured for a project without interfering with each other, so it is possible to gently transition over to the new build system. So you are able to use both in parallel until you have the Gradle build working as well as or better than your existing build.
We will cover how to convert existing projects to use Gradle later in this series, but we’ll start with creating a new project, and analysing what gets created in order to understand a little about how the new build system works. Android Studio is fully integrated with the Gradle build system, so let’s use that to create a new project. Select “File|New Project…”, then we’ll set up a basic project named “GradleTest” with a package name of com.stylingandroid.gradle
with a minSDK of API7 (2.1 Eclair), a targetSDK of API16 (4.1 Jelly Bean), and compiled with API17 (4.2 Jelly Bean). The rest of the details are unimportant because we’re interested in the build configuration and directory structure, and not the actual code.
This creates a new directory tree with a root folder named “GradleTestProject” this top level directory is actually a project parent, which allows us to create a multi-module project containing a number of distinct components such as library projects and APK projects. The actual APK project that we just created resides within this and is named “GradleTest”.
To begin with, let’s look at the parent project. As well as the “GradleTest” sub-project that it contains, there are some other things here. First there is a “.idea’ folder and a files named “GradleTestProject.iml” which are both files specific to Android Studio. We’re not covering Android Studio here, so we’ll ignore those.
There is a file named “local.properties” which is essentially the same as “project.properties” in the Eclipse based build.
There are a couple of files named “gradlew” and “gradlew.bat”, and a folder named “gradle”. These are the components of a Gradle wrapper which gets included within our project. The purpose of the wrapper is to provide a Gradle runtime which is included within the project and enables anyone to check out the code and perform a build without having to first install Gradle. This also ensures that the correct version of Gradle is used to perform the build and using the wrapper to perform the build can often save a lot of problems if an incompatible version of Gradle is installed on the computer performing the build. The “gradlew” and “gradlew.bat” files are *nix and Windows (respectively) scripts to launch the wrapper.
Finally we have two files named “build.gradle” and “settings.gradle” which are the actual build configuration and we’ll look at these in greater detail later in the series.
For now let’s look at how we can perform a build from the command line. To do this we need to invoke the gradle wrapper by calling the appropriate wrapper script file: “gradlew” for Linux / MacOS; “gradlew.bat” for Windows. We need to supply the names of the tasks that we wish to perform. In this case we want to do a “clean” (to remove any build artifacts from previous builds) followed by “assemble” which will build our project and produce an APK:
GradleTestProject >./gradlew clean assemble The TaskContainer.add() method has been deprecated and is scheduled to be removed in Gradle 2.0. Please use the create() method instead. :GradleTest:clean :GradleTest:prepareDebugDependencies :GradleTest:compileDebugAidl :GradleTest:generateDebugBuildConfig :GradleTest:mergeDebugAssets :GradleTest:compileDebugRenderscript :GradleTest:mergeDebugResources :GradleTest:processDebugManifest :GradleTest:processDebugResources :GradleTest:compileDebug :GradleTest:dexDebug :GradleTest:processDebugJavaRes UP-TO-DATE :GradleTest:validateDebugSigning :GradleTest:packageDebug :GradleTest:assembleDebug :GradleTest:prepareReleaseDependencies :GradleTest:compileReleaseAidl :GradleTest:generateReleaseBuildConfig :GradleTest:mergeReleaseAssets :GradleTest:compileReleaseRenderscript :GradleTest:mergeReleaseResources :GradleTest:processReleaseManifest :GradleTest:processReleaseResources :GradleTest:compileRelease :GradleTest:dexRelease :GradleTest:processReleaseJavaRes UP-TO-DATE :GradleTest:packageRelease :GradleTest:assembleRelease :GradleTest:assemble BUILD SUCCESSFUL Total time: 12.42 secs GradleTestProject >
At a first glance, 12 seconds seems rather slow for a build of a really simple project with very little code in it. Fortunately there are a couple of little things that we can do to speed this up. The first is to make use of Gradle’s incremental build which, will perform a checksum upon each file to determine whether that file needs to be re-built, or whether we can reuse a build product from a previous build. When we do a “clean” we remove all previous build products, so just running “assemble” on it’s own will result in a much faster build the second time:
GradleTestProject >./gradlew assemble The TaskContainer.add() method has been deprecated and is scheduled to be removed in Gradle 2.0. Please use the create() method instead. :GradleTest:prepareDebugDependencies :GradleTest:compileDebugAidl UP-TO-DATE :GradleTest:generateDebugBuildConfig UP-TO-DATE :GradleTest:mergeDebugAssets UP-TO-DATE :GradleTest:compileDebugRenderscript UP-TO-DATE :GradleTest:mergeDebugResources UP-TO-DATE :GradleTest:processDebugManifest UP-TO-DATE :GradleTest:processDebugResources UP-TO-DATE :GradleTest:compileDebug UP-TO-DATE :GradleTest:dexDebug UP-TO-DATE :GradleTest:processDebugJavaRes UP-TO-DATE :GradleTest:validateDebugSigning :GradleTest:packageDebug UP-TO-DATE :GradleTest:assembleDebug UP-TO-DATE :GradleTest:prepareReleaseDependencies :GradleTest:compileReleaseAidl UP-TO-DATE :GradleTest:generateReleaseBuildConfig UP-TO-DATE :GradleTest:mergeReleaseAssets UP-TO-DATE :GradleTest:compileReleaseRenderscript UP-TO-DATE :GradleTest:mergeReleaseResources UP-TO-DATE :GradleTest:processReleaseManifest UP-TO-DATE :GradleTest:processReleaseResources UP-TO-DATE :GradleTest:compileRelease UP-TO-DATE :GradleTest:dexRelease UP-TO-DATE :GradleTest:processReleaseJavaRes UP-TO-DATE :GradleTest:packageRelease UP-TO-DATE :GradleTest:assembleRelease UP-TO-DATE :GradleTest:assemble UP-TO-DATE BUILD SUCCESSFUL Total time: 4.059 secs GradleTestProject >
Notice how many of the steps are followed by “UP-TO-DATE” which means that Gradle did not need to re-compile, or rebuild these components. The main overhead that’s left is that each time we run Gradle, it is having to initialise itself. We can actually tell Gradle to keep itself in memory, and each invocation from the command line will re-use this in-memory version of Gradle. We do this by specifying the “–daemon” flag on the command line. This makes little difference the first time we run it because Gradle still needs to be loaded to memory, but on the second invocation with this flag we get another performance improvement:
GradleTestProject >./gradlew assemble --daemon The TaskContainer.add() method has been deprecated and is scheduled to be removed in Gradle 2.0. Please use the create() method instead. :GradleTest:prepareDebugDependencies :GradleTest:compileDebugAidl UP-TO-DATE :GradleTest:generateDebugBuildConfig UP-TO-DATE :GradleTest:mergeDebugAssets UP-TO-DATE :GradleTest:compileDebugRenderscript UP-TO-DATE :GradleTest:mergeDebugResources UP-TO-DATE :GradleTest:processDebugManifest UP-TO-DATE :GradleTest:processDebugResources UP-TO-DATE :GradleTest:compileDebug UP-TO-DATE :GradleTest:dexDebug UP-TO-DATE :GradleTest:processDebugJavaRes UP-TO-DATE :GradleTest:validateDebugSigning :GradleTest:packageDebug UP-TO-DATE :GradleTest:assembleDebug UP-TO-DATE :GradleTest:prepareReleaseDependencies :GradleTest:compileReleaseAidl UP-TO-DATE :GradleTest:generateReleaseBuildConfig UP-TO-DATE :GradleTest:mergeReleaseAssets UP-TO-DATE :GradleTest:compileReleaseRenderscript UP-TO-DATE :GradleTest:mergeReleaseResources UP-TO-DATE :GradleTest:processReleaseManifest UP-TO-DATE :GradleTest:processReleaseResources UP-TO-DATE :GradleTest:compileRelease UP-TO-DATE :GradleTest:dexRelease UP-TO-DATE :GradleTest:processReleaseJavaRes UP-TO-DATE :GradleTest:packageRelease UP-TO-DATE :GradleTest:assembleRelease UP-TO-DATE :GradleTest:assemble UP-TO-DATE BUILD SUCCESSFUL Total time: 1.852 secs GradleTestProject >
So, we have a basic Gradle build defined and working from the command line, and have seen a couple of tricks for improving performance. In the next article we’ll start looking a little deeper in to the build to see what’s happening.
The source code for this article is available here.
© 2013 – 2014, Mark Allison. All rights reserved.
Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
Great article – thanks! One small correction – when doing the incremental build, Gradle is using checksums, not timestamps (as those are not guaranteed to be correct) to determine which resources need to be rebuilt.
Thanks for the information. I’ve updated the article accordingly.
Is it possible to use these improvements in android studio?How?
If you create a Gradle build configuration for your project, and then import that project into Android Studio, then Android Studio will pick up the Gradle configuration automatically. Therefore if you have, for example, a non-standard directory structure, provided you configure your Gradle build correctly, then Android Studio will pick up that configuration when you import the project.
Loving this series of articles.
I’m using Android Studio to follow but when I tried to create a Gradle project I found that Android Studio did not create the settings.gradle file for me. I could find no solution other than to uninstall and reinstall Android Studio. This did the trick perfectly and I was then able to create gradle projects correctly. Thought I’d drop it in just in-case any other readers had the same problem.
Keep up the good work!
Running ‘./gradlew clean assemble’ is getting ‘bash: ./gradlew: Permission denied’ error in Android Studio Terminal. When tried ‘sudo ./gradlew clean assemble’, I am getting ‘sudo: ./gradlew: command not found’
you should use this command in Terminal:
chmod +x gradlew
After that, you issue command:
./gradlew clean assemble