Skip to content

[Uber] Add full-stack Android App Bundle support for dynamic feature modules#520

Open
oliviernotteghem wants to merge 1 commit into
bazelbuild:mainfrom
oliviernotteghem:origin_dynamic_features
Open

[Uber] Add full-stack Android App Bundle support for dynamic feature modules#520
oliviernotteghem wants to merge 1 commit into
bazelbuild:mainfrom
oliviernotteghem:origin_dynamic_features

Conversation

@oliviernotteghem
Copy link
Copy Markdown
Contributor

@oliviernotteghem oliviernotteghem commented Jun 5, 2026

Summary

Implements end-to-end support for Android App Bundles (AAB) with dynamic
feature modules in rules_android, covering all four artifact types:
resources, bytecode/dex, native libraries, and assets.

  • Resources
    Resource tables from all dynamic features are aggregated into the base
    app's AAPT2 resource compilation step so that the final bundle contains
    a single, unified R.txt. Each feature's proto-apk_ is built separately
    (via a dedicated android_feature_module_rule target) and merged into the
    base bundle at packaging time. Manifest entries from feature modules are
    filtered to prevent leaking into the base manifest, and manifest merging
    order is corrected to match Play's expectations.

  • Bytecode / DEX
    Each feature module's deploy jar is passed through R8 alongside the base
    deploy jar. Before invocation, a filtering step (filter_feature_classes.sh)
    identifies all classes already present in the base dex output and strips
    them from each feature jar — preventing duplicate class definitions and
    keeping per-feature dex splits as small as possible. Proguard specs are
    collected transitively across all feature dependencies and supplied to R8
    via --pg-conf, with --pg-compat enabled for compatibility. The resulting
    per-feature dex directories are zipped and embedded in the final AAB.

  • Native libraries
    Native .so files are extracted from each feature's unsigned APK (lib/*),
    filtered to remove non-library entries, and merged into the feature's
    bundle zip alongside its resources and dex. Only the ABI slice matching
    the target device is delivered by Play at install time.

  • Asset packs / is_asset_pack
    Feature modules declared as asset packs (is_asset_pack = True) are handled
    as a separate code path that skips dex compilation entirely and packages
    only the asset tree and manifest.

Resource shrinking and obfuscation
Two new flags on android_application — shrink_resources and
obfuscate_resources — wire R8's resource shrinker and AAPT2's optimize
step into the pipeline. On Uber Rider this reduces binary size by ~1.5 MB
(unused resource pruning) and ~0.5 MB (path minification) respectively.

Startup profile (PGO)
An optional startup_profile argument threads a Baseline Profile through
R8's --startup-profile flag for profile-guided optimization.

This implementation has been rolled out to production across all Uber
Android apps on the Play Store. Apps like Uber Rider ship with nearly a
dozen dynamic feature modules — each carrying its own assets, resources,
bytecode, and native libraries — and are built end-to-end using this
pipeline today.

Test Plan
From examples/bundle/, run:

bazel build //features/assets:feature_module # asset-only feature, no dex
bazel build //features/resources:feature_module # resource-only feature, no dex
bazel build //features/dex:feature_module # feature with bytecode (guava dep)
bazel build //features/native:feature_module # feature with native .so (ffmpeg-kit dep)

Full android_application bundles integrating feature modules
bazel build //app:assets # base app + asset feature module
bazel build //app:native # base app + native library feature module

Each target exercises a different slice of the pipeline:

  • //features/assets — asset pack code path (is_asset_pack, no dex compilation)
  • //features/resources — resource-only feature, AAPT2 proto-apk_ generation
  • //features/dex — Java bytecode compilation + R8 filtering in feature mode
  • //features/native — native .so extraction and jni/ layout conversion in AAR
  • //app:assets — base app + feature manifest merging + bundle assembly
  • //app:native — end-to-end: base app + native libs + manifest merging + bundle

*Attributions
Developed at Uber (Olivier Notteghem @oliviernotteghem ) in
collaboration with Snap Inc (Mauricio Gonzalez - @mauriciogg ) from Snap's Android build
infrastructure team.

…modules

Implements end-to-end support for Android App Bundles (AAB) with dynamic
  feature modules in rules_android, covering all four artifact types:
  resources, bytecode/dex, native libraries, and assets.

  Resources
    Resource tables from all dynamic features are aggregated into the base
    app's AAPT2 resource compilation step so that the final bundle contains
    a single, unified R.txt. Each feature's proto-apk_ is built separately
    (via a dedicated android_feature_module_rule target) and merged into the
    base bundle at packaging time. Manifest entries from feature modules are
    filtered to prevent leaking into the base manifest, and manifest merging
    order is corrected to match Play's expectations.
  Bytecode / DEX
    Each feature module's deploy jar is passed through R8 alongside the base
    deploy jar. Before invocation, a filtering step (filter_feature_classes.sh)
    identifies all classes already present in the base dex output and strips
    them from each feature jar — preventing duplicate class definitions and
    keeping per-feature dex splits as small as possible. Proguard specs are
    collected transitively across all feature dependencies and supplied to R8
    via --pg-conf, with --pg-compat enabled for compatibility. The resulting
    per-feature dex directories are zipped and embedded in the final AAB.

  Native libraries
    Native .so files are extracted from each feature's unsigned APK (lib/*),
    filtered to remove non-library entries, and merged into the feature's
    bundle zip alongside its resources and dex. Only the ABI slice matching
    the target device is delivered by Play at install time.

  Asset packs / is_asset_pack
    Feature modules declared as asset packs (is_asset_pack = True) are handled
    as a separate code path that skips dex compilation entirely and packages
    only the asset tree and manifest.

  Resource shrinking and obfuscation
    Two new flags on android_application — shrink_resources and
    obfuscate_resources — wire R8's resource shrinker and AAPT2's optimize
    step into the pipeline. On Uber Rider this reduces binary size by ~1.5 MB
    (unused resource pruning) and ~0.5 MB (path minification) respectively.

  Startup profile (PGO)
    An optional startup_profile argument threads a Baseline Profile through
    R8's --startup-profile flag for profile-guided optimization.

  This implementation has been rolled out to production across all Uber
  Android apps on the Play Store. Apps like Uber Rider ship with nearly a
  dozen dynamic feature modules — each carrying its own assets, resources,
  bytecode, and native libraries — and are built end-to-end using this
  pipeline today.

Test Plan

  From examples/bundle/, run:

  # All four feature module types
  bazel build //features/assets:feature_module    # asset-only feature, no dex
  bazel build //features/resources:feature_module # resource-only feature, no dex
  bazel build //features/dex:feature_module       # feature with bytecode (guava dep)
  bazel build //features/native:feature_module    # feature with native .so (ffmpeg-kit dep)

  # Full android_application bundles integrating feature modules
  bazel build //app:assets   # base app + asset feature module
  bazel build //app:native   # base app + native library feature module

  Each target exercises a different slice of the pipeline:

  - //features/assets — asset pack code path (is_asset_pack, no dex compilation)
  - //features/resources — resource-only feature, AAPT2 proto-apk_ generation
  - //features/dex — Java bytecode compilation + R8 filtering in feature mode
  - //features/native — native .so extraction and jni/ layout conversion in AAR
  - //app:assets — base app + feature manifest merging + bundle assembly
  - //app:native — end-to-end: base app + native libs + manifest merging + bundle

Attributions

  Developed at Uber (Olivier Notteghem @oliviernotteghem ) in
  collaboration with Snap Inc (Mauricio Gonzalez - @mauriciogg ) from Snap's Android build
  infrastructure team.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant