17 tips for optimizing Android app build speed

17 tips for optimizing Android app build speed

Long build time will slow down the development progress of the project, especially for large projects. The build time of the app can be as long as ten minutes or as short as a few minutes. The long build time has become a development bottleneck. This article provides some optimization suggestions for improving the app build speed based on Google official documents and my own understanding.

1. Create a variant for the development environment

There are many configurations that you need when preparing the release version of your app, but they are not needed when you are developing your app. Starting unnecessary build processes will make your incremental builds or clean builds very slow, so you need to build a variant that only retains the configuration needed for development. The following example creates a dev and prod variant (prod is the configuration of the release version).

  1. android {
  2. ...
  3. defaultConfig {...}
  4. buildTypes {...}
  5. productFlavors {
  6. // When building a variant that uses this flavor, the following configurations
  7. // override those in the defaultConfig block.
  8. dev {
  9. // To avoid using legacy multidex, set minSdkVersion to 21 or higher.
  10. minSdkVersion 21
  11. versionNameSuffix "-dev"
  12. applicationIdSuffix '.dev'
  13. }
  14. prod {
  15. // If you've configured the defaultConfig block for the release version of
  16. // your app, you can leave this block empty and Gradle uses configurations in
  17. // the defaultConfig block instead . You still need to create this flavor.
  18. // Otherwise, all variants use the "dev" flavor configurations.
  19. }
  20. }
  21. }

2. Avoid compiling unnecessary resources

To avoid compiling and including resources that you haven't tested (such as adding a native language and screen density resource), you can just specify a language and a screen density in your 'dev' flavor, like this:

  1. android {
  2. ...
  3. productFlavors {
  4. dev {
  5. ...
  6. // The following configuration limits the "dev" flavor to using
  7. // English stringresources and xxhdpi screen-density resources.
  8. resConfigs "en" , "xxhdpi"
  9. }
  10. ...
  11. }
  12. }

The above configuration will restrict the dev variant to only use english string resources and xxhdpi screen density resources.

3. Configure debug build of Crushlytics to be unavailable

In debug build state, if you do not need to run crash reporting, you can set this plugin to be unavailable to improve your build speed, as follows:

  1. android {
  2. ...
  3. buildTypes {
  4. debug {
  5. ext.enableCrashlytics = false
  6. }
  7. }

The above is just an example. Crushlytics is a crash reporting analysis tool. We may not need it during development, so there is no need to open it. In our actual development, crash reporting SDKs, data statistics SDKs (such as Umeng Statistics, GrowingIO, Baidu Statistics) are set to unavailable during the development phase to improve the build speed.

4. Build your Debug version using static build configuration values

Typically, in your debug builds, use static/hard-coded values ​​for your manifest or resource configuration files. If your manifest or resource values ​​need to be dynamically updated for each build, then Instant Run cannot perform a code swap - it must rebuild and install a new APK.

For example, using dynamic version codes, version names, resources, or other build logic that changes the manifest file, you will have to build the entire APK every time you want to perform a change, even if the actual change may only require a hot swap. If these build configurations need to be dynamically configured, separate them from your release build variants and keep their static values ​​in your debug builds. As shown in the following build.gradle file:

  1. int MILLIS_IN_MINUTE = 1000 * 60
  2. int minutesSinceEpoch = System.currentTimeMillis() / MILLIS_IN_MINUTE
  3. android {
  4. ...
  5. defaultConfig {
  6. // Making either of these two values ​​dynamic in the defaultConfig will
  7. // require a full APK build and reinstallation because the AndroidManifest.xml
  8. // must be updated (which is not supported by Instant Run).
  9. versionCode 1
  10. versionName "1.0"
  11. ...
  12. }
  13. // The defaultConfig values ​​above are fixed, so your incremental builds don't
  14. // need to rebuild the manifest ( and therefore the whole APK, slowing build times).
  15. // But for release builds, it's okay. So the following script iterates through
  16. // all the known variants, finds those that are "release" build types, and
  17. // changes those properties to something dynamic .
  18. applicationVariants. all { variant ->
  19. if (variant.buildType. name == "release" ) {
  20. variant.mergedFlavor.versionCode = minutesSinceEpoch;
  21. variant.mergedFlavor.versionName = minutesSinceEpoch + "-" + variant.flavorName;
  22. }
  23. }
  24. }

5. Use static version dependencies

When you declare dependencies in your build.gradle file, you should avoid using a + sign at the end of the version number, such as com.android.tools.build:gradle:2.+ Because Gradle checks for updates, using dynamic version numbers can lead to unknown version updates, making it difficult to resolve version differences and slower builds. You should use static or hard-coded version numbers instead. For example: com.android.tools.build:gradle:2.2.2 .

6. Set the on demand configuration to enable state

In order for Gradle to know exactly how to build your app, the build system configures all modules and other dependencies of the project before each build (even if you only want to build or test a module). This makes the build speed of large multi-module projects very slow. To tell Gradle to configure only the modules you want to build, use the following steps to enable the on demand configuration:

(1) Select File -> Settings on the menu bar (if you are on a Mac, select Android Studio -> Preferences)

(2) Navigate to Build, Execution, Deployment -> Compiler

(3) Check the Configure on demand checkbox

(4) Click OK

As shown in the figure:

on_demand.png

7. Create library module

Check the code in your app and extract the modularized code into an Android Library module. Modularizing your code in this way will allow the build system to compile only those modules that have changed and cache the build results for subsequent builds. Similarly, configuring on demand and parallel project execution will be more efficient (when you turn on these features).

8. Create Tasks for custom build logic

After you create a build profile (build profiles are discussed later), if it appears that a relatively large portion of the build time is spent in the "configure project" phase, then review your build.gradle script and look for code that can be included in a custom Gradle Task. By moving some of the build logic into a task, it is run only when needed, the results can be cached for subsequent builds, and this build logic can be executed in parallel (if you have enabled parallel execution of the project). For more details, please read the official Gradle documentation.

Tips:

If your build contains a lot of custom tasks, you may want to clean up your build.gradle file and define custom task classes (that is, custom Gradle plugins). Add your classes to the project-root/buildSrc/src/main/groovy/ directory and Gradle will automatically include them in the class path for all build.gradle files in the project.

9. Configure dexOptions and enable library pre-dexing (dex preprocessing)

Let me add a little knowledge point: Dex-in-process: The newly released Android Studio 2.1 adds a new feature: Dex In Process, which can greatly speed up the recompilation speed and also improve the performance of Instant Run. (The 10th optimization suggestion will mention it)

For more details, see Faster Android Studio Builds with Dex In Process

The Android plugin provides a dexOptions script block so you can configure DEX build features that can improve build speed:

(1)preDexLibraaies: declares whether to pre-dex dependent libraries to make your incremental builds faster. Because this feature may slow down your clean builds, you may want to turn this feature off on your continuous integration server.

(2) maxProcessCount : Sets the maximum number of threads to use when running dex-in-process. The default value is 4.

(3)javaMaxHeapSize: Sets the maximum heap size for the DEX compiler. Instead of setting this property, you should increase the Gradle heap size (this heap size is valid for the DEX compiler when dex-in-process is available)

example:

  1. android {
  2. ...
  3. dexOptions {
  4. preDexLibraries true
  5. maxProcessCount 8
  6. // Instead of setting the heap size for the DEX process, increase Gradle's
  7. // heap size to enable dex- in -process. To learn more, read the next section .
  8. // javaMaxHeapSize "2048m"
  9. }
  10. }

You should test these settings by increasing their values ​​and then profile them to see the effect; you may get a negative impact when you allocate too many resources to the process.

10. Increase Gradle's heap size and enable dex-in-process

Dex-in-process allows multiple DEX processes to run in a single VM, which makes incremental builds and clean builds faster. By default, new projects created with Android Studio 2.1 or higher allocate enough memory to enable this feature. If you do not use Android Studio 2.1 or higher to create a project, you need to set the heap size memory for the Gradle daemon to at least 1536MB. The default is as follows:

gradle_heap.png

The following example sets the Gradle heap memory size to 2048MB in gradle.properties:

  1. org.gradle.jvmargs = -Xmx2048m //Set Gradle heap size to 2G

On larger projects, it may be beneficial to allocate more memory to the Gradle heap. However, if you are on a machine with less memory, you may need to configure the IDE to use less memory. For information on how changing the amount of memory allocated to the IDE and Gradle can affect build performance, see profiling your build.

If you define a value for android.dexOptions.javaMaxHeapSize in your Module build.gradle file, you need to set the Gradle heap size to 512MB more than javaMaxHeapSize and at least 1536MB. For example, if you set javaMaxHeapSize to 1280MB in build.gradle, you need to set the Gradle heap size to at least 1792MB (1280 + 512). Of course, it is better to set it larger.

build.gradle:

  1. dexOptions {
  2. javaMaxHeapSize "1280m"
  3. }

gradle.properties:

  1. org.gradle.jvmargs = -Xmx1792m

11. Convert images to WebP format

WebP is an image file format that provides lossy compression like JPEG and transparent support like PNG, but at the same time its compression quality is better than either JPEG or PNG, reducing the size of the image file without compressing it at build time, so it can improve build speed, especially if your app uses a lot of image resources. But one thing is that when decompressing WebP format images, your device's CPU usage will increase slightly. You can easily convert to WebP format using Android Studio, for details, see convert your images to WebP.

Tips: In addition, converting the images in the project to webP format is also a way to optimize the size of the APK. WebP is supported by Android native 4.0. It can provide images of the same quality as JPEG and PNG but with a smaller size. There is no adaptation problem.

12. PNG crunching is prohibited

If you can’t (or don’t want to) convert your PNG images to WebP, you can still improve build speed by disabling automatic image compression every time you build your app. To disable this optimization, add the following code to your build.gradle:

  1. android {
  2. ...
  3. aaptOptions {
  4. cruncherEnabled false
  5. }
  6. }

13. Use Instant Run

Instant Run significantly reduces the time it takes to update your app by pushing certain code and resource changes without having to build a new app, and in some cases, without even restarting the current activity. Use Instant Run by clicking Apply Changes (yellow ⚡ icon) after making code changes. It is turned on by default when you do the following:

  • Build your app using the debug build variant
  • Gradle plugin version 2.3.0 or higher
  • Set minSdkVersion to 15 or higher in module-level build.gradle
  • Release your app on Android 5.0 (API level 21) or higher. Click Run.

14. Using the build cache

The build cache stores certain artifacts generated by the Android Gradle plugin when building your project (such as AAR packages and pre-dexed versions of remote dependencies). When you use the cache, your clean builds are faster because the build system can simply reuse their caches for subsequent builds instead of recreating them.

New projects using the Android Gradle plugin 2.3.0 or higher have the build cache enabled by default (unless you disable it). For more information, read Accelerate clean builds with build cache.

15. Do not use annotation processors

Gradle 2.1 and later can build Java incrementally. Incremental builds are not available when using annotation processors. If possible, avoid using annotation processors so that you benefit from building only changed classes. (Improve compilation time)

16. Profile your build

In large projects (or projects that implement a lot of custom build logic), you may need to have a deeper understanding of the build process to find bottlenecks. You can analyze how long each gradle task takes to execute at each stage of the build lifecycle. For example, if your build data shows that Gradle spends a lot of time configuring your project, this suggests that you need to put your custom build logic outside the configuration phase. In addition, if the mergeDevDebugResources task consumes a lot of build time, this indicates that you need to convert images to WebP format or disable PNG Crunching (Optimization Tips 11 and 12)

Improving your build speed with build profiling usually involves running your build with profiling turned on, changing the build configuration several times, profiling, and watching the results change.

To generate and view a build profile, perform the following steps:

1) Open the project in Android Studio and select View -> Tool Windows -> Terminal to open the command line.

2) Perform a clean build Enter the following command. When you analyze your build, you need to perform a clean build between each build because Gradle will skip tasks whose inputs have not changed. Therefore, the second build with unchanged inputs will usually run faster because tasks are not re-run. Therefore, running a cleanask between builds ensures that you analyze the entire build process.

  1. //If you use ./gradlew on Mac or Linux
  2. gradlew clean

3) Select one of the product flavors to perform a debug build, for example, dev flavor, as follows:

  1. gradlew --profile --recompile-scripts --offline --rerun-tasks assembleFlavorDebug

Command analysis:

--profile: Enable profiling

--recompile-scripts: Force script recompilation to skip cache

--offline: Disables Gradle from fetching offline dependencies. This ensures that any delays are caused by Gradle trying to update dependencies and do not mislead your analysis data. You should prepare a build to ensure that Gradle has downloaded and cached dependencies.

--rerun-tasks: Forces Gradle to rerun all tasks and ignore any task optimizations.

4) After the build is completed, in the project-root/build/reports/profile/ directory:

[[188867]]

profile_build.png

5) Right-click profile_timestamp.html and choose Open in browser. You will see the following picture. You can observe each tab in the report to understand your build. For example, Tasks Execution shows the execution time of each task.

profile_in_brower.png

Task Execution displays the execution time of each task, as shown below:

task_execution.png

6), Optional: Before making any changes to the Project or build configuration, repeat step 3 several times, but remove the --rerun-tasks flag. Since Gradle tries to save time and does not re-execute tasks whose inputs have not been modified (they are marked as UP-TO-DATE under the Task Execution tab, as shown below:), you can identify which tasks have not been executed. For example, if :app:processDevUniversalDebugManifest is not marked as UP-TO-DATE, it means that your build configuration is to dynamically update the manifest file for each build. However, some tasks still need to be executed every time, such as: :app:checkDevDebugManifest

profile_up_to_date.png

Now that you have a build analysis report, you can start looking for optimization opportunities by observing each tab of the build report. Some build configurations require experimentation because they benefit differently in different projects or workspaces. For example, large projects with a lot of code may benefit from using obfuscation, removing unused code, and reducing the size of the APK. However, small projects may benefit from turning off obfuscation (which is still time-consuming). In addition, increasing or decreasing the Gradle heap size on a machine with little memory may also have the opposite effect.

17. Project componentization

For large projects, the above optimization suggestions may have some effect, but the build speed is still a bit slow, then you can consider componentization, split the project into separate components, each module in the development environment is an APK, and when released, each module is a lib for the main project to use. Due to space constraints, I will not introduce componentization in detail here. Componentization is a trend now. If you have the energy or strength, componentization is a very good choice.

at last

The above are some optimization suggestions for solving the slow app build speed. If you think your project build speed is slow, you can try these optimization items. If you have any questions, please leave a message in the comment area. If you have any better optimization suggestions, you can also leave a message below and I will add it to the end of the article.

refer to

  • Optimize Your Build Speed
  • Gradle

<<:  Android compatibility | NDK toolset update notes

>>:  Deeply debug network requests using WireShark

Recommend

Stop hoarding food! Someone has already gotten into trouble!

This article was reviewed by Pa Li Ze, chief phys...

APP promotion: How to increase users three times at low cost?

1. Project Background A domestic financial APP pr...

Are "999", "990" and "925" the same? What kind of silver are they?

Review expert: Gan Qiang, lecturer at Beijing Ins...

Advanced Copywriting Guide | How to be a good copywriter?

Copywriting is not about creating words and sente...

How to promote a website? Detailed explanation of website promotion!

Promotion goals Compared with the complete Intern...