Gradle for Android
Introduction#
Gradle is a JVM-based build system that enables developers to write high-level scripts that can be used to automate the process of compilation and application production. It is a flexible plugin-based system, which allows you to automate various aspects of the build process; including compiling and signing a .jar
, downloading and managing external dependencies, injecting fields into the AndroidManifest
or utilising specific SDK versions.
Syntax#
-
apply plugin
: The plugins which should been used normally just'com.android.application'
or'com.android.library'
. -
android
: The main configuration of your appcompileSdkVersion
: The compile SDK versionbuildToolsVersion
: The build tools versiondefaultConfig
: The default settings which can been overwritten by flavors and build typesapplicationId
: The application id you use e.g. in the PlayStore mostly the same as your package nameminSdkVersion
: The minimal required SDK versiontargetSdkVersion
: The SDK version you compile against (should be always the newst one)versionCode
: The internal version number which needs to be bigger on each updateversionName
: The version number the user can see in the app details page
buildTypes
: See somewhere else (TODO)
-
dependencies
: The maven or local dependencies of your appcompile
a single dependencytestCompile
: a dependency for the unit or integration tests
Remarks#
See also
- The official gradle homepage
- How to configure gradle builds
- The android plugin for gradle
- Android Gradle DSL
Gradle for Android - Extended documentation:
There is another tag where you can find more topics and examples about the use of gradle in Android.
https://stackoverflow.com/documentation/android-gradle/topics
A basic build.gradle file
This is an example of a default build.gradle
file in a module.
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion '25.0.3'
signingConfigs {
applicationName {
keyAlias 'applicationName'
keyPassword 'password'
storeFile file('../key/applicationName.jks')
storePassword 'keystorePassword'
}
}
defaultConfig {
applicationId 'com.company.applicationName'
minSdkVersion 14
targetSdkVersion 25
versionCode 1
versionName '1.0'
signingConfig signingConfigs.applicationName
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
testCompile 'junit:junit:4.12'
}
DSL (domain-specific language)
Each block in the file above is called a DSL
(domain-specific language).
Plugins
The first line, apply plugin: 'com.android.application'
, applies the Android plugin for Gradle to the build and makes the android {}
block available to declare Android-specific build options.
For an Android Application:
apply plugin: 'com.android.application'
For an Android Library:
apply plugin: 'com.android.library'
Understanding the DSLs in the sample above
The second part, The android {...}
block, is the Android DSL
which contains information about your project.
For example, you can set the compileSdkVersion
which specifies the Android API level , Which should be used by Gradle to compile your app.
The sub-block defaultConfig
holds the defaults for your manifest. You can override
them with Product Flavors.
You can find more info in these examples:
Dependencies
The dependencies
block is defined outside the android
block {...}
: This means it’s not defined by the Android plugin but it’s standard Gradle.
The dependencies
block specifies what external libraries (typically Android libraries, but Java libraries are also valid) you wish to include in your app. Gradle will automatically download these dependencies for you (if there is no local copy available), you just need to add similar compile
lines when you wish to add another library.
Let’s look at one of the lines present here:
compile 'com.android.support:design:25.3.1'
This line basically says
add a dependency on the Android support design library to my project.
Gradle will ensure that the library is downloaded and present so that you can use it in your app, and its code will also be included in your app.
If you’re familiar with Maven, this syntax is the GroupId, a colon, ArtifactId, another colon, then the version of the dependency you wish to include, giving you full control over versioning.
While it is possible to specify artifact versions using the plus (+) sign, best practice is to avoid doing so; it can lead to issues if the library gets updated with breaking changes without your knowledge, which would likely lead to crashes in your app.
You can add different kind of dependencies:
A particular attention should be dedicated to the aar flat dependencies.
You can find more details in this topic.
Note about the -v7 in appcompat-v7
compile 'com.android.support:appcompat-v7:25.3.1'
This simply means that this library (appcompat
) is compatible with the Android API level 7 and forward.
Note about the junit:junit:4.12
This is Testing dependency for Unit testing.
Specifying dependencies specific to different build configurations
You can specify that a dependency should only be used for a certain build configuration or you can define different dependencies for the build types or the product flavors (e.g., debug, test or release) by using debugCompile
, testCompile
or releaseCompile
instead of the usual compile
.
This is helpful for keeping test- and debug- related dependencies out of your release build, which will keep your release APK
as slim as possible and help to ensure that any debug information cannot be used to obtain internal information about your app.
signingConfig
The signingConfig
allows you to configure your Gradle to include keystore
information and ensure that the APK built using these configurations are signed and ready for Play Store release.
Here you can find a dedicated topic.
Note: It’s not recommended though to keep the signing credentials inside your Gradle file. To remove the signing configurations, just omit the signingConfigs
portion.
You can specify them in different ways:
- storing in an external file
- storing them in setting environment variables.
See this topic for more details : Sign APK without exposing keystore password.
You can find further information about Gradle for Android in the dedicated Gradle topic.
Defining product flavors
Product flavors are defined in the build.gradle
file inside the android { ... }
block as seen below.
...
android {
...
productFlavors {
free {
applicationId "com.example.app.free"
versionName "1.0-free"
}
paid {
applicationId "com.example.app.paid"
versionName "1.0-paid"
}
}
}
By doing this, we now have two additional product flavors: free
and paid
. Each can have its own specific configuration and attributes. For example, both of our new flavors has a separate applicationId
and versionName
than our existing main
flavor (available by default, so not shown here).
Adding product flavor-specific dependencies
Dependencies can be added for a specific product flavor, similar to how they can be added for specific build configurations.
For this example, assume that we have already defined two product flavors called free
and paid
(more on defining flavors here).
We can then add the AdMob dependency for the free
flavor, and the Picasso library for the paid
one like so:
android {
...
productFlavors {
free {
applicationId "com.example.app.free"
versionName "1.0-free"
}
paid {
applicationId "com.example.app.paid"
versionName "1.0-paid"
}
}
}
...
dependencies {
...
// Add AdMob only for free flavor
freeCompile 'com.android.support:appcompat-v7:23.1.1'
freeCompile 'com.google.android.gms:play-services-ads:8.4.0'
freeCompile 'com.android.support:support-v4:23.1.1'
// Add picasso only for paid flavor
paidCompile 'com.squareup.picasso:picasso:2.5.2'
}
...
Adding product flavor-specific resources
Resources can be added for a specific product flavor.
For this example, assume that we have already defined two product flavors called free
and paid
. In order to add product flavor-specific resources, we create additional resource folders alongside the main/res
folder, which we can then add resources to like usual. For this example, we’ll define a string, status
, for each product flavor:
/src/main/res/values/strings.xml
<resources>
<string name="status">Default</string>
</resources>
/src/free/res/values/strings.xml
<resources>
<string name="status">Free</string>
</resources>
/src/paid/res/values/strings.xml
<resources>
<string name="status">Paid</string>
</resources>
The product flavor-specific status
strings will override the value for status
in the main
flavor.
Define and use Build Configuration Fields
BuildConfigField
Gradle allows buildConfigField
lines to define constants. These constants will be accessible at runtime as static fields of the BuildConfig
class. This can be used to create flavors by defining all fields within the defaultConfig
block, then overriding them for individual build flavors as needed.
This example defines the build date and flags the build for production rather than test:
android {
...
defaultConfig {
...
// defining the build date
buildConfigField "long", "BUILD_DATE", System.currentTimeMillis() + "L"
// define whether this build is a production build
buildConfigField "boolean", "IS_PRODUCTION", "false"
// note that to define a string you need to escape it
buildConfigField "String", "API_KEY", "\"my_api_key\""
}
productFlavors {
prod {
// override the productive flag for the flavor "prod"
buildConfigField "boolean", "IS_PRODUCTION", "true"
resValue 'string', 'app_name', 'My App Name'
}
dev {
// inherit default fields
resValue 'string', 'app_name', 'My App Name - Dev'
}
}
}
The automatically-generated <package_name>.BuildConfig
.java in the gen folder contains the following fields based on the directive above:
public class BuildConfig {
// ... other generated fields ...
public static final long BUILD_DATE = 1469504547000L;
public static final boolean IS_PRODUCTION = false;
public static final String API_KEY = "my_api_key";
}
The defined fields can now be used within the app at runtime by accessing the generated BuildConfig
class:
public void example() {
// format the build date
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
String buildDate = dateFormat.format(new Date(BuildConfig.BUILD_DATE));
Log.d("build date", buildDate);
// do something depending whether this is a productive build
if (BuildConfig.IS_PRODUCTION) {
connectToProductionApiEndpoint();
} else {
connectToStagingApiEndpoint();
}
}
ResValue
The resValue
in the productFlavors
creates a resource value. It can be any type of resource (string
, dimen
, color
, etc.). This is similar to defining a resource in the appropriate file: e.g. defining string in a strings.xml
file. The advantage being that the one defined in gradle can be modified based on your productFlavor/buildVariant. To access the value, write the same code as if you were accessing a res from the resources file:
getResources().getString(R.string.app_name)
The important thing is that resources defined this way cannot modify existing resources defined in files. They can only create new resource values.
Some libraries (such as the Google Maps Android API) require an API key provided in the Manifest as a meta-data
tag. If different keys are needed for debugging and production builds, specify a manifest placeholder filled in by Gradle.
In your AndroidManifest.xml
file:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}"/>
And then set the field accordingly in your build.gradle
file:
android {
defaultConfig {
...
// Your development key
manifestPlaceholders = [ MAPS_API_KEY: "AIza..." ]
}
productFlavors {
prod {
// Your production key
manifestPlaceholders = [ MAPS_API_KEY: "AIza..." ]
}
}
}
The Android build system generates a number of fields automatically and places them in BuildConfig.java
. These fields are:
Field | Description |
---|---|
DEBUG |
a Boolean stating if the app is in debug or release mode |
APPLICATION_ID |
a String containing the ID of the application (e.g. com.example.app ) |
BUILD_TYPE |
a String containing the build type of the application (usually either debug or release ) |
FLAVOR |
a String containing the particular flavor of the build |
VERSION_CODE |
an int containing the version (build) number.This is the same as versionCode in build.gradle or versionCode in AndroidManifest.xml |
VERSION_NAME |
a String containing the version (build) name.This is the same as versionName in build.gradle or versionName in AndroidManifest.xml |
In addition to the above, if you have defined multiple dimensions of flavor then each dimension will have its own value. For example, if you had two dimensions of flavor for color
and size
you will also have the following variables:
Field | Description |
---|---|
FLAVOR_color |
a String containing the value for the ‘color’ flavor. |
FLAVOR_size |
a String containing the value for the ‘size’ flavor. |
Centralizing dependencies via “dependencies.gradle” file
When working with multi-module projects, it is helpful to centralize dependencies in a single location rather than having them spread across many build files, especially for common libraries such as the Android support libraries and the Firebase libraries.
One recommended way is to separate the Gradle build files, with one build.gradle
per module, as well as one in the project root and another one for the dependencies, for example:
root
+- gradleScript/
| dependencies.gradle
+- module1/
| build.gradle
+- module2/
| build.gradle
+- build.gradle
Then, all of your dependencies can be located in gradleScript/dependencies.gradle
:
ext {
// Version
supportVersion = '24.1.0'
// Support Libraries dependencies
supportDependencies = [
design: "com.android.support:design:${supportVersion}",
recyclerView: "com.android.support:recyclerview-v7:${supportVersion}",
cardView: "com.android.support:cardview-v7:${supportVersion}",
appCompat: "com.android.support:appcompat-v7:${supportVersion}",
supportAnnotation: "com.android.support:support-annotations:${supportVersion}",
]
firebaseVersion = '9.2.0';
firebaseDependencies = [
core: "com.google.firebase:firebase-core:${firebaseVersion}",
database: "com.google.firebase:firebase-database:${firebaseVersion}",
storage: "com.google.firebase:firebase-storage:${firebaseVersion}",
crash: "com.google.firebase:firebase-crash:${firebaseVersion}",
auth: "com.google.firebase:firebase-auth:${firebaseVersion}",
messaging: "com.google.firebase:firebase-messaging:${firebaseVersion}",
remoteConfig: "com.google.firebase:firebase-config:${firebaseVersion}",
invites: "com.google.firebase:firebase-invites:${firebaseVersion}",
adMod: "com.google.firebase:firebase-ads:${firebaseVersion}",
appIndexing: "com.google.android.gms:play-services-appindexing:${firebaseVersion}",
];
}
Which can then be applied from that file in the top level file build.gradle
like so:
// Load dependencies
apply from: 'gradleScript/dependencies.gradle'
and in the module1/build.gradle
like so:
// Module build file
dependencies {
// ...
compile supportDependencies.appCompat
compile supportDependencies.design
compile firebaseDependencies.crash
}
Another approach
A less verbose approach for centralizing library dependencies versions can be achieved by declaring the version number as a variable once, and using it everywhere.
In the workspace root build.gradle
add this:
ext.v = [
supportVersion:'24.1.1',
]
And in every module that uses the same library add the needed libraries
compile "com.android.support:support-v4:${v.supportVersion}"
compile "com.android.support:recyclerview-v7:${v.supportVersion}"
compile "com.android.support:design:${v.supportVersion}"
compile "com.android.support:support-annotations:${v.supportVersion}"
Directory structure for flavor-specific resources
Different flavors of application builds can contain different resources. To create a flavor-specific resource make a directory with the lower-case name of your flavor in the src
directory and add your resources in the same way you would normally.
For example, if you had a flavour Development
and wanted to provide a distinct launcher icon for it you would create a directory src/development/res/drawable-mdpi
and inside that directory create an ic_launcher.png
file with your development-specific icon.
The directory structure will look like this:
src/
main/
res/
drawable-mdpi/
ic_launcher.png <-- the default launcher icon
development/
res/
drawable-mdpi/
ic_launcher.png <-- the launcher icon used when the product flavor is 'Development'
(Of course, in this case you would also create icons for drawable-hdpi, drawable-xhdpi etc).
Why are there two build.gradle files in an Android Studio project?
<PROJECT_ROOT>\app\build.gradle
is specific for app module.
<PROJECT_ROOT>\build.gradle
is a “Top-level build file” where you can add configuration options common to all sub-projects/modules.
If you use another module in your project, as a local library you would have another build.gradle
file:
<PROJECT_ROOT>\module\build.gradle
In the top level file you can specify common properties as the buildscript block or some common properties.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'com.google.gms:google-services:3.0.0'
}
}
ext {
compileSdkVersion = 23
buildToolsVersion = "23.0.1"
}
In the app\build.gradle
you define only the properties for the module:
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}
dependencies {
//.....
}
Executing a shell script from gradle
A shell script is a very versatile way to extend your build to basically anything you can think of.
As an exmaple, here is a simple script to compile protobuf files and add the result java files to the source directory for further compilation:
def compilePb() {
exec {
// NOTICE: gradle will fail if there's an error in the protoc file...
executable "../pbScript.sh"
}
}
project.afterEvaluate {
compilePb()
}
The ‘pbScript.sh’ shell script for this example, located in the project’s root folder:
#!/usr/bin/env bash
pp=/home/myself/my/proto
/usr/local/bin/protoc -I=$pp \
--java_out=./src/main/java \
--proto_path=$pp \
$pp/my.proto \
--proto_path=$pp \
$pp/my_other.proto
Debugging your Gradle errors
The following is an excerpt from Gradle - What is a non-zero exit value and how do I fix it?, see it for the full discussion.
Let’s say you are developing an application and you get some Gradle error that appears that generally will look like so.
:module:someTask FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':module:someTask'.
> some message here... finished with non-zero exit value X
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: Y.ZZ secs
You search here on StackOverflow for your problem, and people say to clean and rebuild your project, or enable MultiDex, and when you try that, it just isn’t fixing the problem.
There are ways to get more information, but the Gradle output itself should point at the actual error in the few lines above that message between :module:someTask FAILED
and the last :module:someOtherTask
that passed. Therefore, if you ask a question about your error, please edit your questions to include more context to the error.
So, you get a “non-zero exit value.” Well, that number is a good indicator of what you should try to fix. Here are a few occur most frequently.
1
is a just a general error code and the error is likely in the Gradle output2
seems to be related to overlapping dependencies or project misconfiguration.3
seems to be from including too many dependencies, or a memory issue.
The general solutions for the above (after attempting a Clean and Rebuild of the project) are:
1
- Address the error that is mentioned. Generally, this is a compile-time error, meaning some piece of code in your project is not valid. This includes both XML and Java for an Android project.2
&3
- Many answers here tell you to enable multidex. While it may fix the problem, it is most likely a workaround. If you don’t understand why you are using it (see the link), you probably don’t need it. General solutions involve cutting back your overuse of library dependencies (such as all of Google Play Services, when you only need to use one library, like Maps or Sign-In, for example).
Specifying different application IDs for build types and product flavors
You can specify different application IDs or package names for each buildType
or productFlavor
using the applicationIdSuffix configuration attribute:
Example of suffixing the applicationId
for each buildType
:
defaultConfig {
applicationId "com.package.android"
minSdkVersion 17
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
debuggable false
}
development {
debuggable true
applicationIdSuffix ".dev"
}
testing {
debuggable true
applicationIdSuffix ".qa"
}
}
Our resulting applicationIds
would now be:
- com.package.android for
release
- com.package.android.dev for
development
- com.package.android.qa for
testing
This can be done for productFlavors
as well:
productFlavors {
free {
applicationIdSuffix ".free"
}
paid {
applicationIdSuffix ".paid"
}
}
The resulting applicationIds
would be:
- com.package.android.free for the
free
flavor - com.package.android.paid for the
paid
flavor
Sign APK without exposing keystore password
You can define the signing configuration to sign the apk in the build.gradle
file
using these properties:
storeFile
: the keystore filestorePassword
: the keystore passwordkeyAlias
: a key alias namekeyPassword
: A key alias password
In many case you may need to avoid this kind of info in the build.gradle
file.
Method A: Configure release signing using a keystore.properties file
It’s possible to configure your app’s build.gradle
so that it will read your signing configuration information from a properties file like keystore.properties
.
Setting up signing like this is beneficial because:
- Your signing configuration information is separate from your
build.gradle
file - You do not have to intervene during the signing process in order to provide passwords for your keystore file
- You can easily exclude the
keystore.properties
file from version control
First, create a file called keystore.properties
in the root of your project with content like this (replacing the values with your own):
storeFile=keystore.jks
storePassword=storePassword
keyAlias=keyAlias
keyPassword=keyPassword
Now, in your app’s build.gradle
file, set up the signingConfigs
block as follows:
android {
...
signingConfigs {
release {
def propsFile = rootProject.file('keystore.properties')
if (propsFile.exists()) {
def props = new Properties()
props.load(new FileInputStream(propsFile))
storeFile = file(props['storeFile'])
storePassword = props['storePassword']
keyAlias = props['keyAlias']
keyPassword = props['keyPassword']
}
}
}
}
That’s really all there is to it, but don’t forget to exclude both your keystore file and your keystore.properties
file from version control.
A couple of things to note:
- The
storeFile
path specified in thekeystore.properties
file should be relative to your app’sbuild.gradle
file. This example assumes that the keystore file is in the same directory as the app’sbuild.gradle
file. - This example has the
keystore.properties
file in the root of the project. If you put it somewhere else, be sure to change the value inrootProject.file('keystore.properties')
to the location of yours, relative to the root of your project.
Method B: By using an environment variable
The same can be achieved also without a properties file, making the password harder to find:
android {
signingConfigs {
release {
storeFile file('/your/keystore/location/key')
keyAlias 'your_alias'
String ps = System.getenv("ps")
if (ps == null) {
throw new GradleException('missing ps env variable')
}
keyPassword ps
storePassword ps
}
}
The "ps"
environment variable can be global, but a safer approach can be by adding it to the shell of Android Studio only.
In linux this can be done by editing Android Studio’s Desktop Entry
Exec=sh -c "export ps=myPassword123 ; /path/to/studio.sh"
You can find more details in this topic.
Versioning your builds via “version.properties” file
You can use Gradle to auto-increment your package version each time you build it. To do so create a version.properties
file in the same directory as your build.gradle
with the following contents:
VERSION_MAJOR=0
VERSION_MINOR=1
VERSION_BUILD=1
(Changing the values for major and minor as you see fit). Then in your build.gradle
add the following code to the android
section:
// Read version information from local file and increment as appropriate
def versionPropsFile = file('version.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def versionMajor = versionProps['VERSION_MAJOR'].toInteger()
def versionMinor = versionProps['VERSION_MINOR'].toInteger()
def versionBuild = versionProps['VERSION_BUILD'].toInteger() + 1
// Update the build number in the local file
versionProps['VERSION_BUILD'] = versionBuild.toString()
versionProps.store(versionPropsFile.newWriter(), null)
defaultConfig {
versionCode versionBuild
versionName "${versionMajor}.${versionMinor}." + String.format("%05d", versionBuild)
}
}
The information can be accessed in Java as a string BuildConfig.VERSION_NAME
for the complete {major}.{minor}.{build} number and as an integer BuildConfig.VERSION_CODE
for just the build number.
Changing output apk name and add version name:
This is the code for changing output application file name (.apk). The name can be configured by assigning a different value to newName
android {
applicationVariants.all { variant ->
def newName = "ApkName";
variant.outputs.each { output ->
def apk = output.outputFile;
newName += "-v" + defaultConfig.versionName;
if (variant.buildType.name == "release") {
newName += "-release.apk";
} else {
newName += ".apk";
}
if (!output.zipAlign) {
newName = newName.replace(".apk", "-unaligned.apk");
}
output.outputFile = new File(apk.parentFile, newName);
logger.info("INFO: Set outputFile to "
+ output.outputFile
+ " for [" + output.name + "]");
}
}
}
Disable image compression for a smaller APK file size
If you are optimizing all images manually, disable APT Cruncher for a smaller APK file size.
android {
aaptOptions {
cruncherEnabled = false
}
}
Enable Proguard using gradle
For enabling Proguard configurations for your application you need to enable it in your module-level gradle file. You need to set the value of minifyEnabled
to true
.
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
The above code will apply your Proguard configurations contained in the default Android SDK combined with the “proguard-rules.pro” file on your module to your released apk.
Enable experimental NDK plugin support for Gradle and AndroidStudio
Enable and configure the experimental Gradle plugin to improve AndroidStudio’s NDK support. Check that you fulfill the following requirements:
- Gradle 2.10 (for this example)
- Android NDK r10 or later
- Android SDK with build tools v19.0.0 or later
Configure MyApp/build.gradle file
Edit the dependencies.classpath line in build.gradle from e.g.
classpath 'com.android.tools.build:gradle:2.1.2'
to
classpath 'com.android.tools.build:gradle-experimental:0.7.2'
(v0.7.2 was the latest version at the time of writing. Check the latest version yourself and adapt your line accordingly)
The build.gradle file should look similar to this:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle-experimental:0.7.2'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Configure MyApp/app/build.gradle file
Edit the build.gradle file to look similar to the following example. Your version numbers may look different.
apply plugin: 'com.android.model.application'
model {
android {
compileSdkVersion 19
buildToolsVersion "24.0.1"
defaultConfig {
applicationId "com.example.mydomain.myapp"
minSdkVersion.apiLevel 19
targetSdkVersion.apiLevel 19
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles.add(file('proguard-android.txt'))
}
}
ndk {
moduleName "myLib"
/* The following lines are examples of a some optional flags that
you may set to configure your build environment
*/
cppFlags.add("-I${file("path/to/my/includes/dir")}".toString())
cppFlags.add("-std=c++11")
ldLibs.addAll(['log', 'm'])
stl = "c++_static"
abiFilters.add("armeabi-v7a")
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
Sync and check that there are no errors in the Gradle files before proceeding.
Test if plugin is enabled
First make sure you have downloaded the Android NDK module. Then create an new app in AndroidStudio and add the following to the ActivityMain file:
public class MainActivity implements Activity {
onCreate() {
// Pregenerated code. Not important here
}
static {
System.loadLibrary("myLib");
}
public static native String getString();
}
The getString()
part should be highlighted red saying that the corresponding JNI function could not be found. Hover your mouse over the function call until a red lightbulb appears. Click the bulb and select create function JNI_...
. This should generate a myLib.c file in the myApp/app/src/main/jni directory with the correct JNI function call. It should look similar to this:
#include <jni.h>
JNIEXPORT jstring JNICALL
Java_com_example_mydomain_myapp_MainActivity_getString(JNIEnv *env, jobject instance)
{
// TODO
return (*env)->NewStringUTF(env, returnValue);
}
If it doesn’t look like this, then the plugin has not correctly been configured or the NDK has not been downloaded
Show all gradle project tasks
gradlew tasks -- show all tasks
Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.
Build tasks
-----------
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles all Debug builds.
assembleRelease - Assembles all Release builds.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
compileDebugAndroidTestSources
compileDebugSources
compileDebugUnitTestSources
compileReleaseSources
compileReleaseUnitTestSources
extractDebugAnnotations - Extracts Android annotations for the debug variant into the archive file
extractReleaseAnnotations - Extracts Android annotations for the release variant into the archive file
jar - Assembles a jar archive containing the main classes.
mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests.
testClasses - Assembles test classes.
Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]
Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.
Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'LeitnerBoxPro'.
components - Displays the components produced by root project 'LeitnerBoxPro'. [incubating]
dependencies - Displays all dependencies declared in root project 'LeitnerBoxPro'.
dependencyInsight - Displays the insight into a specific dependency in root project 'LeitnerBoxPro'.
help - Displays a help message.
model - Displays the configuration model of root project 'LeitnerBoxPro'. [incubating]
projects - Displays the sub-projects of root project 'LeitnerBoxPro'.
properties - Displays the properties of root project 'LeitnerBoxPro'.
tasks - Displays the tasks runnable from root project 'LeitnerBoxPro' (some of the displayed tasks may belong to subprojects)
.
Install tasks
-------------
installDebug - Installs the Debug build.
installDebugAndroidTest - Installs the android (on device) tests for the Debug build.
uninstallAll - Uninstall all applications.
uninstallDebug - Uninstalls the Debug build.
uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build.
uninstallRelease - Uninstalls the Release build.
Verification tasks
------------------
check - Runs all checks.
connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices.
connectedCheck - Runs all device checks on currently connected devices.
connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices.
deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers.
deviceCheck - Runs all device checks using Device Providers and Test Servers.
lint - Runs lint on all variants.
lintDebug - Runs lint on the Debug build.
lintRelease - Runs lint on the Release build.
test - Run unit tests for all variants.
testDebugUnitTest - Run unit tests for the debug build.
testReleaseUnitTest - Run unit tests for the release build.
Other tasks
-----------
assembleDefault
clean
jarDebugClasses
jarReleaseClasses
transformResourcesWithMergeJavaResForDebugUnitTest
transformResourcesWithMergeJavaResForReleaseUnitTest
Delete “unaligned” apk automatically
If you don’t need automatically generated apk files with unaligned
suffix (which you probably don’t), you may add the following code to build.gradle
file:
// delete unaligned files
android.applicationVariants.all { variant ->
variant.assemble.doLast {
variant.outputs.each { output ->
println "aligned " + output.outputFile
println "unaligned " + output.packageApplication.outputFile
File unaligned = output.packageApplication.outputFile;
File aligned = output.outputFile
if (!unaligned.getName().equalsIgnoreCase(aligned.getName())) {
println "deleting " + unaligned.getName()
unaligned.delete()
}
}
}
}
From here
Ignoring build variant
For some reasons you may want to ignore your build variants. For example: you have ‘mock’ product flavour and you use it only for debug purposes, such as unit/instrumentation tests.
Let’s ignore mockRelease variant from our project. Open build.gradle file and write:
// Remove mockRelease as it's not needed.
android.variantFilter { variant ->
if (variant.buildType.name.equals('release') && variant.getFlavors().get(0).name.equals('mock')) {
variant.setIgnore(true);
}
}
Seeing dependency tree
Use the task dependencies. Depending on how your modules are set up, it may be either ./gradlew dependencies
or to see the dependencies of module app use ./gradlew :app:dependencies
The example following build.gradle file
dependencies {
compile 'com.android.support:design:23.2.1'
compile 'com.android.support:cardview-v7:23.1.1'
compile 'com.google.android.gms:play-services:6.5.87'
}
will produce the following graph:
Parallel execution is an incubating feature.
:app:dependencies
------------------------------------------------------------
Project :app
------------------------------------------------------------
. . .
_releaseApk - ## Internal use, do not manually configure ##
+--- com.android.support:design:23.2.1
| +--- com.android.support:support-v4:23.2.1
| | \--- com.android.support:support-annotations:23.2.1
| +--- com.android.support:appcompat-v7:23.2.1
| | +--- com.android.support:support-v4:23.2.1 (*)
| | +--- com.android.support:animated-vector-drawable:23.2.1
| | | \--- com.android.support:support-vector-drawable:23.2.1
| | | \--- com.android.support:support-v4:23.2.1 (*)
| | \--- com.android.support:support-vector-drawable:23.2.1 (*)
| \--- com.android.support:recyclerview-v7:23.2.1
| +--- com.android.support:support-v4:23.2.1 (*)
| \--- com.android.support:support-annotations:23.2.1
+--- com.android.support:cardview-v7:23.1.1
\--- com.google.android.gms:play-services:6.5.87
\--- com.android.support:support-v4:21.0.0 -> 23.2.1 (*)
. . .
Here you can see the project is directly including com.android.support:design
version 23.2.1, which itself is bringing com.android.support:support-v4
with version 23.2.1. However, com.google.android.gms:play-services
itself has a dependency on the same support-v4
but with an older version 21.0.0, which is a conflict detected by gradle.
(*)
are used when gradle skips the subtree because those dependencies were already listed previously.
Use gradle.properties for central versionnumber/buildconfigurations
You can define central config info’s in
- a separate gradle include file https://stackoverflow.com/documentation/android/95/gradle-for-android/2911/centralizing-dependencies
- a stand alone properties file https://stackoverflow.com/documentation/android/95/gradle-for-android/7837/versioning-your-builds
or do it with root gradle.properties
file
the project structure
root
+- module1/
| build.gradle
+- module2/
| build.gradle
+- build.gradle
+- gradle.properties
global setting for all submodules in gradle.properties
# used for manifest
# todo increment for every release
appVersionCode=19
appVersionName=0.5.2.160726
# android tools settings
appCompileSdkVersion=23
appBuildToolsVersion=23.0.2
usage in a submodule
apply plugin: 'com.android.application'
android {
// appXXX are defined in gradle.properties
compileSdkVersion = Integer.valueOf(appCompileSdkVersion)
buildToolsVersion = appBuildToolsVersion
defaultConfig {
// appXXX are defined in gradle.properties
versionCode = Long.valueOf(appVersionCode)
versionName = appVersionName
}
}
dependencies {
...
}
Note: If you want to publish your app in the F-Droid app store you have to use magic numbers in the gradle file because else f-droid robot cannot read current versionnumner to detect/verify version changes.
Display signing information
In some circumstances (for example obtaining a Google API key) you need to find your keystore fingerprint. Gradle has a convenient task that display all the signing information, including keystore fingerprints:
./gradlew signingReport
This is a sample output:
:app:signingReport
Variant: release
Config: none
----------
Variant: debug
Config: debug
Store: /Users/user/.android/debug.keystore
Alias: AndroidDebugKey
MD5: 25:08:76:A9:7C:0C:19:35:99:02:7B:00:AA:1E:49:CA
SHA1: 26:BE:89:58:00:8C:5A:7D:A3:A9:D3:60:4A:30:53:7A:3D:4E:05:55
Valid until: Saturday 18 June 2044
----------
Variant: debugAndroidTest
Config: debug
Store: /Users/user/.android/debug.keystore
Alias: AndroidDebugKey
MD5: 25:08:76:A9:7C:0C:19:35:99:02:7B:00:AA:1E:49:CA
SHA1: 26:BE:89:58:00:8C:5A:7D:A3:A9:D3:60:4A:30:53:7A:3D:4E:05:55
Valid until: Saturday 18 June 2044
----------
Variant: debugUnitTest
Config: debug
Store: /Users/user/.android/debug.keystore
Alias: AndroidDebugKey
MD5: 25:08:76:A9:7C:0C:19:35:99:02:7B:00:AA:1E:49:CA
SHA1: 26:BE:89:58:00:8C:5A:7D:A3:A9:D3:60:4A:30:53:7A:3D:4E:05:55
Valid until: Saturday 18 June 2044
----------
Variant: releaseUnitTest
Config: none
----------
Defining build types
You can create and configure build types in the module-level build.gradle
file inside the android {}
block.
android {
...
defaultConfig {...}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".debug"
}
}
}