Optimization Techniques to increase Build Performance!

Optimization Techniques to increase Build Performance!

Learn optimization techniques to fasten up build times, smaller APK sizes and better resource management!

As Android developers, we're always looking for ways to improve our workflow. One area that often gets overlooked is the optimization of APK builds. By refining this process, we can reap several benefits, including faster build times, smaller APK sizes, and better resource management. Let's delve deeper into how we can achieve this.

Optimization Techniques

Minify and Shrink Code

Android Studio's default build settings include a code-shrinking feature that removes unused code and resources to reduce your APK size. The Gradle build system uses tools like R8 or ProGuard for this.

ProGuard

ProGuard is a tool that shrinks, optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and methods with semantically obscure names. The result is a smaller-sized .apk file that is more difficult to reverse engineer.

android {
    ...
    buildTypes {
        release {
            // Enables code shrinking for the release build type
            minifyEnabled true

            // Specifies the ProGuard configuration files
            // The first file is the default ProGuard settings from the Android SDK
            // The second file is the developer's custom ProGuard rules
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

R8

R8 is a tool that combines desugaring, shrinking, obfuscating, optimizing, and indexing — all in one step, which can lead to significant build performance improvements.

android {
    buildTypes {
        release {
            // Enables code shrinking for the release build type
            minifyEnabled true

            // Specifies the ProGuard configuration files
            // The first file is the default ProGuard settings from the Android SDK
            // The second file is the developer's custom ProGuard rules
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

            // Specifies whether to use ProGuard for code shrinking and optimization
            // Setting this to false means R8, the new code shrinker from Google, is used instead
            useProguard false
        }
    }
}

R8 is designed to be faster and more efficient than ProGuard. It produces smaller APKs because it removes more unused code and resources. As the default option in Android Studio, R8 is integrated more smoothly into the build process and is easier to configure.

R8 reduces app size by 10% while ProGuard reduce app size by 8.5%

Enable Shrink Resources

When you enable resource shrinking, the Android build system will remove all the unused resources from the APK. You can enable it in your build.gradle file:

android {
    buildTypes {
        release {
            // Enables resource shrinking, which removes unused resources from the packaged app
            shrinkResources true

            // Enables code shrinking, which removes unused code and resources from your release build
            minifyEnabled true

            // Specifies the ProGuard configuration files
            // The first file is the default ProGuard settings from the Android SDK
            // The second file is the developer's custom ProGuard rules
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

Android App Bundles

Android App Bundles (AAB) is a new upload format that includes all your app’s compiled code and resources, but defers APK generation and signing to Google Play.

There are few ways through which Android App Bundles help optimize builds:

Modularization

Modularization in Android App Bundles is a powerful technique for optimizing APK builds. It involves organizing your codebase into independent, self-contained modules, each serving a clear purpose. This practice not only enhances code readability and maintainability but also offers significant benefits in terms of APK size and delivery.

Here's a step-by-step guide on how to implement modularization in Android App Bundles:

  • Create a new module: In Android Studio, go to File > New > New Module... and choose the type of module you want to create. Give it a name and click Finish.
```groovy

// This is a Groovy code block in your build.gradle file
// It defines a new module in your Android project
apply plugin: 'com.android.application'

android {
    // ...
}

dependencies {
    // ...
}
  • Define the module in your app's settings.gradle file: This file is located in the root directory of your project. Add an include statement for your new module
// This is a Groovy code block in your settings.gradle file
// It includes the new module in your Android project
include ':app', ':newModule'
  • Add dependencies to the new module: In the build.gradle file of your new module, add any dependencies that the module needs.
// This is a Groovy code block in your newModule's build.gradle file
// It adds dependencies to your new module
dependencies {
    implementation project(':app')
    // ...
}
  • Use the new module in your app: Now you can use the classes and resources from your new module in your app. Just make sure to add the necessary import statements in your Java or Kotlin files.

  • Build your app: Finally, build your app as usual. Android Studio will automatically include your new module in the APK.

Dynamic Delivery

Dynamic Delivery is a feature of Android App Bundles that helps optimize APK builds. It works by generating and serving optimized APKs for each user's device configuration, which means users only download the code and resources they need to run the app on their device. This results in smaller APK sizes, faster downloads, and less storage usage on the user's device.

Dynamic Feature Modules

Dynamic feature modules allow you to separate certain features and resources from the base APK and include them in your app bundle as installable modules. Users can download and install these modules on-demand after they've already installed the base APK.

Here's a step-by-step guide to create a dynamic feature module in Android Studio,

  • Open Android Studio: Start by opening your project in Android Studio.

  • Create New Module: Go to File > New > New Module.... In the Create New Module window that appears, select Dynamic Feature Module and click Next.

  • Configure Module: In the Configure your new module screen, enter the module name, package name, and minimum SDK. You can also check the Include module in base module checkbox to automatically include the dynamic feature module in your base module's build.gradle file. Click Next.

  • Configure Dynamic Delivery: In the Configure Dynamic Delivery for <module_name> screen, you can choose whether the module should be included at install time (Fusing) or downloaded later (On Demand). Click Finish.

  • Update Base Module: Open the build.gradle file for your base module and make sure it includes the dynamic feature module:

android {
    ...
    // Specifies the dynamic feature modules associated with this app
    // Replace "<module_name>" with the name of your dynamic feature module
    dynamicFeatures = [":<module_name>"]
    ...
}
  • Update Dynamic Feature Module: Open the build.gradle file for your dynamic feature module. It should include the com.android.dynamic-feature plugin and a dependency on the base module:
// Applies the dynamic feature plugin to this module
// This plugin enables the module to be downloaded on-demand as a dynamic feature of your app
apply plugin: 'com.android.dynamic-feature'

android {
    // Contains all the Android-specific configuration options for this dynamic feature module
    ...
}

dependencies {
    // Specifies that this dynamic feature module depends on the base module of your app
    // Replace '<base_module_name>' with the name of your base module
    implementation project(':<base_module_name>')
    ...
}
  • Create Feature Content: Now you can create activities, fragments, and other resources in your dynamic feature module just like in any other module.

  • Use Feature: To use the dynamic feature, you need to request it using the SplitInstallManager API. Here's an example of how to request a dynamic feature:

// Creates an instance of SplitInstallManager, which is used to download and install dynamic feature modules
SplitInstallManager splitInstallManager = SplitInstallManagerFactory.create(context);

// Creates a request to download a dynamic feature module
// Replace "<module_name>" with the name of the module you want to download
SplitInstallRequest request = SplitInstallRequest
        .newBuilder()
        .addModule("<module_name>")
        .build();

// Starts the download and installation of the dynamic feature module
splitInstallManager.startInstall(request)
        .addOnSuccessListener(sessionId -> {
            // This block is executed if the download and installation is successful
        })
        .addOnFailureListener(exception -> {
            // This block is executed if the download or installation fails
        });

In this example, replace <module_name> with the name of your dynamic feature module.

✌️ Please note that dynamic feature modules are a part of the Android App Bundle and Dynamic Delivery system. They require the app to be distributed through Google Play or another service that supports App Bundles.

On-Demand Resources

On-Demand Resources, also known as Dynamic Feature Modules in Android, are a powerful tool for optimizing APK builds. They allow developers to separate certain features and resources from the base APK and include them only when needed. This can significantly reduce the size of the initial APK download and install, leading to quicker downloads, less storage usage, and potentially higher install rates.

Here's how On-Demand Resources work:

  • Separate Features: Developers can separate certain features or resources into Dynamic Feature Modules. These modules are separate from the base APK and can be downloaded and installed at runtime as needed.

  • Request Features: When a user wants to use a feature contained in a Dynamic Feature Module, the app requests the module using the SplitInstallManager API. Here's an example of how to request a module:

// Creates an instance of SplitInstallManager, which is used to download and install dynamic feature modules
SplitInstallManager splitInstallManager = SplitInstallManagerFactory.create(context);

// Creates a request to download a dynamic feature module
// Replace "<module_name>" with the name of the module you want to download
SplitInstallRequest request = SplitInstallRequest
        .newBuilder()
        .addModule("<module_name>")
        .build();

// Starts the download and installation of the dynamic feature module
splitInstallManager.startInstall(request)
        .addOnSuccessListener(sessionId -> {
            // This block is executed if the download and installation is successful
        })
        .addOnFailureListener(exception -> {
            // This block is executed if the download or installation fails
        });

In this example, replace <module_name> with the name of your Dynamic Feature Module.

  • Download and Install: The Dynamic Delivery system downloads and installs the requested module. The app can then access the features and resources contained in the module.

  • Uninstall Modules: If a Dynamic Feature Module is no longer needed, the app can request to uninstall it, freeing up storage space on the user's device.

By using On-Demand Resources, developers can create smaller, more efficient APKs that only include the features and resources a user needs at any given time. This can lead to a better user experience and more efficient use of device resources.

Asset Pack Delivery

Asset Pack Delivery in Android App Bundles is a powerful feature that helps optimize APK builds. It allows developers to deliver large resources, such as graphics and media files, separately from the main APK. This means that these resources can be downloaded on-demand, reducing the initial download size and enabling faster updates.

Here's a step-by-step guide on how to implement Asset Pack Delivery in Android App Bundles:

  • Create a new asset pack: In Android Studio, go to File > New > New Asset Pack... and choose the type of asset pack you want to create. Give it a name and click Finish.
// This is a Groovy code block in your build.gradle file
// It defines a new asset pack in your Android project
android {
    // ...
    assetPacks {
        ":myAssetPack"
    }
}
  • Add assets to the asset pack: Place any assets that you want to include in the asset pack in the src/main/assets directory of the asset pack module.

  • Define the delivery mode of the asset pack: In the build.gradle file of your asset pack module, specify the delivery mode of the asset pack. This can be install-time, fast-follow, or on-demand.

// This is a Groovy code block in your asset pack's build.gradle file
// It defines the delivery mode of the asset pack
android {
    assetPack {
        packName = "myAssetPack"
        dynamicDelivery {
            deliveryType = "on-demand"
        }
    }
}
  • Use the assets in your app: Now you can use the assets from your asset pack in your app. Just make sure to use the AssetPackManager API to request the asset pack and load the assets.

  • Build your app: Finally, build your app as usual. Android Studio will automatically include your asset pack in the APK.

By using Asset Pack Delivery, you can deliver large resources separately from the main APK, reducing the initial download size and enabling faster updates. This is especially useful for games and media-heavy apps that have large resources.

Leverage App Signing by Google Play

When you opt in to app signing by Google Play, Google manages your app's signing key for you and uses it to sign your APKs for distribution. This allows Google to optimize your app's binary files, which can reduce the size of your app and increase the speed of user’s devices.

Here's how you can enable App Signing by Google Play:

  • Open Google Play Console: Navigate to your app.

  • Select App Signing: In the left-hand menu, select Setup > App integrity.

  • Enroll in App Signing: Follow the instructions to enroll in App Signing by Google Play. You'll need to upload your current signing key.

When you're ready to release your app:

  • Build your app bundle: You can use the following command to build your app bundle:
# This command is used to build a release version of your app bundle
# It uses the Gradle wrapper ('./gradlew') included in your project
# 'bundleRelease' is the task that builds the release version
./gradlew bundleRelease
  • Upload your app bundle to Google Play: In the Google Play Console, navigate to Release > Production, then create a new release and upload your app bundle.

    Google Play will then use the app bundle to generate and serve optimized APKs for each user's device configuration. The APKs will be signed with the app signing key managed by Google Play.

    Please note that once you opt in to App Signing by Google Play, you can't opt out. Therefore, make sure to backup your keystore and its passwords before opting in.

Remove Unused Resources

Android Studio's Lint tool can help you find and remove unused resources.

Here's how you can use Lint to find and remove these resources:

  1. Run Lint: In Android Studio, go to Analyze > Run Inspection by Name....

  2. Choose Inspection: In the popup dialog, type Unused Resources and select it. Click OK.

  3. Choose Scope: Choose the scope for the inspection. You can choose Whole project to check the entire project. Click OK.

  4. View Results: The Inspection Results window will open with a list of unused resources. You can view the resource name, type, and location.

  5. Remove Unused Resources: Right-click on the issue and select Safe Delete to remove the resource. Android Studio will confirm if it's safe to delete the resource.

Here's a code block that shows an example of an unused resource in a strings.xml file:

<resources>
    <!-- This is the name of your application, used in the launcher and other places -->
    <string name="app_name">My Application</string>

    <!-- This is an unused string resource. If not referenced anywhere in your code, it can be safely removed -->
    <string name="unused_string">This string is not used</string>
</resources>

In this example, unused_string is an unused resource and can be safely removed.

Optimize Images

You can use tools like WebP to optimize your images without losing quality.

Here's how you can use WebP in Android Studio to optimize your images without losing quality:

  1. Open Android Studio: Start by opening your project in Android Studio.

  2. Navigate to the Image: In the Project window, navigate to the image you want to convert to WebP. The image should be in the res/drawable directory.

  3. Convert to WebP: Right-click on the image and select Convert to WebP.... A dialog box will appear.

  4. Choose the Conversion Settings: In the dialog box, you can choose the encoding quality. A lower value will result in a smaller file size, but the image quality may decrease. If you want to keep the image quality, set the quality to 100%.

  5. Preview and Convert: Click on Preview to see the new file size and any changes in quality. If you're satisfied with the preview, click on OK to convert the image.

Here's a code block that shows how you can use a WebP image in your layout:

<ImageView
    <!-- Specifies the width of the ImageView. "wrap_content" means the ImageView will be just big enough to enclose its content -->
    android:layout_width="wrap_content"

    <!-- Specifies the height of the ImageView. "wrap_content" means the ImageView will be just big enough to enclose its content -->
    android:layout_height="wrap_content"

    <!-- Specifies the drawable resource to be used as the content of the ImageView. "@drawable/my_image" refers to a drawable resource named "my_image" -->
    android:src="@drawable/my_image" /

In this example, my_image is the name of your WebP image. Android Studio will automatically recognize the WebP format.

Please note that while WebP provides excellent compression, it's not supported on all Android versions. As of now, lossless and transparent WebP images are only supported on Android 4.2.1 (API level 17) and higher. Lossy, non-transparent WebP images are supported on Android 4.0 (API level 14) and higher.

Use Vector Graphics Where Possible

Vector graphics are usually smaller than raster graphics and they scale without losing quality. Android supports Vector Drawable for this purpose.

Here's how you can convert SVG graphics to Vector Drawable using Android Studio:

  1. Open Android Studio: Start by opening your project in Android Studio.

  2. Navigate to the Drawable Folder: In the Project window, navigate to the res/drawable directory.

  3. Import SVG as Vector Drawable: Right-click on the drawable folder and select New > Vector Asset.

  4. Choose Local File: In the Vector Asset Studio dialog that appears, select Local file (SVG, PSD) under Asset Type.

  5. Select SVG File: Click on the Path field and select your SVG file from your local file system.

  6. Import and Convert: Click Next and then Finish. Android Studio will automatically convert the SVG to a Vector Drawable and add it to your drawable directory.

Here's a code block that shows how you can use a Vector Drawable in your layout:

<ImageView
    <!-- Specifies the width of the ImageView. "wrap_content" means the ImageView will be just big enough to enclose its content -->
    android:layout_width="wrap_content"

    <!-- Specifies the height of the ImageView. "wrap_content" means the ImageView will be just big enough to enclose its content -->
    android:layout_height="wrap_content"

    <!-- Specifies the drawable resource to be used as the content of the ImageView. "@drawable/my_vector_image" refers to a vector drawable resource named "my_vector_image" -->
    android:src="@drawable/my_vector_image" />

In this example, my_vector_image is the name of your Vector Drawable. Android Studio will automatically recognize the Vector Drawable format.

Please note that Vector Drawables are supported on Android 5.0 (API level 21) and higher. For lower API levels, Android Studio generates PNG files at build time for the required screen densities.

Split APKs by ABI

You can create separate APKs for different device configurations, such as different CPU architectures (ABIs). This can reduce the size of your APK because each APK contains only the code and resources needed for a specific configuration.

Here is how you can enable it in your app level build.gradle file:

android {
    ...
    splits {
        // This block is used to split the APK per ABI (Application Binary Interface)
        abi {
            // Enables ABI split
            enable true

            // Clears any previously included ABIs
            reset()

            // Specifies the ABIs that should be included in the split
            // 'x86' and 'x86_64' are for devices using Intel chips
            // 'armeabi-v7a' and 'arm64-v8a' are for devices using ARM chips
            include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'

            // Disables the creation of a universal APK that includes all ABIs
            universalApk false
        }
    }
}

Use the Android Size Analyzer Plugin

This Gradle plugin provides actionable insights to reduce the size of your APK. You can install the plugin by modifying your project-level build.gradle file:

buildscript {
    ...
    dependencies {
        ...
        // Specifies the version of the Android Gradle plugin to use
        // This plugin provides the DSL used to configure Android-specific options in your build.gradle files
        classpath 'com.android.tools.build:gradle:3.6.0'

        // Specifies the version of the Google Play Services OSS Licenses plugin to use
        // This plugin is used to display the open source licenses of the libraries used in your app
        classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
    }
}

Once the plugin is installed, you can run it by using the analyzeApk task. For example, if your APK file is named app-debug.apk, you would run:

# This command is used to analyze an APK using the Gradle wrapper ('./gradlew') included in your project
# 'analyzeApk' is the task that performs the analysis
# '--apk app-debug.apk' specifies the APK file to analyze
./gradlew analyzeApk --apk app-debug.apk

This will provide a detailed breakdown of what's taking up space in your APK.

As always, thoroughly test your app after making these changes to ensure that everything still works as expected.

Conclusion

Optimizing APK builds is an essential step in Android development. By implementing these basic and advanced techniques, you can significantly reduce the size of your APK, leading to improved user experience and potentially higher download rates. Remember, every byte counts when it comes to mobile apps!

Read more on optimizing app performances in our previous articles!

Part One →

Part Two →

Gradle Optimizations →

Did you find this article valuable?

Support Dashwave for Mobile Devs by becoming a sponsor. Any amount is appreciated!