Speeding up Android Gradle builds

article-img

Yes, I definitely drunk too much coffee and wasted an awful lot of time waiting for Android Studio to build my project, so I decided to write those 10 tips to help you improve your Gradle building process. To do this I used a big project as an example that we have just released in Tengio. The initial build time for it is around 1 minute and 40 seconds.

1. Keep the Android Gradle plugin version up to date

Google works continuously to fix bugs and improve the building process. Just by updating it from 2.2.0 to 3.0.0-alpha2 you will save a lot of time.

buildscript {

    repositories {
        maven { url 'https://maven.google.com' }
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0-alpha2'
    }
}

2. Avoid legacy multidex

As you know, if your app uses more than 64K method, you need to use multidex. However if your minSdkVersion is < 21 and you have multidex enabled you are going to use the legacy multidex that slows down the build significantly. Now if you are using Android Studio 2.3+ to build your app you won’t care about this because Android Studio automatically detects the API level of the phone plugged or emulator image, and switches to native multidex when it is possible. But, if you run the app command line you should add a new productFlavors in your build.gradle. As you can see, I have defined a new dev productFlavors setting the minSdkVersion 21 (you can set one later if you need). Now, from command line you should be able to call “assembleDevDebug”. In this way, you are running your app avoiding the multidex.

android {
    ...
    defaultConfig {...}
    buildTypes {...}
    
    productFlavors {
        dev {
            minSdkVersion 21
        }
    }
}

Well I don’t generally run apps command line, so I have never tried it before. Anyway I saved 10 seconds adding it.

3. Disable multi-APK

Multi-APK is important for release, but do you really need it during the development process? The answer is: no! You are probably running the app on a smartphone or an emulator so you don’t need Android Studio to prepare several apks for different devices. To disable it declare a new gradle property (‘devBuild’ for me) and pass it to Gradle every time you make a dev build.

command line:

./gradlew speeding-gradle:assembleDevDebug -PdevBuild

in Android Studio: Preferences > Build, Execution, Deployment > Compiler and add it in Command-line Options

And now you can check the property in the build.gradle

android {
    ...
    if (project.hasProperty('devBuild')) {
    	splits.abi.enable = false
    	splits.density.enable = false
    }
}

This is definitely the thing that permitted me to save more. The build time went down by 15 seconds.

4. Include minimal resources

By default, the build system will include all the resources that your app and the linked libraries defined. As mentioned before, you are probably running your app on a plugged smartphone or on a emulator image, so it means that you don’t need all the resources not required from that device. To do this you should add the resConfigs key in a productFlavors. It specifies the language and the screen density that you need for your build.

android {
    ...
    defaultConfig {...}
    buildTypes {...}
    
    productFlavors {
        dev {
            minSdkVersion 21
            resConfigs ("en", "xxxhdpi")
        }
    }
}

This one obviously depends a lot on the quantity of resources that your app has. I saved 5 seconds this time.

5. Disable PNG crunching

Android Studio elaborates PNGs inside the project to reduce their sizes. Good and necessary thing for release, but not for development. To do this there are more options:

  • you can use the same property that we have used in tip number 3 and disabling the cruncher for aaptOptions.
android {
    ...
    if (project.hasProperty('devBuild')) {
        ...
        aaptOptions.cruncherEnabled = false
    }
} 
  • adding the following to your build.gradle, but you need to remember to set it to true for release
android {
  ...
  aaptOptions {
    cruncherEnabled false
  }
}
  • or you can convert all PNGs in WebP. It is 25% smaller than a PNG but you have to be careful about the OS version requirements.

This depends on the number of resources as well and I saved other 5 seconds adding it.

6. Use Instant Run

When you use Instant Run the build system tries to use the minimal thing required by the device by looking at: target, resources and API level.

7. Avoid inadvertent changes

It’s quite common to use date as app version code like this:

def dateTime = new Date().format("ddMMyyHHmm").toInteger()

android {
    ...
    defaultConfig {
        ...
        versionCode dateTime
    }
}  

This means that every time you run the app, the AndroidManifest changes and it costs time (2,5 times slower than without changes). What you can do is to use the property that we set before (‘devBuild’). So, if the project has that property, it sets the version code to a static value else, it generates a unique value:

def dateTime = project.hasProperty('devBuild') ? 100 : new Date().format("ddMMyyHHmm").toInteger()

android {
    ...
    defaultConfig {
        ...
        versionCode dateTime
    }
}  

Another important thing you have to take care is Fabric. Fabric by default generates a unique build id on every build. Fortunately you can disable it on your build.gradle:

android {
    ...
    buildTypes {
        debug {
        	ext.alwaysUpdateBuildId = false
        	...
        }
    }
}  

There is also a flag to disable crashlytics all together. You can think to use that as well.

android {
    ...
    buildTypes {
        debug {
        	ext.enableCrashlytics = false
        	...
        }
    }
}  

But you also need to disable Crashlytics initialisation

Crashlytics crashlyticsKit = new Crashlytics.Builder()
    .core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
    .build();

Fabric.with(this, crashlyticsKit);

To be honest I had to do the opposite test here because the project already had a static versionCode and I have never used a date for it. The difference was impressive.. 27 seconds more.

8. Don’t use dynamic versions

compile 'com.android.support:appcompat-v7:25.3+'

This makes gradle check for a new version every 24 hours and you are making your build nondeterministic. You should use:

compile 'com.android.support:appcompat-v7:25.3.1'

9. Watch the memory

By default Android Studio Gradle has 1.5 GB of memory. This may be enough or not for the characteristic of your project. You should find what setting is better for you changing this Gradle property

org.gradle.jvmargs=-Xmx1536m

10. Enable Gradle Caching

Gradle 2.3 was only caching predex external libraries. From Gradle 3.5 you can cache all tasks outputs from every previous build from any location. To enable it add this properties to gradle.properties:

org.gradle.caching = true

This is on preview for developers so you should see important improvements day by day. Android Studio 3.0 or later required.

Not happy? Your build process is still slow? There is something wrong! Fortunately Gradle allows you to debug it. Here you can find some suggestions.

./gradlew assembleDebug --info

It tells you what tasks are running and why.

./gradlew assembleDebug --profile

It tells you how much time every task it takes to execute

article-img-centered

Conclusion

In the end the project is more or less 40 seconds faster. If there aren’t changes, it takes just 1 second compared to the 19 it required before. Isn’t it amazing?

This is a quick summary of the nice talk Speeding Up Your Android Gradle Builds (Google I/O ‘17) that I had the pleasure to attend.

Giorgio image

Giorgio

Giorgio is a dedicated Android developer who is always keen to build on his knowledge. Until last year Giorgio was a scout master and is very passionate about the great outdoors.

comments powered by Disqus