preface

To be a good Android developer, you need a completeThe knowledge systemHere, let’s grow up to be what we want to be.

First, rediscover Gradle

Engineering construction tools from the ancient MK, make, cmake, qmake, to mature Ant, Maven, Ivy, and finally to the Internet era of SBT, Gradle, experienced a long history of evolution and change.

Gradle, as a new generation of building tools, undoubtedly has its own huge advantages. Therefore, it is self-evident that it is important to master all kinds of gestures and use scenarios of Gradle building tools.

In addition, Gradle has become an essential part of advanced Android knowledge. Therefore, mastering Gradle and improving the depth of our automated build technology can make us more powerful.

1. What is Gradle?

  • 1) It is a powerful build tool, not a language.
  • 2) It uses the Groovy language to create a DSL that is not a language itself.

2. Why use Gradle?

For the following three reasons:

  • 1) It’s one of the latest and most powerful build tools available, and there’s a lot we can do with it.
  • 2) Use programs instead of traditional XML configuration to make project building more flexible.
  • 3) Rich third-party plug-ins, so that we can use them as we want.

Gradle build process

In general, a complete Gradle build is divided into three parts:

  • Initialization phase:First, during initialization Gradle decides which Project modules to build and creates a corresponding Project instance for each module.
  • Configuration phase:Then, the modules of each project in the project are configured and the configuration scripts contained therein are executed.
  • Task execution:Finally, each Gradle task that participates in the build process is executed.

Two, packing speed

Mastering Gradle’s build speed skills can save us a lot of build time, and larger projects with more and more dependent modules will save more time, so it is a significant input-output ratio.

Upgrade to the latest Gradle version

Bringing Gradle and the Android Gradle Plugin up to date will give you an obvious boost in build speed, especially if you are using a lower version.

2. Enable the offline mode

With Android Studio in offline mode, all build operations go to the local cache, which, no doubt, will greatly reduce build times.

3. Configure the maximum heap memory of AS

By default, AS has a maximum memory size of 1960MB. You can select Help => Edit Custom VM Options to open a studio. Vmoptions file. Changing the second line -xmx1960m to -xmx3g increases the available memory to 3GB.

Remove unnecessary Moudle or merge some modules

Too much Moudle can complicate Module dependencies in a project. Gradle checks for dependencies between modules at build time. Gradle then spends a lot of build time sorting out the dependencies between modules. To avoid problems caused by modules referencing each other. In addition to removing unnecessary Moudle or merging some modules, we can also package stable underlying modules into aar, upload them to the local Maven repository of the company, and rely on them remotely.

5. Delete unnecessary files from Module

  • 1) If we don’t need to write unit tests, we can delete the test directory directly.
  • 2) If we don’t need to write UI test code, we can also delete the androidTest directory.
  • In addition, if there is only pure code in the Moudle, you can delete the res directory directly.

6. Remove unwanted resources from your project

Android Studio provides the function of automatically detecting invalid files and deleting them, namely the Remove Unused Resource function. The operation path is as follows:

Right-click and select Refactor. Right-click Remove Unused Resource and click Refactor

Note that there is no need to select Delete unused@ id declarations too. If you use databinding, it may fail to compile.

7. Optimize the use of third-party libraries

The general optimization steps are as follows:

1) Replace existing tripartite libraries of the same type with smaller libraries.

2) Use exclude to exclude some unnecessary or duplicate dependencies in the three-party library.

For example, I used this technique in the Awesome WanAndroid project, relying on LeakCanary to find that it contained the support package, so we could exclude it with a code like this:

   debugImplementation (rootProject.ext.dependencies["leakcanary-android"]) {
       exclude group: 'com.android.support'
   }
   releaseImplementation (rootProject.ext.dependencies["leakcanary-android-no-op"]) {
       exclude group: 'com.android.support'
   }
   testImplementation (rootProject.ext.dependencies["leakcanary-android-no-op"]) {
       exclude group: 'com.android.support'
   }
Copy the code

3) Use debugImplementation to rely on libraries that are only used during debug, such as some offline performance testing tools. Here is an example code:

// Use this only when BlockCanary is enabled in the debug package for stalled monitoring and notifications
debugImplementation 'com. Making. Markzhai: blockcanary - android: 1.5.0'
Copy the code

8. Utilize the local cache of the company’s Maven repository

When the first development introduces a new or updated version of the repository, the company’s Maven repository caches the corresponding version. This way, other development colleagues can get the cache directly from the company’s Maven repository when the project is built.

Set minSdkVersion to 21 during Debug build

This way, we can avoid slowing down the build speed by using the MutliDex. Add the following code to build.gradle in the main Moudle:

    productFlavors {
        speed {
            minSdkVersion 21}}Copy the code

After synchronizing the project, select speedDebug from Build Variants on the right side of Android Studio, as shown below:

It is important to note that the actual lowest version of our current project, for example, is 18. Now that we have speedDebug enabled, the project will be written with 21 as standard. In this case, we need to pay attention to the API between 18 and 21. For example, I use the new Material Design control released in version 21 in the layout, which is ok at this time, but in fact, we need to do the corresponding adaptation for the corresponding layout below version 21.

You can also define flavors in the SRC directory and create a new flavor file named flavors.

10. Configure Gradle. properties

The common configuration items are as follows:

    // Build initialization requires many tasks, such as starting the Java virtual machine, loading the virtual machine environment, loading the class file, etc. Configure this to enable the thread daemon, and only enable the thread for the first build (Gradle 3.0 by default).
    org.gradle.daemon=true  
    
    // Configure the size of the vm at compile time
    org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8  
    
    // Enable parallel compilation, which uses multiple threads, only for modular projects (there are multiple Library projects that depend on the main project)
    org.gradle.parallel=true  
    
    // The biggest advantage is that it helps speed up multi-moudle projects. When compiling projects with multiple modules that depend on each other, Gradle will choose to compile as needed, i.e. only the related modules will compile
    org.gradle.configureondemand=true   
    
    // Enable the build cache, Gradle 3.5 new caching mechanism, can cache the output of all tasks,
    // Unlike buildCache, which only caches external libs of dex, it can be reused
    // Any time the build cache is set to include other branches of the build cache
    org.gradle.caching=true
Copy the code

The most effective configuration item here is to configure the vm size at compile time. Let’s analyze the meaning of the parameter in detail, as follows:

  • – Xmx2048m:Specifies that the maximum heap memory that the JVM is allowed to allocate is 2048MB, which is allocated on demand.
  • – XX: MaxPermSize = 512 m:Specifies that the maximum amount of non-heap memory that the JVM is allowed to allocate is 512MB, which is also allocated on demand as above.

11. Configure DexOptions

We can set maxProcessCount in the dexOptions configuration to 8 so that the maximum number of concurrent processes at compile time can be increased to 8.

12. Use Walle to improve the efficiency of multi-channel package production

Walle is a new generation of channel package packaging under The Signature V2 Scheme of Android. It adds custom channel information to the Apk Signature Block to generate channel packages, thus improving the generation efficiency of channel packages. In addition, it can also be used as a stand-alone tool, or deployed on the HTTP server to process channel package Apk upgrade network requests in real time. Students who need it can refer to Meituan’s Walle.

13. Set the language supported by the application

If the application is not internationalized, we can make the application support only Chinese resource configuration by setting resConfigs to “zh”. As follows:

    android {
        defaultConfig {
            resConfigs "zh"}}Copy the code

Use incremental compilation

Gradle is built in the following three ways:

  • 1) Full Build: Build from 0.
  • 2) Incremental Build Java Change: Incremental build of Java changes that have been made after the source code has been modified and have been built before.
  • 3) Incremental build Resource change: Incremental build after modifying the resource file and has been built before.

Incremental compilation has been used by default since Gradle 4.10, which tests to see if any Gradle Task input or output has changed since the last build. If not, Gradle considers the task to be up to date and therefore skips its action. Since Gradle can analyze the dependencies of a project down to the class level, only the affected classes will be recompiled. If you need to start incremental compilation on an older version, you can use the following configuration:

    tasks.withType(JavaCompile) {
        options.incremental = true
    }
Copy the code

15. Use loops for dependency optimization (🔥)

In the Build. gradle of the App Moudle for the Awesome-WanAndroid project, there are almost a few hundred lines of dependent code, as follows:

    dependencies {
        implementation fileTree(include: ['*.jar'].dir: 'libs')

        / / starter
        api files('libs/launchstarter - release - 1.0.0. Aar')
        
         //base
        implementation rootProject.ext.dependencies["appcompat-v7"]
        implementation rootProject.ext.dependencies["cardview-v7"]
        implementation rootProject.ext.dependencies["design"]
        implementation rootProject.ext.dependencies["constraint-layout"]
        
        annotationProcessor rootProject.ext.dependencies["glide_compiler"]
        
         //canary
        debugImplementation (rootProject.ext.dependencies["leakcanary-android"]) {
            exclude group: 'com.android.support'
        }
        releaseImplementation (rootProject.ext.dependencies["leakcanary-android-no-op"]) {
            exclude group: 'com.android.support'
        }
        testImplementation (rootProject.ext.dependencies["leakcanary-android-no-op"]) {
            exclude group: 'com.android.support'}...Copy the code

Is there a good way not to write so many dependencies in build.gradle?

Yes, you use a loop to traverse dependencies. The answer seems simple, but it’s not so simple to deal with all the situations you encounter when you rely on it. Below, I directly give the corresponding adaptation code, you can directly use.

First, the dependency configuration of build.gradle in the app is as follows:

    // Handle all aar dependencies
    apiFileDependencies.each { k, v -> api files(v)}

    // Handle all xxxImplementation dependencies
    implementationDependencies.each { k, v -> implementation v }
    debugImplementationDependencies.each { k, v -> debugImplementation v }
    releaseImplementationDependencies.each { k, v -> releaseImplementation v }
    androidTestImplementationDependencies.each { k, v -> androidTestImplementation v }
    testImplementationDependencies.each { k, v -> testImplementation v }
    debugApiDependencies.each { k, v -> debugApi v }
    releaseApiDependencies.each { k, v -> releaseApi v }
    compileOnlyDependencies.each { k, v -> compileOnly v }
    
    AnnotationProcessor dependencies
    processors.each { k, v -> annotationProcessor v }
    
    // Handle all dependencies containing exclude
    implementationExcludes.each { entry ->
        implementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry)
            }
        }
    }
    debugImplementationExcludes.each { entry ->
        debugImplementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry.key, module: childEntry.value)
            }
        }
    }
    releaseImplementationExcludes.each { entry ->
        releaseImplementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry.key, module: childEntry.value)
            }
        }
    }
    testImplementationExclude.each { entry ->
        testImplementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry.key, module: childEntry.value)
            }
        }
    }
    androidTestImplementationExcludes.each { entry ->
        androidTestImplementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry.key, module: childEntry.value)
            }
        }
    }
Copy the code

Then, you can configure the dependency array with the corresponding name in the config.gradle global dependency management file. The code looks like this:

    dependencies = [
            // base
            "appcompat-v7"                      : "com.android.support:appcompat-v7:${version["supportLibraryVersion"]}". ]  annotationProcessor = ["glide_compiler"                    : "com.github.bumptech.glide:compiler:${version["glideVersion"]}". ]  apiFileDependencies = ["launchstarter"                                   :"Libs/launchstarter - release - 1.0.0. Aar." "
    ]
    
    debugImplementationDependencies = [
            "MethodTraceMan"                                  : "Com. Making. Zhengcx: MethodTraceMan: 1.0.7"]... implementationExcludes = ["Com. Android. Support. Test. Espresso: espresso - idling - resource: 3.0.2." " : [
                    'com.android.support' : 'support-annotations']]...Copy the code

Concrete code examples can be viewed on the Awesome-WanAndroid build.gradle and config.gradle.

Gradle common commands

1. Gradle query command

1) View the main tasks

    ./gradlew tasks
Copy the code

2) View all tasks, including cache tasks, etc

    ./gradlew tasks --all
Copy the code

2. Run the Gradle command

1) Run a task [TaskName] on module [moduleName]

    ./gradlew :moduleName:taskName
Copy the code

3, Gradle quick build command

Gradle provides a series of quick build commands to replace the IDE’s visual build operations, such as clean, build, etc. Note that the build command builds packages for both debug and release environments.

1) View the build version

    ./gradlew -v
Copy the code

2) Clear the Build folder

    ./gradlew clean
Copy the code

3) check the dependencies and compile the package

    ./gradlew build
Copy the code

4) Compile and install the debug package

    ./gradlew installDebug
Copy the code

5) Compile and print logs

    ./gradlew build --info
Copy the code

6) Compile and output the performance report, which is usually in the build project root directory build/ Reports /profile

    ./gradlew build --profile
Copy the code

7) Build and print the stack log in debug mode

    ./gradlew build --info --debug --stacktrace
Copy the code

8) Force updates to the latest dependencies, clear builds before building

    ./gradlew clean build --refresh-dependencies
Copy the code

9) Compile and Debug package

/ Gradlew assembleDebug # Take the first letter of each word./ Gradlew aDCopy the code

10) Compile and type the Release package

./gradlew assembleRelease # Take the first letter of each word./gradlew aRCopy the code

Gradle build and install commands

1) Package and install in Release mode

    ./gradlew installRelease
Copy the code

2) Uninstall the Release mode package

    ./gradlew uninstallRelease
Copy the code

3) All channels are packaged in debug release mode

    ./gradlew assemble
Copy the code

5. Gradle Check package dependencies

1) Check the dependencies in the root directory of the project

    ./gradlew dependencies
Copy the code

2) Check the dependencies under the APP module

    ./gradlew app:dependencies
Copy the code

3) Check the dependent projects that contain the implementation keyword in the APP module

    ./gradlew app:dependencies --configuration implementation
Copy the code

Use Build Scan to diagnose the application Build process

Before we learn about Build Scan, we need to take a look at the old Gradle Build diagnostic tool Profile Report.

1, the Profile report

In general, we would use the following command to generate a local build analysis report:

    ./gradlew assembleDebug --profile
Copy the code

Here, we run this command in the root directory of the Awesome-WanAndroid App to get four blocks of views. So let’s see.

1), and the Summary

Gradle Build information overview interface, used to view the Total Build Time, initialization (Startup, Settings and BuildSrc, Loading Projects), configuration, and task execution Time. As shown in the figure below:

2), the Configuaration

Gradle sets the time spent on each project. We can see the individual configuration time of All projects, app modules, and other modules. As shown in the figure below:

3)

The amount of time Gradle spends resolving dependencies for each task. As shown in the figure below:

4), Task Execution

The time spent by Gradle executing each Gradle task. As shown in the figure below:

Note that the Execution time of the Task is the sum of the Execution time of all Gradle tasks. In fact, multi-module tasks are executed in parallel.

2, Build the Scan

Build Scan is an official performance inspection tool for diagnosing the application Build process. It identifies problems that cause slow Build times. To enable Build Scan diagnosis, run the following command under the project:

    ./gradlew build --scan 
Copy the code

Appears when using the above command if you are using a Mac

    zsh: permission denied: ./gradlew
Copy the code

You can assign execution permission to GradLew by adding the following lives:

    chmod +x gradlew
Copy the code

At the end of the build –scan command, we can see the following:

As you can see, clicking the link below in Publishing Build Scan takes you to the diagnostic page for Build Scan.

Note that if you are using Build Scan for the first time, you will need to activate Build Scan using your own mailbox first. As shown in the following interface:

Here, I typed in my email [email protected] and hit Go! After that, we can log in to our email to confirm authorization. As shown in the figure below:

Just click Discover Your build.

After successful authorization, we can see the diagnostic page for Build Scan. As shown in the figure below:

As can be seen, there are a series of functional tabs on the right side of the interface for us to choose and view. Here is the Summary overview interface by default. Our purpose is to view the build Performance of the application, so click the Performance TAB on the right side to see the build analysis interface as shown in the figure below:

As can be seen from the figure above, in addition to Build, Configuration, Dependency resolution and Task execution in the Performance interface, There are also daemons, Network activities, Settings and suggestions.

In the Build interface, there are three sub-projects, namely Total Build Time, Total Garbage collection Time, Peak Heap Memory Usage, We have already analyzed the configuration items in Total Build Time, so let’s take a look at the meanings of the remaining two items, as follows:

  • Total garbage collection time:Total garbage collection time.
  • Peak Heap Memory Usage:Maximum heap memory usage.

For Peak heap Memory Usage, there are three subitems with the following meanings:

  • 1) PS Eden Space: Young Generation Eden (Garden of Eden) physical memory area. Most of the new objects generated in the program are in Eden.
  • 2) PS Survivor Space: Two Survivor physical memory areas of Young Generation Eden. When Eden is full, surviving objects are copied to one of the Survivor extent. when this Survivor extentis full, surviving objects in this Survivor extentare copied to another Survivor extent. when this Survivor extentis also full, surviving objects are copied to the tenured generation.
  • 3) PS Old Gen: Old Generation. Generally, the life cycle of objects in the Old Generation is relatively long.

Since our intent was to focus on the build time of the project, we focused directly on Task Execution. As shown in the figure below:

As you can see, all tasks in the Awesome-WanAndroid project are Not cacheable. At this point, we can scroll down the interface to see the build time of all tasks. As follows:

If we want to see a tinyPicPluginSpeedRelease this task execution details, you can click: app: tinyPicPluginSpeedRelease this one, then, will jump to the Timeline interface, Show tinyPicPluginSpeedRelease execution of corresponding information. As shown in the figure below:

In addition, here we click the first icon on the top right of the popup box: Focus on Task in Timeline to see the exact position of the task in the entire Gradle build timeline, as shown in the picture below:

At this point, we can see that Build Scan is much more powerful than Profile Report, so I strongly recommend using it as a priority for Gradle Build time diagnosis and optimization.

Five, the summary

The running time of each Gradle build decreases with the number of times the project is compiled. Therefore, in order to accurately evaluate the optimization effect of Gradle build speed, we can run the following commands before and after optimization to compare and analyze, as shown below:

    gradlew --profile --recompile-scripts --offline --rerun-tasks assembleDebug
Copy the code

The meanings of parameters are as follows:

  • Profile:Enable performance detection.
  • Recompile – scripts:Recompile the script without caching.
  • Offline:Enable the offline compilation mode.
  • The return – task:Run all Gradle tasks and ignore all optimizations.

In addition, Facebook’s Buck and Google’s Bazel are excellent build tools, so there are three reasons why they don’t use open source build tools:

  • 1) unified build tools: all internal projects use the same set of build tools, including Android, Java, iOS, Go, C++, etc. A uniform optimization of compilation tools would benefit all projects.
  • 2) Code organization: Facebook and Google put all their projects in one repository, so the repository is huge, and they don’t use Git. Currently Google uses Piper, and Facebook is based on HG, which is also a distributed file system.
  • 3) Extreme performance: Buck and Bazel do perform better than Gradle, including their various build optimizations. However, they are too customized and do not support external dependencies such as Maven and JCenter.

However, the optimization ideas inside the Buck and Bazel compilation and build tools are worth learning and reference, if you are interested in it. In the next article, we will look at the essential foundation of Gradle – Groovy, which will give us a solid foundation for the rest of Gradle learning. Stay tuned.

Reference links:


Gradle Github address

Gradle configuration best practices

Increase compilation speed by 50%! Practice of improving the effectiveness of Alitrip App project

Gradle speed up: Save you a cup of coffee a day

Speed up gradle builds

Gradle modular configuration: Keep your Gradle code under 100 lines

7, Gradle android-build common command parameters and explanation

8, Android packaging speed up practice

GRADLE build best practices

Thank you for reading this article. I hope you can share it with your friends or tech groups. It means a lot to me.

I hope we can be friends beforeGithub,The Denver nuggetsLast time we shared our knowledge.