Understanding Dynamic Feature Modules & AAB

Shashank kumar
6 min readJun 29, 2024

--

Play Feature Delivery uses advanced capabilities of app bundles, allowing certain features of your app to be delivered conditionally or downloaded on demand. First, you need to separate these features from your base app into feature modules.

Google Play’s app serving model uses Android App Bundles to generate and serve optimized APKs for each user’s device configuration, so users download only the code and resources they need to run your app.

Analyzing the Android App Bundle

“An Android App Bundle is a publishing format that includes all your app’s compiled code and resources, and defers APK generation and signing to Google Play.”

An app bundle is different from an APK in that you can’t deploy one to a device. Rather, it’s a publishing format that includes all your app’s compiled code and resources in a single build artifact. So, after you upload your signed app bundle, Google Play has everything it needs to build and sign your app’s APKs and serve them to users.

Android App Bundles

Let’s analyze an Android App Bundle with multiple modules, namely dynamicFeature, native, java, kotlin , assets— these are the dynamic feature modules I created for this project.

The above indicates the contents of an app bundle which can be from Android Studio’s Analyze App Bundle feature, In general, a module in an app bundle consists of

  1. Dex Files -The primary purpose of DEX files is to package the compiled code into a format that the Dalvik Virtual Machine (DVM) or the Android Runtime (ART) can execute.
  2. Manifest — The AndroidManifest.xml file is a required XML file for every Android application that describes the essential information about the app. Each dynamic feature module has its own AndroidManifest.xml file. It typically includes the components specific to that module and can specify its permissions and features.
  3. Resources — crucial for organizing and managing the various resources the app uses. These resources include layouts, images, strings, animations, styles, and more.
  4. root — This directory stores files that are later relocated to the root of any APK that includes the module that this directory is located in. For example, the base/root/ directory of an app bundle may include Java-based resources that your app loads using Class.getResource().

As we can see there is a different module created for the main module called the base module — The base module in an Android App Bundle contains the core functionality of the app.

AAB Lifecycle

When the Android App Bundle is processed by Google Play, it is split into multiple APKs tailored to specific device configurations. The base split APK is generated from the base module and contains the core functionality and resources required for the app to start. This APK is mandatory and is installed on all devices, ensuring that the app can run on any supported device.

In addition to the base split APK, the Android App Bundle generates several other APKs called configuration APKs. These are optimized for different device configurations and may include:

  • Density APKs: For different screen densities (e.g., mdpi, hdpi, xhdpi).
  • Language APKs: For different languages and locales.
  • ABI APKs: For different CPU architectures (e.g., armeabi-v7a, arm64-v8a).
  • Dynamic Feature APKs: For optional features that can be downloaded on-demand.

For each of the Dynamic Feature Modules in the App Bundle, there will be one Split APK created. Each of these split APKs is downloaded and installed when the user requests it or it’s required by the device.

Apart from these some other components of App Bundle are -

  1. BUNDLE-METADATA: This directory includes metadata files that contain information useful for tools or app stores. Such metadata files may include ProGuard mappings and the complete list of your app’s DEX files. Files in this directory are not packaged into your app’s APKs.
  2. Module Protocol Buffer (*.pb) files: These files provide metadata that helps describe the contents of each app module to app stores, such as Google Play.

How App Bundles are generated

Let’s dive deeper into AGP Code to understand how an Android App Bundle is generated.

PackageBundleTask.kt is a Gradle task for building Android App Bundles (.aab files). This task packages the various modules and components of the application into the final .aab file.

The task gathers all the necessary inputs, such as feature module zips — Dynamic Feature Modules are treated as separate modules encapsulated in zipped archives, asset pack zips, main dex list, obfuscation mapping file, integrity config file, and native debug metadata files. These inputs are specified using properties and annotations to indicate how they should be treated by Gradle (e.g. - whether they are optional, sensitive to path changes, etc).

The task configures bundle options, such as ABI splits, density splits, language splits, texture splits, and device tier splits. It uses BundleOptions to encapsulate these configurations and applies them to the BundleTool command.

Configuration options (such as splitting by ABI, density, and language) also apply to Dynamic Feature Modules, allowing for tailored APK generation based on device capabilities and user requirements.

The task sets up metadata files and optimizations. This includes compression settings, integrity configurations, native library compression, and other bundle-specific settings.

The BundleToolWorkAction class constructs and executes the BuildBundleCommand using the BundleTool, which is a tool provided by Google to build AAB files. The command is configured with all the gathered inputs, configurations, and metadata, and then it is executed to produce the final .aab file.

How Split APKs are created

bundletool can convert an app bundle into the various APKs that are deployed to devices.

The BuildApksManager class is part of the Android Bundle Tool and manages the process of generating APKs from an Android App Bundle (AAB).

The execute() method in BuildApksManager begins by determining which types of APKs to generate based on the command options and the bundle configuration. For split APKs specifically, it identifies which modules need to be split and their configurations (e.g., dimensions).

The mergeNonRemovableInstallTimeModules method from the BundleModuleMerger class identifies which modules to merge into the base module based on specific conditions, such as not merging modules marked as removable (<dist:removable dist:value=”true” />). It merges configurations like assets (Assets), native libraries (NativeLibraries), and resource tables (ResourceTable) from the identified modules into the base module and it constructs the updated AppBundle with the merged modules, excluding those that were merged into the base.

This merged app bundle is then used to generate Split APKs using the generateSplits Method of SplitApkGenerator Class, The SplitApksGenerator class is responsible for generating split APKs from an Android App Bundle (AAB).

generateSplits generates APK splits based on the provided modules and configuration. It delegates to variantTargetingGenerator to produce variant targetings and then generates splits for each variant using generateSplitApks which generates split APKs for a specific variant targeting by iterating through modules (modulesForVariant). It uses ModuleSplitter to split each module into APKs based on the given configuration and variant targeting.
One key thing to notice in SplitApkGenerator is that this class allows for specific configurations (featureModulesCustomConfig) to manage Dynamic Feature Modules, enabling control over their optimization and inclusion in split APKs.

featureModulesCustomConfig is an optional configuration parameter injected into SplitApksGenerator. It allows developers to specify custom rules and configurations specifically tailored for Dynamic Feature Modules. For example, it can specify whether certain modules should be compressed differently or excluded from specific optimizations based on their nature or usage scenarios. Dynamic Feature Modules can be selectively included or excluded from split APKs based on their configuration in featureModulesCustomConfig.

Then the GeneratedApks.fromModuleSplits()method processes each variant separately to inject necessary configurations like split.xml and locale configurations (LocaleConfigXmlInjector) into the ModuleSplits.

A variant is a set of APKs. One device is guaranteed to receive only APKs from the same.

Once all APKs are generated, it uses ApkSerializerManager to serialize the APK set to the specified output format, the apkSerializerManager.serializeApkSet() serializes APKs into a final APK set using an ApkSetWriter created from tempDir.getPath(). The method efficiently packages APKs for distribution, handling both dynamic features and device-specific configurations, while allowing customization based on testing and fused module needs.

--

--