Introducing Gradle and the Manifest

In the discussion of Android Studio, this book has mentioned something called “Gradle”, without a lot of explanation.

In this chapter, the mysteries of Gradle will be revealed to you.

(well, OK, some of the mysteries…)

We also mentioned in passing in the previous chapter the concept of the “manifest” — AndroidManifest.xml — as being a special file in our Android projects.

On the one hand, Gradle and the manifest are not strictly related. On the other hand, some (but far from all) of the things that we can set up in the manifest can be overridden in Gradle.

So, in this chapter, we will review both what Gradle is, what the manifest is, what each of their roles are, and the basics of how they tie together.

Gradle: The Big Questions

First, let us “set the stage” by examining what this is all about, through a series of fictionally-asked questions (FAQs).

What is Gradle?

Gradle is software for building software, otherwise known as “build automation software” or “build systems”. You may have used other build systems before in other environments, such as make (C/C++), rake (Ruby), Ant (Java), Maven (Java), etc.

These tools know — via intrinsic capabilities and rules that you teach them — how to determine what needs to be created (e.g., based on file changes) and how to create them. A build system does not compile, link, package, etc. applications directly, but instead directs separate compilers, linkers, and packagers to do that work.

Gradle uses a domain-specific language (DSL) built on top of Groovy to accomplish these tasks.

What is Groovy?

There are many programming languages that are designed to run on top of the Java VM. Some of these, like JRuby and Jython, are implementations of other common programming languages (Ruby and Python, respectively). Other languages are unique, and Groovy is one of those.

Groovy scripts look a bit like a mashup of Java and Ruby. As with Java, Groovy supports:

As with Ruby, though:

Groovy is an interpreted language, like Ruby and unlike Java. Groovy scripts are run by executing a groovy command, passing it the script to run. The Groovy runtime, though, is a Java JAR and requires a JVM in order to operate.

One of Groovy’s strengths is in creating a domain-specific language (or DSL). Gradle, for example, is a Groovy DSL for doing software builds. Gradle-specific capabilities appear to be first-class language constructs, generally indistinguishable from capabilities intrinsic to Groovy. Yet, the Groovy DSL is largely declarative, like an XML file.

To some extent, we get the best of both worlds: XML-style definitions (generally with less punctuation), yet with the ability to “reach into Groovy” and do custom scripting as needed.

What Does Android Have To Do with Gradle?

Google has published the Android Gradle Plugin, which gives Gradle the ability to build Android projects. Google is also using Gradle and the Android Gradle Plugin as the build system behind Android Studio.

Obtaining Gradle

As with any build system, to use it, you need the build system’s engine itself.

If you will only be using Gradle in the context of Android Studio, the IDE will take care of getting Gradle for you. If, however, you are planning on using Gradle outside of Android Studio (e.g., command-line builds), you will want to consider where your Gradle is coming from. This is particularly important for situations where you want to build the app with no IDE in sight, such as using a continuous integration (CI) server, like Jenkins.

Also, the way that Android Studio works with Gradle — called the Gradle Wrapper – opens up security issues for your development machine, if you are the sort to download open source projects from places like GitHub and try using them.

Direct Installation

What most developers looking to use Gradle outside of Android Studio will wind up doing is installing Gradle directly.

The Gradle download page contains links to ZIP archives for Gradle itself: binaries, source code, or both.

You can unZIP this archive to your desired location on your development machine.

Linux Packages

You may be able to obtain Gradle via a package manager on Linux environments. For example, there is an Ubuntu PPA for Gradle.

The gradlew Wrapper

If you are starting from a project that somebody else has published, you may find a gradlew and gradlew.bat file in the project root, along with a gradle/ directory. This represents the “Gradle Wrapper”.

The Gradle Wrapper consists of three pieces:

Android Studio uses the gradle-wrapper.properties file to determine where to download Gradle from, for use in your project, from the distributionUrl property in that file:

#Thu May 10 08:57:09 EDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
(from Basic/Button/gradle/wrapper/gradle-wrapper.properties)

When you create or import a project, or if you change the version of Gradle referenced in the properties file, Android Studio will download the Gradle pointed to by the distributionUrl property and install it to a .gradle/ directory (note the leading .) in your project. That version of Gradle will be what Android Studio uses.

RULE #1: Only use a distributionUrl that you trust.

If you are importing an Android project from a third party — such as the samples for this book — and they contain the gradle/wrapper/gradle-wrapper.properties file, examine it to see where the distributionUrl is pointing to. If it is loading from services.gradle.org, or from an internal enterprise server, it is probably trustworthy. If it is pointing to a URL located somewhere else, consider whether you really want to use that version of Gradle, considering that it may have been tampered with.

The batch file, shell script, and JAR file are there to support command-line builds. If you run the gradlew command, it will use a local copy of Gradle installed in .gradle/ in the project. If there is no such copy of Gradle, gradlew will download Gradle from the distributionUrl, as does Android Studio. Note that Android Studio does not use gradlew for this role — that logic is built into Android Studio itself.

RULE #2: Only use a gradlew that you REALLY trust.

It is relatively easy to examine a .properties file to check a URL to see if it seems valid. Making sense of a batch file or shell script can be cumbersome. Decompiling a JAR file and making sense of it can be rather difficult. Yet, if you use gradlew that you obtained from somebody, that script and JAR are running on your development machine, as is the copy of Gradle that they install. If that code was tampered with, the malware has complete access to your development machine and anything that it can reach, such as servers within your organization.

Note that you do not have to use the Gradle Wrapper at all. If you would rather not worry about it, install a version of Gradle on your development machine yourself and remove the Gradle Wrapper files. You can use the gradle command to build your app (if your Gradle’s bin/ directory is in your PATH), and Android Studio will use your Gradle installation (if you teach it where to find it, such as via the GRADLE_HOME environment variable).

Versions of Gradle and the Android Gradle Plugin

The Android Gradle Plugin that we will use to give Gradle “super Android powers!” is updated periodically. Each update has its corresponding required version of Gradle. Google maintains a page listing the Gradle versions supported by each Android Gradle Plugin version

If you are using the Gradle Wrapper, you are using an installation of Gradle that is local to the project. So long as the version of Gradle in the project matches the version of the Android Gradle Plugin requested in the project’s build.gradle file — as will be covered later in this chapter — you should be in fine shape.

If you are not using the Gradle Wrapper, you will need to decide when to take on a new Android Gradle Plugin release and plan to update your Gradle installation and build.gradle files in tandem at that point.

Gradle Environment Variables

If you installed Gradle yourself, you will want to define a GRADLE_HOME environment variable, pointing to where you installed Gradle, and to add the bin/ directory inside of Gradle to your PATH environment variable.

You may also consider setting up a GRADLE_USER_HOME environment variable, pointing to a directory in which Gradle can create a .gradle/ subdirectory, for per-user caches and related materials. By default, Gradle will use your standard home directory.

Examining the Gradle Files

An Android Studio project usually has two build.gradle files, one at the project level and one at the “module” level (e.g., in the app/ directory).

The Project-Level File

The build.gradle file in the project directory controls the Gradle configuration for all modules in your project. Right now, most likely you only have one module, and many apps only ever use one module. However, it is possible for you to add other modules to this project, and we will explore reasons for doing so later in this book.

Here is a typical top-level build.gradle file:


// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

buildscript

In Groovy terms, a “closure” is a block of code wrapped in braces ({ }).

The buildscript closure in Gradle is where you configure the JARs and such that Gradle itself will use for interpreting the rest of the file. Hence, here you are not configuring your project so much as you are configuring the build itself.

The repositories closure inside the buildscript closure indicates where plugins can come from. Here, jcenter() is a built-in method that teaches Gradle about JCenter, a popular location for obtaining open source libraries. Similarly, google() is a built-in method that teaches Gradle about a site where it can download plugins from Google.

The dependencies closure indicates what is required to be able to run the rest of the build script. classpath 'com.android.tools.build:gradle:3.0.0' is not especially well-documented by the Gradle team. However the 'com.android.tools.build:gradle:3.0.0' portion means:

The first time you run your build, with the buildscript closure as shown above, Gradle will notice that you do not have this dependency. It will then download that artifact from Google, as Google serves up its plugin from its own site nowadays.

Sometimes, the last segment of the version is replaced with a + sign (e.g., 3.0.+). This tells Gradle to download the latest version, thereby automatically upgrading you to the latest patch-level (e.g., 3.0.1 at some point).

allprojects

The allprojects closure says “apply these settings to all modules in this project”. Here, we are setting up jcenter() and google() as places to find libraries used in any of the modules in our project. We will use lots of libraries in our projects — having these “repositories” set up in allprojects makes it simpler for us to request them.

The Module-Level Gradle File

In your app/ module, you will also find a build.gradle file. This has settings unique for this module, independent of any other module that your project may have in the future.

Here is a typical module-level build.gradle file:


apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.commonsware.myapplication"
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

For now, let’s focus on some key elements of this file.

dependencies

This build.gradle file also has a dependencies closure. Whereas the dependencies closure in the buildscript closure in the top-level build.gradle file is for libraries used by the build process, the dependencies closure in the module’s build.gradle file is for libraries used by your code in that module. We will get into the concept of these libraries later in the book.

android

The android closure contains all of the Android-specific configuration information. This closure is what the Android plugin enables, where the plugin itself comes from the apply plugin: 'com.android.application' line at the top, coupled with the classpath line from the project-level build.gradle file.

But before we get into what is in this closure, we should “switch gears” and talk about the manifest file, as what goes in the android closure is related to what goes in the manifest file.

Introducing the Manifest

The foundation for any Android application is the manifest file: AndroidManifest.xml. This will be in your app module’s src/main/ directory (the main source set) for typical Android Studio projects.

Here is where you declare what is inside your application — the activities, the services, and so on. You also indicate how these pieces attach themselves to the overall Android system; for example, you indicate which activity (or activities) should appear on the device’s main menu (a.k.a., launcher).

When you create your application, you will get a starter manifest generated for you. For a simple application, offering a single activity and nothing else, the auto-generated manifest will probably work out fine, or perhaps require a few minor modifications. On the other end of the spectrum, the manifest file for the Android API demo suite is over 1,000 lines long. Your production Android applications will probably fall somewhere in the middle.

As mentioned previously, some items can be defined in both the manifest and in a build.gradle file. The approach of putting that stuff in the manifest still works. For Android Studio users, you will probably use the Gradle file and not have those common elements be defined in the manifest.

Things In Common Between the Manifest and Gradle

There are a few key items that can be defined in the manifest and can be overridden in build.gradle statements. These items are fairly important to the development and operation of our Android apps as well.

Package Name and Application ID

The root of all manifest files is, not surprisingly, a manifest element:


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.commonsware.empublite">

Note the android namespace declaration. You will only use the namespace on many of the attributes, not the elements (e.g., <manifest>, not <android:manifest]).

The biggest piece of information you need to supply on the <manifest> element is the package attribute.

The package attribute will always need to be in the manifest, even for Android Studio projects. The package attribute will control where some source code is generated for us, notably some R and BuildConfig classes that we will encounter later in the book.

Since the package value is used for Java code generation, it has to be a valid Java package name. Java convention says that the package name should be based on a reverse domain name (e.g., com.commonsware.myapplication), where you own the domain in question. That way, it is unlikely that anyone else will accidentally collide with the same name.

The package also serves as our app’s default “application ID”. This needs to be a unique identifier, such that:

By default, the application ID is the package value, but Android Studio users can override it in their Gradle build files. Specifically, inside of the android closure can be a defaultConfig closure, and inside of there can be an applicationId statement:


android {
    // other stuff

    defaultConfig {
        applicationId "com.commonsware.myapplication"
        // more other stuff
    }
}

Not only can Android Studio users override the application ID in the defaultConfig closure, but there are ways of having different application ID values for different scenarios, such as a debug build versus a release build. We will explore that more later in the book.

minSdkVersion and targetSdkVersion

Your defaultConfig closure inside the android closure in your module’s build.gradle file has a pair of properties named minSdkVersion and targetSdkVersion. Technically, these override values that could be defined via a <uses-sdk> element in the manifest, though few projects will have such an element nowadays.

Of the two, the more critical one is minSdkVersion. This indicates what is the oldest version of Android you are testing with your application. The value of the attribute is an integer representing the Android API level. So, if you are only testing your application on Android 4.1 and newer versions of Android, you would set your minSdkVersion to be 16. The initial value is what you requested when you had Android Studio create the project.

You can also specify a targetSdkVersion. This indicates what version of Android you are thinking of as you are writing your code. If your application is run on a newer version of Android, Android may do some things to try to improve compatibility of your code with respect to changes made in the newer Android. Nowadays, most Android developers should specify a target SDK version of 15 or higher. We will start to explore more about the targetSdkVersion as we get deeper into the book; for the moment, whatever your IDE gives you as a default value is probably a fine starting point.

Version Code and Version Name

Similarly, the defaultConfig closure has versionCode and versionName properties. In principle, these override android:versionName and android:versionCode attributes on the root <manifest> element in the manifest, though you will not find many projects using those XML attributes.

These two values represent the versions of your application. The versionName value is what the user will see for a version indicator in the Applications details screen for your app in their Settings application. Also, the version name is used by the Play Store listing, if you are distributing your application that way. The version name can be any string value you want.

The versionCode, on the other hand, must be an integer, and newer versions must have higher version codes than do older versions. Android and the Play Store will compare the version code of a new APK to the version code of an installed application to determine if the new APK is indeed an update. The typical approach is to start the version code at 1 and increment it with each production release of your application, though you can choose another convention if you wish. During development, you can leave these alone, but when you move to production, these attributes will matter greatly.

Other Gradle Items of Note

The android closure has a compileSdkVersion property. compileSdkVersion specifies the API level to be compiled against, usually as a simple API level integer (e.g., 19). This indicates what Java classes, methods, and so on are available to you when you write your app. Typically, you set this to be the latest production release of Android, using the values noted earlier in the book.

The android closure may have a buildToolsVersion property. buildToolsVersion indicates the version of the Android SDK build tools that you wish to use with this project. The Android Gradle Plugin really is a thin wrapper around a series of “build tools” that handle most of the work of creating an APK out of your project. If your android closure does not have buildToolsVersion, the Android Gradle Plugin will use its own default version of these build tools, and for many projects this will suffice. In this book, most projects will state their buildToolsVersion, though some will skip that line and use the plugin-provided default.

Where’s the GUI?

You might wonder why we have to slog through all of this Groovy code and wonder if there is some GUI for affecting Gradle settings.

The answer is yes… and no.

There is the project structure dialog, that allows you to maintain some of this stuff. And you are welcome to try it. However, the more complex your build becomes, the more likely it is that the GUI will not suffice, and you will need to work with the Gradle build files more directly. Hence, this book will tend to focus on the build files.

The Rest of the Manifest

Not everything in the manifest can be overridden in the Gradle build files. Here are a few key items that will always be defined in the manifest, not in a build.gradle file.

An Application For Your Application

In your initial project’s manifest, the primary child of the <manifest> element is an <application> element.

By default, when you create a new Android project, you get a single <activity> element inside the <application> element:


<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.commonsware.myapplication"
  xmlns:android="http://schemas.android.com/apk/res/android">

  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name="MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>

</manifest>

The <activity> element supplies android:name for the class implementing the activity, android:label for the display name of the activity, and (sometimes) an <intent-filter> child element describing under what conditions this activity will be displayed. The stock <activity> element sets up your activity to appear in the launcher, so users can choose to run it. As we’ll see later in this book, you can have several activities in one project, if you so choose.

The android:name attribute, in this case, has a bare Java class name (MainActivity). Sometimes, you will see android:name with a fully-qualified class name (e.g., com.commonsware.myapplication.MainActivity). Sometimes, you will see a Java class name with a single dot as a prefix (e.g., .MainActivity). Both MainActivity and .MainActivity refer to a Java class that will be in your project’s package — the one you declared in the package attribute of the <manifest> element.

Supporting Multiple Screens

Android devices come with a wide range of screen sizes, from 2.8” tiny smartphones to 46” TVs. Android divides these into four buckets, based on physical size and the distance at which they are usually viewed:

  1. Small (under 3”)
  2. Normal (3” to around 4.5”)
  3. Large (4.5” to around 10”)
  4. Extra-large (over 10”)

By default, your application will support small and normal screens. It also will support large and extra-large screens via some automated conversion code built into Android.

To truly support all the screen sizes you want, you should consider adding a <supports-screens> element to your manifest. This enumerates the screen sizes you have explicit support for. For example, if you are providing custom UI support for large or extra-large screens, you will want to have the <supports-screens> element. So, while the starting manifest file works, handling multiple screen sizes is something you will want to think about.

You wind up with an element akin to:


<supports-screens
  android:largeScreens="true"
  android:normalScreens="true"
  android:smallScreens="false"
  android:xlargeScreens="true" />

Much more information about providing solid support for all screen sizes, including samples of the <supports-screens> element, will be found later in this book as we cover large-screen strategies.

Other Stuff

As we proceed through the book, you will find other elements being added to the manifest, such as:

These and other elements will be introduced elsewhere in the book.

Learning More About Gradle

This book will go into more about Gradle, both in the core chapters and in the trails. But, the focus will be on the Android Gradle Plugin, and Gradle itself offers a lot more than that. The Gradle Web site hosts documentation, links to Gradle-specific books, and links to other Gradle educational resources.

Visit the Trails!

There are a few more chapters in this book getting into more details about the use of Gradle and the Android Gradle Plugin.

There is also the “Advanced Gradle for Android Tips” chapter for other Gradle topics, and the chapter on manifest merging in Gradle.