Mobile Development 11 min read

Accelerating Full Android Builds by Pre‑compiling Library Modules into AARs

To cut full‑build times in large Android projects, the team pre‑compiles each library module into an AAR, publishes it to a Maven repository, computes a content‑plus‑dependency‑graph hash to detect existing AARs, and automatically swaps project dependencies for matching AARs while addressing duplicate‑class and version conflicts.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Accelerating Full Android Builds by Pre‑compiling Library Modules into AARs

1.1. Background

In everyday development, building the project is a fundamental step that determines development efficiency. As business code accumulates, compilation time grows. Although many incremental compilation acceleration methods exist, many scenarios still require full builds, and we face several difficulties:

In the K‑song project, the total codebase is 1.6 million lines, with Kotlin accounting for about 43% of the code but 70% of the compilation time, so this proportion must be reduced.

We need a method that retains Kotlin’s development convenience while alleviating the rapid increase of full‑build time.

1.2. Solution

If we can reduce the amount of Kotlin that needs to be compiled, the build time will drop – either by reducing code or by pre‑compiling code. The latter is more feasible.

Android supports two binary archive formats: JAR and AAR.

Both formats store compiled classes, but JAR does not contain resources, which is unfriendly for componentized projects. Library modules compile directly into AARs.

Therefore, if we continuously generate AARs for all library modules via a CI/CD pipeline and publish them to a common Maven repository, we can use those AARs during compilation.

1.3. Preliminary Presentation of the Solution

Library modules are pre‑compiled into AARs; we need to change the dependency type from implementation project to implementation aar .

If the library module code changes, we must re‑generate the AAR and update the dependency version, which is time‑consuming. Can we automate this repetitive step?

A better approach: at compile time, check whether an AAR matching the current library module version exists. If it does, use the AAR for compilation. The workflow is: Compute a hash of all files in the current code version, including: JavaSource JavaResource Assets Resources Aidl JniLibs AndroidManifest.xml Proguard Lint Check whether the Maven repository contains an AAR with the corresponding hash (path = repository/libraryName/version‑md5). Modify the library module’s dependency type to AAR.

1.4. Problems Encountered

1. JAR duplicate class conflict

We can see that B directly depends on C. This relationship is declared in B’s .pom metadata. When C’s code changes, the remote AAR no longer matches, so C participates in compilation both as an AAR and as a project. If C contains a JAR, a conflict occurs.

2. Project duplicate class conflict

share_m and share are from the same code repository; developers changed the name for verification, resulting in different routes but identical code. Gradle treats them as two AARs and reports duplicate classes.

3. Third‑party library version conflict

After compilation, the share code version remains 1.2.0 because B.aar depends on share:1.2.0. Gradle considers all requested versions in the dependency graph and selects the highest one.

The first issue can be solved by removing the B‑>C AAR dependency, either by deleting the entry from the POM (K‑song’s approach) or by changing B’s dependency on C to compileOnly (though this may cause resource‑packing errors).

The second issue is addressed by keeping the module name consistent and only changing the version when publishing.

For the third issue, common remedies include:

Use a higher version to override all participating versions.

Change B‑>share:1.2.0 to compileOnly to break transitive dependency.

If a dynamic lower version is required, extract a whitelist, delete the dependency from the POM, and let the app’s main module depend on it (K‑song’s approach).

When publishing B as an AAR, omit any third‑party dependency metadata in the POM and let the app handle those dependencies.

To resolve transitive dependencies, features like transitive , force , and strict dependencies exist, but K‑song rarely uses them to keep development transparent and preserve original code; they mainly edit the POM directly.

From the above problems, it is clear that the unique identifier = its own content + dependency graph, so the dependency graph must be included when calculating the MD5. When can we obtain the dependency graph?

Gradle’s build lifecycle consists of three steps:

1、
Initialization
Gradle supports single‑project and multi‑project builds. In this phase, Gradle determines which projects will participate and creates a Project instance for each.
2、
Configuration
During this phase, project objects are configured. All project build scripts are executed as part of the build.
3、
Execution
Gradle determines the subset of tasks to create and configure based on the task names passed to the gradle command and the current directory. Then Gradle executes each selected task.

Since the dependency graph is generated in the configuration phase, we can parse it in the project evaluation callback ( afterEvaluate ).

K‑song’s app module depends on all library modules globally. When the app receives the evaluation callback first, we can modify the app’s dependency graph at that moment to block subsequent configuration of other library modules. At that point, library modules have not yet been evaluated, so their dependency graphs are unavailable for MD5 calculation; we would have to manually parse the build.gradle of each library module. Some configurations are declared directly in dependencies , while others are extracted into a shared versionConfigs.gradle .

Note that modifying the dependency graph after every project has finished evaluation is unsafe; Gradle will prevent it.

1.5. Final Workflow

During the configuration phase, each project’s build.gradle runs, determining dependencies. After project evaluation ( afterEvaluate ), a notification is received.

Parse the configuration for local project dependencies ( DefaultProjectDependency ), compute the MD5 of the project, including the AAR contents and the dependency graph.

After obtaining the MD5, construct the Maven repository path according to the addressing rule and check whether the corresponding AAR exists remotely.

If the AAR exists, replace the local project dependency with a remote AAR dependency ( DefaultExternalModuleDependency ).

QQ Music is hiring Android/iOS client developers. Click “View Original” at the bottom left to submit your resume.

You can also send your resume to: [email protected]

ci/cdAndroidbuild optimizationGradleKotlinAAR
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login 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.