Now that we know how Gradle works, how to create custom tasks and plug-ins, how to run tests, and how to set up a continuous integration environment, we have made some progress at Gradle. This chapter includes optimization tips that we didn’t cover in previous chapters that make it easier to build, develop, and deploy Android projects using Gradle.

In this chapter, we will learn about the following topics:

  • Compress the Apk size
  • Speed up Build speed
  • Ignore Lint checks
  • Use Ant in Gradle
  • Advanced tips for application publishing

First let’s look at how to reduce the size of the Apk.

Compress the Apk size

The size of Android APK has been increasing dramatically over the past few years. There are several main reasons for this: the introduction of more third-party public libraries into the project; Adapt to more screen types; Then there are more and more functions as the demand increases.

Keeping Apk as small as possible was essential during the development process, as Google Play itself requires Apk files to be limited to 50MB, and a smaller Apk allows users to download and install apps faster, taking up less storage space on the phone.

In this section, we will see some properties in the Gradle build configuration file that can be used to shrink the APK file.

ProGuard

ProGuard is a Java tool that not only reduces program size, but also optimizes, obfuscates, and prevalidates code at compile time. It iterates through all code paths in the application, finding unused code and removing it. ProGuard also renames classes and fields in code, a process that confuses code and makes it harder to reverse-decompile.

Gradle’s Android plugin has a Boolean property called minifyEnabled on buildType, which we need to set to true to enable ProGuard:

 android {
       buildTypes {
           release {
               minifyEnabled true
               proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
           }
        }
}
Copy the code

When we set minifyEnabled to True, Gradle will execute the proguardRelease task and invoke ProGuard to obfuscate during the build process.

Apk compiled after ProGuard is enabled needs to be tested again, as it is possible that some of the code we still need has been removed due to configuration issues that make many developers tired of ProGuard. To solve this problem, we can define ProGuard’s rules to exclude classes from being deleted or confused. The proguardFiles property is used to specify the file that contains the ProGuard rule. For example, to keep a class, add the following simple rule:

 -keep public class <MyClass>
Copy the code

The getDefaultProguardFile(‘ proGuard-Android.txt ‘) method gets the default ProGuard Settings from a file named proGuard-Android.txt, The file is in the Android SDK/tools/ ProGuard. By default, Android Studio adds the proGuard-rules.pro file to every Android module, where we can add module-specific obturation rules.

ProGuard rules are different for every application or Library you build, so we won’t go into too much detail in this section. For more information about ProGuard and ProGuard rules, check out the official Android ProGuard documentation.

In addition to compressing Java code, reducing the resources used can also help reduce Apk size. Let’s look at how to compress resource files.

Compress Resource files

Gradle and the Gradle Android Plugin can delete all unused resource files when an application is packaged. This is useful if we have old resources that we forgot to delete. Another point is that when importing a third-party library with a large number of resource files, but only a small portion of the resources are used, you can solve this problem by enabling reduced resources. There are two ways to reduce resources, either automatically or manually.

Automatic compression

The automatic compression is done simply by configuring the shrinkResources property in the build. If this property is set to true, the Android build tool will automatically detect which resources are not being used and will not include them in the APK.

One requirement for using this feature is that ProGuard must be enabled. Because of the way resource reduction works, the Android build tool can only figure out which resources are not being used if the code referencing those resources has been removed.

The following code snippet shows how to configure automatic reduction resources on a buildType:

android {
   buildTypes {
       release {
               minifyEnabled = true
               shrinkResources = true
        }
   }
}
Copy the code

To see the exact size of APK after automatic resource shrinking is enabled, run the shrinkReleaseResources task. This task prints out it reduces the package size:

:app:shrinkReleaseResources
Removed unused resources: Binary resource data reduced from 433KB to 354KB: Removed 18%
Copy the code

We can learn more about the resources removed from APK by adding the –info flag to the build command:

$ gradlew clean assembleRelease --info
Copy the code

When this flag is used, Gradle prints out a large amount of detailed information about the build process, including each resource removed from the final build output.

One problem with automatic resource reduction is that it can remove too many resources. In particular, dynamically used resources can be stripped unexpectedly. To prevent this, you can define exceptions in a file named keep.xml placed in res/ RAW/(none need to be created yourself). A simple keep.xml file looks like this:

<? The XML version = "1.0" encoding = "utf-8"? > <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/keep_me,@layout/also_used_*"/>Copy the code

The keep.xml file itself will not be included in the final APK.

Manual compression

A mild way to reduce resources is to remove only certain language files or images at certain resolutions. Some libraries (such as the Google Play service) contain many languages, and if our application only supports one or two languages, it doesn’t make sense to include all the language files in those libraries in the APK. The resConfigs property can be used to configure the resources to be retained, and the rest will be thrown.

For example, if you want to keep only English, Danish, and Dutch strings, you can use resConfigs, as shown below:

android {
       defaultConfig {
           resConfigs "en", "da", "nl"
       }
}
Copy the code

To reserve a specified resolution resource, you can set the following parameters:

android {
       defaultConfig {
           resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
       }
}
Copy the code

Language and resolution resource Settings can be set simultaneously. In fact, every type of resource can be restricted using this property.

If it is difficult to set up ProGuard, or if you simply want to get rid of language or Density resources that your application does not support, resConfigs is a great way to do resource reduction.

Speed up Build speed

Many Android developers who use Gradle complain that compiling takes too long. It can take longer to build than Ant because Gradle has three phases in its build life cycle, and it goes through three phases each time a task is executed. While this makes the entire build process configurable at every step, it also leads to a very slow build. Fortunately, there are several ways to speed up Gradle builds.

Gradle optimization

One way to adjust Gradle build speed is to change some of the default Settings. This is mentioned in chapter 5, “Managing Multi-module Builds,” in parallel build execution, but there are some other Settings that you can tweak.

As a quick review, we can enable parallel building by setting the Parallel property in Gradle. The properties file is placed in the root directory of the project. You need to add the following lines:

org.gradle.parallel=true
Copy the code

Another change is to enable the Gradle daemon, which starts the background process when the build is first run. Any subsequent builds will then reuse this background process, reducing startup costs. As long as you use Gradle, the process exists and terminates after three hours of idle time. Using daemons is especially useful when using Gradle multiple times in a short period of time. We can enable the daemon in the gradle.properties file as follows:

org.gradle.daemon=true
Copy the code

In Android Studio, the Gradle daemon is enabled by default. This means that after the first build from within the IDE, the next build will be faster. However, if you build from a command line interface, the Gradle daemon is disabled unless you enable it in the Settings file.

To speed up compilation, you can also adjust parameters on the Java Virtual Machine (JVM). There is a Gradle property called jvMargs that allows you to set different values for the JVM’s memory allocation pool. The two parameters that have a direct impact on build speed are Xms and Xmx. The Xms parameter is used to set the initial amount of memory to be used by the JVM, while the Xmx parameter is used to set the maximum memory usage for the JVM. We can manually set these values in the gradle.properties file as follows:

org.gradle.jvmargs=-Xms256m -Xmx1024m
Copy the code

We need to set the correct quantities and units, which can be K (kilobytes), M (megabytes) and G (gigabytes). By default, maximum memory allocation (Xmx) is set to 256 MB, and initial memory allocation (Xms) is not set. The best Settings depend on the capabilities of your computer.

The last one can be configured to speed up the building of the attribute is org. Gradle. Configureondemand. This property is particularly useful if you have a complex project with multiple modules, because it tries to limit the time spent in the configuration phase by skipping unnecessary modules for the task being performed. If this property is set to true, Gradle will try to find out which modules have and do not have configuration changes before running the configuration phase. If we had only one Android application and one library in our project, this feature would not be very useful. If you have a lot of modules loosely coupled, this feature can save a lot of build time.

System-level Gradle properties

To apply these properties to all gradle-based projects, you can create a gradle.properties file under the.gradle directory in the user directory. On Windows, the full path to this directory is % UserProfile %.gradle, and on Linux and Mac OS X it is ~ /.gradle. The reason for setting these properties in the user directory, rather than at the project level, is that the CI server needs to ensure that memory consumption is not too high and build time is relatively unimportant, so setting them on the development PC rather than the project prevents CI server from using optimized configuration as well.

Android Studio set up

You can configure Gradle properties in Android Studio Settings to speed up the compilation process. To find the Compiler Settings, open the Settings dialog box, and then navigate to Build, Execution, Deployment | Compiler, in the scheme, we can find the parallel construct, the JVM option, on-demand configuration Settings, etc. These Settings are only displayed in Android modules based on Gradle. You can refer to the screenshot below:

Configuring these Settings from Android Studio is easier than manually configuring them in a build profile, and the Settings interface makes it easy to find individual build properties.

Build analysis

If we want to know which parts of a build slow down the build process, we can configure the entire build process. This is done by adding the –profile flag when executing a Gradle task. When using this flag, Gradle creates a profile report that tells us which parts of the build process are the most time-consuming. Once we know where the bottlenecks are, we can make the necessary changes. Analysis reports will be saved as HTML files in the build/ Reports /profile directory of the module.

Here is the report generated after executing the build task on a multi-module project:

As shown above, the Summary bar shows an overview of the time spent in each phase during the execution of the task. The Configuration bar shows how much time the Configuration phase took. The Dependency Resolution section shows how long each module takes to resolve dependencies. Finally, the Task Execution section contains a very detailed Execution time for each Task, in order of Execution time from highest to lowest.

Jack and Jill

We can enable Jack and Jill to speed up the build. Jack (Java Android Compiler Kit) is a new Android build Kit that compiles Java source code directly into the Android Dalvik executable (DEX) format. It has its own packaging and resource compression process and generates an intermediate library in.Jack format. Jill (Jack Intermediate Library Linker) is a tool that can convert.aar and.jar files into.jack libraries. These tools will reduce build time and simplify the Android build process.

To be able to use Jack and Jill, we need build tools with version 21.1.1 or higher and Android plug-ins with Version 1.0.0 or higher for Gradle. To enable Jack and Jill, just set one property in the build.gradle file defaultConfig block:

Android {buildToolsRevision '22.0.1' defaultConfig {useJack = true}}Copy the code

You can also enable Jack and Jill on specific Build Variants. This way, you can use Jack and Jill on a particular build, and the rest of the build will go the normal way

android {
    productFlavors {
        regular {
            useJack = false
      }
        experimental {
            useJack = true
      }
   }
}
Copy the code

Once useJack is set to true, compressing resources and obfuscation is no longer through ProGuard, but you can still use ProGuard rule syntax to specify certain rules and exceptions for Jack to use.

Ignore Lint checks

When building with Gradle, the Android build task will perform Lint checks on the code. Lint is a static code analysis tool used to flag potential errors in layout and Java code. In some cases, Lint will stop the build as soon as it reports an error. If you haven’t used Lint in your project before and you want to migrate to Gradle, Lint can cause a lot of errors. To keep builds from breaking because of Lint checks, you can configure Gradle to ignore Lint errors and prevent them from stopping builds by disabling abortOnError. This is only a temporary solution, as ignoring Lint checks can result in potential errors like missing translations, which can cause the application to crash. To prevent Lint from blocking the build process, abortOnError can be disabled as follows:

android {
       lintOptions {
           abortOnError false
       }
}
Copy the code

Disabling Lint abort temporarily makes it easier to migrate existing build processes to Gradle.

Advanced tips for application publishing

In Chapter 4, “Creating Build Variants,” you learned how to use buildType and productFlavor to create multiple versions of the same application. However, in some cases, it may be easier to use more specific techniques, such as APK splitting.

Apk split

Each Build Variant can be viewed as a separate version, and each version can have its own code, resources, and manifest file. APK splitting, on the other hand, only affects application packaging. Compilation, minification, obfuscation, and so on are still shared. This mechanism allows you to split APK based on density or application Binary Interface (ABI).

You can configure the APK splits by defining the Splits in the Android splits. To configure a Density split, please create a Density from the Splits. If you want to set up ABI splitting, use ABI blocks.

android {
       splits {
           density {
               enable true
               exclude 'ldpi', 'mdpi'
               compatibleScreens 'normal', 'large', 'xlarge'
           }
     }
}
Copy the code

If your application only needs to support a few screen densities, you can use include to create a density whitelist. To use include, you first need to use the reset() attribute, which resets the contained density list to an empty string.

The compatibleScreens property in the code snippet above is optional. After configuration, Gradle automatically inserts the corresponding node into the manifest file. The configuration in the example is suitable for applications that support regular to very large screens, not devices with small screens.

Splitting APK based on the ABI works in much the same way as splitting by screen density. ABI split is independent of screen size, so there is no property called compatibleScreens, so all properties except compatibleScreens are the same as density split properties.

After the density split is set and the build is executed, Gradle generates a generic APK and several APKs for specific screen densities. This means you get a collection of APKs like this:

app-hdpi-release.apk
app-universal-release.apk
app-xhdpi-release.apk
app-xxhdpi-release.apk
app-xxxhdpi-release.apk
Copy the code

One thing to note about using APK split is that if you are pushing multiple APKs to Google Play, make sure that each APK has a different version code. This means that each split should have a unique version code. You can do this in Gradle by looking at the applicationVariants property

ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]
   import com.android.build.OutputFile
   android.applicationVariants.all { variant ->
       // assign different version code for each output
       variant.outputs.each { output ->
           output.versionCodeOverride =  project.ext.versionCodes.get
             (output.getFilter(OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
    }
}
Copy the code

The above code checks the ABI used in the Build Variant and multiplies the version code to ensure that each variant has a unique version code.

conclusion

After reading this chapter, you know how to reduce the size of the build output APK and how to configure Gradle and JVM to speed up builds. Migrating Ant projects is a piece of cake for you. You also learned a few tips to make development and deployment easier.