Android & Gradle Tutorial

knowventBanner

Gradle is the new favorite when it comes to automating your build process. If you are like me you have probably found yourself searching all over the web to figure out how to use this tool. In this gradle tutorial, I will explain the What, Why, Where, When and How of this flexible build platform.

The Build Files

Before I get into the Gradle code, there are two files you need to concern yourself with, settings.gradle and build.gradle The settings.gradle file lives in the root directory of the source tree and specifies which projects to include in the build. source This file is only necessary if you have a multi-project build, like the one I am detailing in this tutorial. The build.gradle file is where you will describe the build rules. Both files are use a domain specific language based on Groovy. Gradle has a well documented DSL reference guide if you are interested.

Note: Gradle also uses a file called gradle.properties. This file is used to set properties for your build environment. You can read more about is here. For the purpose of this tutorial I will not be covering this file’s usage.

Gradle Plugins

“Gradle is a general purpose tool that is capable of building and automating anything.”Gradle team

Gradle’s primary purpose is to process builds while being platform agnostic. To provide this, Gradle implements inversion of control through its use of plugins. This was a little confusing to me since I was expecting Gradle to be specifically used just for Java/Android builds. I wasn’t expecting to be required to add other components to get Gradle to build my Android project. Android has a couple of plugins as does Java, Google App Engine and Javascript. Regardless, specifying the plugin to use in my build only required a couple of lines of code.

My project is not simple nor is it following best practices when it comes to library and jar dependency management. However, I wanted to be able to describe the build process using this flexible and declarative platform without changing the project configuration to confirm to my build platform. I was happy to find out that Gradle support such scenarios.

The Application

The directory structure doesn’t dictate how the Gradle builds are performed. However, I want to describe the structure I used to provide some context around by build files and their locations:

Directory structure

  • Workspace Root Dir
    • Hello Gradle Android Project
    • Referenced Projects Folder
      • ActionBarSherlock Library
      • SlidingMenu Library
      • SwitchBackport Library
      • GoogleStyle Library
      • GoogleServices Library

Below is the dependency structure:

  • Hello Gradle Project is Dependant on:
    • SlidingMenu
      • ActionBarSherlock is Dependant on:
        • android-support-v4.jar
    • SwitchBackport
    • GoogleStyle
    • GoogleServices
      • android-support-v4.jar

Starting with Gradle Init

Before you can do anything you will need to install Gradle on your development environment. There is a great How to install Gradle found here. Each project that will need built will need to be initialized. This is done by executing gradle init command on each of your route project folders. This essentially creates an empty build.gradle file and adds the Gradle wrapper files (gradlew.bat and gradlew).

We already covered the *.gradle file types. The wrapper files allow you to execute gradle builds on systems that lacking the gradle software. This also makes it possible to ensure everyone is using the same version of the build system without sending out notifications to tell everyone to update their individual development environment. Also, since you committed the wrapper files, you will be able to build a older commits using that commit’s Gradle version.

The Primary build.gradle File (Hello Gradle Android Project)

BuildScript Section

The first section found in your build.gradle is the BuildScript:

buildscript {
    repositories {
        mavenCentral()
        maven { url 'http://download.crashlytics.com/maven' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.7.+'
        classpath 'com.crashlytics.tools.gradle:crashlytics-gradle:1.+'
    }
}

This section sets up the metadata needed specifically just for Gradle like locating and downloading your referenced plugins.

Repositories object defines the repositories to use to search for plugins and dependencies.

dependencies object defines external libraries that is required to execute the build, usually required for the plugins used.

Plugins Section

apply plugin: 'android'

Here I am using one plugin to execute this project build called android. Note: Other Android projects that are designated as a library project will use android-library plugin instead. Since this is the root/application project android plugin is used.

Project Dependencies

dependencies {
    //References all jars except android-support-v4.jar.  This jar will be reference via actionbarsherlock
    compile fileTree(dir: 'HelloGradle/libs', include: '*.jar', exclude: '**/android-support-v4.jar')
    compile fileTree(dir: 'SupportingLib/google-play-services_lib/libs', include: '*.jar')
    //This project path string are defined in the setting.gradle file
    compile project(path: ':libraries:google-play-services_lib')
    compile project(path: ':libraries:actionbarsherlock')
    compile project(path: ':libraries:SlidingMenu-jfeinstein')
    compile project(path: ':libraries:switch-backport')
    compile project(path: ':libraries:google-style')
}

Above is the Hello Gradle project dependencies. There are two methods used to describe the location/type of the dependencies to compile: fileTree and project. This simply tells Gradle to “Include” references to these resource and compile them before you compile the Hello Gradle project. fileTree method denotes dependence to files found (or not found) in a directory. The project method points to a project name defined in the settings.gradle file (more on the settings.build file later).

Defined Methods

def computeVersionName() {
    return "2.0.0." + getDate()
}
def getDate() {
    def date = new Date()
    def formattedDate = date.format('yyyyMMddHHmm')
    return formattedDate
}

These are two custom defined method I created in my build process. computeVersionName() returns a version/build name unique for each time Gradle is execute. This is done by using the getDate() method that returns a format date string to append to the version name.

Android Project Settings

android {
    sourceSets {
        main {
            manifest.srcFile 'HelloGradle/AndroidManifest.xml'
            java.srcDirs = ['HelloGradle/src']
            resources.srcDirs = ['HelloGradle/src']
            aidl.srcDirs = ['HelloGradle/src']
            renderscript.srcDirs = ['HelloGradle/src']
            res.srcDirs = ['HelloGradle/res']
            assets.srcDirs = ['HelloGradle/assets']
        }
    }
    compileSdkVersion 19
    buildToolsVersion "19.0.0"

    defaultConfig {
        versionCode 4
        versionName computeVersionName()
        minSdkVersion 8
        targetSdkVersion 16
    }
    signingConfigs {
        // It's not necessary to specify, but I like to keep the debug keystore
        // in SCM so all our debug builds (on all workstations) use the same
        // key for convenience
        debug {
            storeFile file("Keystores/debug.keystore")
        }
        release {
            storeFile file("Keystores/production.keystore")
            storePassword "storepass"
            keyAlias "alias"
            keyPassword "keypass"

        }
    }
    buildTypes {
        debug {
            //packageNameSuffix ".debug"  
            packageNameSuffix ""

        }
        release {
            signingConfig signingConfigs.release
        }

    }
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/NOTICE.txt'
    }
    lintOptions {
        abortOnError false
    }

}

The android Gradle object is defined for and consumed by the android plugin. Another confusing part about my research was the Android Gradle User Guide documentation. This again made it seem like Gradle was specifically designed for Android/Java by mixing together the usage of Gradle and the usage of the Android Gradle plugin.

I will touch of each of the object list above:

  • sourceSets: SourceSets are used to build the APK. Here you can see that I needed to override the default location for each of the SrcDir location since the build.gradle file is in the parent directory. You can read more about source sets here.
  • compileSdkVersion & buildToolsVersion: By Default the compileSdkVersion is only necessary. This properties are used to tell gradle what SDK to use to compile your android application.
  • defaultConfig: The defaultConfig property is where the version and target SDK are configured.
  • signingConfigs: This is where you define the configuration for signing your Android builds. Each signing configuration property corresponds to a build type defined.
  • buildTypes: The Android plugin allows for configuring debug & release as well as creating other “custom” Build Types.
  • packagingOptions: This property give you control with overriding default build packaging settings. My project contain multiple Jars with the same files (license and notice files) causing the build to fail (source). I was able to exclude these files from the builds packaging routine.
  • lintOptions: You can configured how you want your build to address any Lint info/warnings/errors. Here I have disabled aborting the build on error. There are more settings documented here.

The Primary settings.gradle File (Hello Gradle Android Project)

The settings.gradlefile is used in multi-project scenario. As noted above, each project will have its own build.gradle file but the root will be the only project with a settings.gradle file. The settings.gradle includes each project required and names it using a colon class dependency syntax.

Settings.Gradle File

/*
// To declare projects as part of a multi-project build use the 'include' method
include 'shared'
include 'api'
include 'services:webservice'
*/

include 'libraries:actionbarsherlock'
project(':libraries:actionbarsherlock').projectDir = new File(settingsDir, './SupportingLib/actionbarsherlock')


include 'libraries:SlidingMenu-jfeinstein'
project(':libraries:SlidingMenu-jfeinstein').projectDir = new File(settingsDir, './SupportingLib/SlidingMenu-jfeinstein')

include 'libraries:switch-backport'
project(':libraries:switch-backport').projectDir = new File(settingsDir, './SupportingLib/switch-backport')


include 'libraries:google-play-services_lib'
project(':libraries:google-play-services_lib').projectDir = new File(settingsDir, './SupportingLib/google-play-services_lib')

include 'libraries:google-style'
project(':libraries:google-style').projectDir = new File(settingsDir, './SupportingLib/google-style')


rootProject.name = 'HelloGradle'

As you can see each project that is required to support building HelloGradle is included and its projectDir set in the settings.build file. This allows the build file to reference each project.

Depended Android Libraries build.gradle File.

The build file for each dependent library of the Hello Android application contains a build.gradle file in the project root (same folder as the AndroidManifest.xml file). All of these Android libraries essentially use the same build.gradle file.

Build.Gradle File for Slideing Menu

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.7.+'
    }
}

apply plugin: 'android-library'


dependencies {
    compile project(path: ':libraries:actionbarsherlock')
}

android {
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
    }
    compileSdkVersion 19
    buildToolsVersion "19.0.0"

    defaultConfig {
        versionCode 1
        versionName "1"
        minSdkVersion 8
        targetSdkVersion 16
    }
}

Again, this looks much like the HelloGradle application build.gradle file. The major difference here is the plugin used and dependencies. This project is an Android library project. Because of this, it will need to be built using the android-library plugin instead of the android plugin. You will also notice that the sourceSets.main properties object does not reference a parent folder, just the src directory. Those are the main differences between the HelloGradle settings.build file and the library’s build.gradle files.

Conclusion

Setting up a Gradle build scripts for your Android application can seem over whelming. It is in part due to the Gradle team and Google not painting a clear picture to the community as to its role. Hopefully, I was able to clear the air on the role of Gradle, Gradle plugins and the build scripting.

Posted in Continuous Integration, Programming and tagged , , , , , .