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 clickFinish
.
```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 theCreate New Module
window that appears, selectDynamic Feature Module
and clickNext
.Configure Module: In the
Configure your new module
screen, enter the module name, package name, and minimum SDK. You can also check theInclude module in base module
checkbox to automatically include the dynamic feature module in your base module'sbuild.gradle
file. ClickNext
.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
). ClickFinish
.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 thecom.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 clickFinish
.
// 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 beinstall-time
,fast-follow
, oron-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:
Run Lint: In Android Studio, go to
Analyze > Run Inspection by Name...
.Choose Inspection: In the popup dialog, type
Unused Resources
and select it. ClickOK
.Choose Scope: Choose the scope for the inspection. You can choose
Whole project
to check the entire project. ClickOK
.View Results: The
Inspection Results
window will open with a list of unused resources. You can view the resource name, type, and location.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:
Open Android Studio: Start by opening your project in Android Studio.
Navigate to the Image: In the
Project
window, navigate to the image you want to convert to WebP. The image should be in theres/drawable
directory.Convert to WebP: Right-click on the image and select
Convert to WebP...
. A dialog box will appear.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%.
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 onOK
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:
Open Android Studio: Start by opening your project in Android Studio.
Navigate to the Drawable Folder: In the
Project
window, navigate to theres/drawable
directory.Import SVG as Vector Drawable: Right-click on the
drawable
folder and selectNew > Vector Asset
.Choose Local File: In the
Vector Asset Studio
dialog that appears, selectLocal file (SVG, PSD)
underAsset Type
.Select SVG File: Click on the
Path
field and select your SVG file from your local file system.Import and Convert: Click
Next
and thenFinish
. Android Studio will automatically convert the SVG to a Vector Drawable and add it to yourdrawable
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!