Mobile Development 32 min read

Boosting Android Build Speed: Enterprise WeChat’s Low‑Impact App Bundle Modularization

This article explains how Enterprise WeChat adopted Android App Bundle and low‑intrusion dynamic feature modules to restructure a massive Android codebase, achieving faster engineering cycles, parallel and incremental compilation, reduced package size, and seamless migration with minimal code changes.

WeChat Client Technology Team
WeChat Client Technology Team
WeChat Client Technology Team
Boosting Android Build Speed: Enterprise WeChat’s Low‑Impact App Bundle Modularization
Introduction Android App Bundle is a new official publishing format that enables more efficient development and release of Android apps. Enterprise WeChat leveraged App Bundle with a low‑intrusion, near‑zero‑refactor solution, using dynamic feature modules for all business modules and presenting a parallel compilation scheme to accelerate continuous integration.

Project Background

Android App Bundle

It is the official Android publishing format that, together with Kotlin, Android Studio, and JetPack APIs, forms the Modern Android Development stack worth studying and applying in projects.

The core consists of Google Play distribution and the Android Split APK runtime loading mechanism, delivering smaller apps for better user experience, higher install success rates, and lower uninstall rates. Converting .apk to .aab is straightforward and requires no code refactoring.

Since the second half of 2021, Google requires new apps to be published as App Bundles on Google Play. Apps larger than 150 MB must use Play Feature Delivery or Play Asset Delivery. See the official documentation for details.

Enterprise WeChat

After five years of rapid growth, Enterprise WeChat serves over 5.5 million enterprise customers and connects with more than 400 million WeChat users. The product spans many industries, roles, and identities, forming a highly complex large‑scale software system. The Android client has grown into a massive app, exposing typical large‑app problems:

Many business modules, low code/resource isolation, complex dependencies

Low compilation efficiency

Large package size and divergent channel builds

Huge legacy codebase, difficult to refactor

Project structure not aligned with organizational growth

Enterprise WeChat adopted a modular development approach to gradually solve these issues. After using App Bundle in 2019 to address the 64‑bit Play Store requirement, the modular workflow was further refined.

Evolution of Enterprise WeChat’s Modular Development

The evolution can be divided into three stages:

Stage 1: Library Module Reuse Gradle introduced multi‑project builds, shifting from package‑level to module‑level dependency management and achieving library module boundary isolation.

Stage 2: Layered Refactoring Defined app/module/api/library layers, used API communication and inversion of control, split the monolithic app into a thin shell (app) and business modules (module). Refer to the “WeChat Android Modular Architecture Refactoring Practice” for details.

Stage 3: Grouped Refactoring Introduced Android App Bundle and dynamic feature modules, changing the delivery from a single app.apk to base.apk + split.apk. This yields smaller initial install packages, stricter dependency, code, and resource boundaries, and more flexible deployment.

What Does Modular Development Solve?

Improved Engineering Speed Each feature is designed, built, debugged, and tested independently, reducing merge conflicts and interruptions for large monolithic projects. Shortened Compilation Time Gradle’s Android Studio compilation system is optimized for modular apps, making builds much faster than for large monoliths.

Improving Engineering Speed

In the new modular approach, a third type – dynamic feature – is added. Dynamic modules depend on the base module and can be developed and tested independently.

Dynamic modules present two balancing challenges:

If the base project is large, its build time becomes a bottleneck.

Splitting most code into dynamic modules makes the base tiny, but then every feature needs mock test code for integration, lacking an independent integration test environment.

To fully exploit dynamic modules, Enterprise WeChat implements:

Plugin‑based compilation – split the base into the smallest possible project and pre‑allocate module names and resource partition IDs.

Integration test environment – provide .apk or .aab caches for other business builds.

Shortening Compilation Time

Gradle improves compilation efficiency in three ways:

Parallel compilation – construct parallel relationships among similar projects in the task dependency graph, fully utilizing multi‑threaded builds.

Incremental compilation – each task tracks its inputs and outputs to enable incremental builds.

Compilation cache – use build‑cache, .aar, or plugin‑generated .apk artifacts to skip already‑built tasks.

In practice, two main ideas are applied:

Incremental compilation improvements – reduce changed code/resources, increase local cache hit rate (e.g., Google Instant Run, Alibaba Freeline, Tencent Lightning).

Project structure improvements – reduce the amount of code/resources participating in a build, increase server‑side cache hit rate (e.g., WeChat modularization, Taobao pluginization).

Parallel builds can introduce scheduling overhead, deadlocks, and cache miss penalties; excessive parallelism may degrade performance.

Why Refactor in Stage 3?

Different development models affect modular benefits. Comparing trunk‑based development and Git‑Flow:

Trunk‑based development – all developers work on the main branch, avoiding long‑lived feature branches and enabling rapid integration verification.

Git‑Flow – isolates feature development in branches, reducing merge conflicts but extending branch lifetimes.

Enterprise WeChat uses trunk‑based development, which can cause:

Single compile errors may break integration for the whole team.

Concurrent module modifications and cache invalidation can sharply drop efficiency.

Stage 3 refactoring aims to enhance module isolation, adopt split‑APK loading, and improve parallel and cache efficiency while reducing dependency count and concurrent modification impact.

Approach and Challenges

Transform all business modules into dynamic modules, split the base into the smallest possible project, and pre‑allocate module names and resource partition IDs.

Reduce dependency count to improve cache hit rate; provide build caches for other business modules.

Maintain low‑intrusion, near‑zero‑refactor migration: no changes to inter‑module compile dependencies, only minimal Gradle and AndroidManifest adjustments.

Support both .aab and .apk release modes; the switch is active only during development and does not affect production.

Low‑Intrusion, Near‑Zero Refactor

Compilation Critical Tasks

Key differences between App Bundle and APK compilation:

Dependency handling

1. Count base dependencies – all base dependencies are packaged into base.apk; duplicate feature dependencies are ignored.

2. Count feature dependencies – each feature’s dependencies are packaged into its feature.apk.

3. Validate dependency uniqueness – a library used by multiple features causes a conflict.

Resource compilation

4. Collect feature dependency resources (including AndroidManifest entries).

5. Collect base dependency resources.

6. Merge AndroidManifest from base and all features.

7. Compile base resources into a .ap_ package for features to reference.

Code compilation

8. Compile base code into a .jar providing R references.

9. Compile feature code; package name changes can break R references.

Module Dependency Conflicts

App Bundle checks for duplicate dependencies in the base stage, preventing runtime duplicate .dex files.

Gradle manages dependencies via configurations (implementation, compileOnly, etc.). Example configuration:

dependencies {
    implementation(Deps.Lib.kotlin_stdlib_jdk8)
    compileOnly(Deps.Mockito.wechat_media_codec)
}

Custom configuration for modular implementation:

configurations {
    create("modularImplementation").apply {
        getByName("implementation")?.extendsFrom(this)
    }
}

fun DependencyHandler.modularImplementation(dependencyNotation: Any): Dependency? = add("modularImplementation", dependencyNotation)

Runtime classpath (runtimeClasspath) determines packaging; compileClasspath determines compilation success.

AndroidManifest Merge Errors

During bundling, feature AndroidManifest files are merged into the base, but their referenced resources are not, leading to missing‑resource errors in the base manifest.

Parsing code (android.content.pm.PackageParser) shows that feature component configurations override base components, while application attributes are taken from the base.

Solution: copy application and uses‑permission entries from features into the base manifest and clear featureManifests during the processDebugManifest task:

task.featureManifests = variantScope.getArtifactCollection(
        METADATA_VALUES, MODULE, METADATA_FEATURE_MANIFEST);
if (featureManifests != null) {
    providers.addAll(computeProviders(
            featureManifests.getArtifacts(),
            InternalArtifactType.METADATA_FEATURE_MANIFEST));
}

// Clear merged feature manifests
featureManifests.getArtifacts().clear()

Resource Compilation Errors

Resource compilation involves collection, ID allocation, and linking. Errors mainly occur in the linking stage when resources are not properly isolated.

Missing isolation leads to cross‑module resource references that may not exist.

Dynamic module partitioning isolates resources, but referencing another feature’s resources still causes errors.

Mitigation steps:

Mock various resource types and quickly refactor to new modules, counting reference scopes.

Sink high‑frequency public resources into the base module.

Refactor low‑frequency business resources according to ownership.

R.java Compilation Errors

R.java is heavily referenced across modules. After splitting modules, package name changes cause massive R reference errors.

Historical evolution:

ADT + Eclipse – full package names required.

Gradle + Android Studio – generated non‑namespaced R classes simplify usage.

Two optimization directions for R.java:

Break recursion, generate smaller R files.

Generate bytecode directly (R.jar) for faster builds.

Our approach combines both: make feature R classes inherit from base R, allowing direct access without full package names.

// After processing, feature R extends base R
class R extends BaseR { /* ... */ }

This yields symmetric resource and code overriding: feature resources have higher priority, and code can access base resources transparently.

Runtime Consistency of R.id

Two runtime issues arise:

Resource ID chaos – random allocation can cause mismatches when using .apk caches.

Cross‑module NPE – findViewById may return null when IDs collide across modules.

Solution: use AAPT2’s --stable-ids to fix base resource IDs and modify AAPT2 to generate stable IDs for id/attr types.

AAPT2 is obtained from the Google Maven repository ( com.android.tools.build:aapt2). We replace it with a custom build that adds the required stability features:

subprojects {
    configurations.all {
        resolutionStrategy.eachDependency {
            if (Env.isBuildBundle && requested.name == "aapt2") {
                useTarget("com.tencent.wework.tools.build:aapt2:2.3.3.1-wecomponent-6051327")
            }
        }
    }
}

Low‑Cost Switching

Dynamic modules add a dist:module entry in AndroidManifest, which does not affect normal APK builds. By toggling the dynamicModules flag in Gradle, developers can switch between .aab and .apk without code changes.

Research Modes and Process

Four Development Modes

Domestic App Bundle ecosystem is immature; use plugin frameworks to supplement.

Enterprise WeChat fully adopts App Bundle, achieving up to 300 % faster compilation, with incremental builds typically under 30 seconds.

International Google Play market – dynamic handling for large SDKs, with upcoming channel releases.

Partner teams focusing on engine or SDK development benefit from pre‑allocated feature builds and real‑user runtime environments.

Research Process

Cache management via Maven for various artifact types (.aar, .apk, .zip, .aab, .txt).

Metadata management with separate repositories to map commits and version data.

Parallel compilation across local and remote build machines, leveraging distributed resources.

Domestic Ecosystem Comparison

In China, App Bundle support is limited (custom ROMs, alternative app stores). Solutions include self‑built CDN distribution, plugin frameworks (e.g., Qigsaw), resource guard tools, and custom parallel compilation pipelines.

Conclusion

Enterprise WeChat tackled typical large‑app challenges—low module isolation, slow compilation, large package size, massive legacy code, and mismatched project structure—by adopting low‑intrusion, near‑zero‑refactor modularization based on Android App Bundle, parallel and incremental compilation, and custom AAPT2 enhancements.

Gradlemodular architectureAndroid App BundleDynamic Feature Modulesparallel compilation
WeChat Client Technology Team
Written by

WeChat Client Technology Team

Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.