Savitar: Incremental Compilation Acceleration for Android Development
Savitar, a custom Android Studio plugin suite, accelerates incremental compilation by detecting changed files with Watchman, gathering project metadata, invoking javac/kotlinc and AAPT2 for on‑demand builds, and hot‑loading Dex and resources at runtime, cutting average incremental build time from 110 seconds to 15 seconds and saving thousands of developer hours.
In a recent Youzan Mobile Salon, the Android team shared their experience with compilation speed improvements. The first part covered full‑project compilation acceleration, and this article focuses on the second part – the incremental compilation acceleration solution called Savitar .
Background : The Android project consists of 25 modules, over 450,000 lines of Java/Kotlin code, and multiple build flavors. Typical incremental builds take about two minutes, and the full edit‑compile‑install cycle can reach three minutes on a MacBook Pro 2016, severely affecting developer productivity.
Solution Exploration : The team evaluated existing frameworks such as BUCK, Freeline, and InstantRun. BUCK offered strong incremental caching but required complex configuration and lacked full Kotlin support. Freeline was fast but did not support Kotlin. InstantRun supported many features but added overhead and did not provide noticeable speed gains for a multi‑process project. Consequently, the team decided to design a custom solution based on the principle of "compile‑on‑demand, dynamic loading".
Architecture : Savitar is divided into four parts (see the diagram in the original article): GUI plugin – the user‑facing interface that updates the Runner JAR, performs checks, and invokes compilation scripts. Runner – a JAR containing the core logic for change detection, script generation, and compilation execution. Gradle plugin – extracts project information and injects code for loading compiled artifacts. External dependencies – auxiliary programs required by the whole workflow.
The overall workflow (illustrated in the article) proceeds as follows: Detect changed files. Gather project metadata (dependencies, module paths, Git information). Compile source code and resources to produce Dex and APK artifacts. Reload the modified artifacts into the running app.
4.2 Change Detection
The team first tried using $ git diff --name-only ${上次成功构建的commitId} HEAD to list modified files, but Git could not capture newly added files and sometimes reported unchanged files. They switched to Watchman , a file‑watching service, with commands such as:
$ watchman watch ${文件夹}
$ watchman -j > ${diff信息保存文件} <<-EOT
["query", "${文件夹}", {"since": "${上次修改时间}", "expression": ["exists"], "fields": ["name"]}]
EOTWatchman stores the diff in JSON. To handle branch switches, they combined Git (for reliable baseline comparison) with Watchman, ensuring accurate change sets.
Flavor filtering is applied after change detection to ignore modifications belonging to inactive product flavors (e.g., Pad vs. Phone).
4.3 Project Information Gathering
Compilation requires two kinds of information:
Build dependencies – paths to compiled class directories, third‑party JAR/AAR files (extracted from .idea/libraries XML entries).
Project metadata – module package names, active flavor, source sets, SDK version, etc.
An example of a library entry extracted from the XML configuration:
<component name="libraryTable">
<library name="Gradle: android.arch.core:common:1.1.0@jar">
<CLASSES>
<root url="jar://${USER_HOME}/.gradle/caches/modules-2/files-2.1/android.arch.core/common/1.1.0/8007981f7d7540d89cd18471b8e5dcd2b4f99167/common-1.1.0.jar!/"/>
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://${USER_HOME}/.gradle/caches/modules-2/files-2.1/android.arch.core/common/1.1.0/f211e8f994b67f7ae2a1bc06e4f7b974ec72ee50/common-1.1.0-sources.jar!/"/>
</SOURCES>
</library>
</component>The gathered data is stored in classes such as MakeParam , ProjectParam , ModuleParam , and SourceSetsParam .
4.4 Compilation Implementation
Source code compilation uses javac for Java and kotlinc for Kotlin. The command template is:
# Execute kotlinc/javac
sh kotlinc{or javac} \
-classpath \
${projectPath}/build/intermediates/classes/${Flavor}/debug: # build output
${android_home}/platforms/android-${version}/android.jar: # Android SDK & third‑party JARs
-d ${产物输出目录} \
@$${kotlin修改文件集合.ch} \
@$${java修改文件集合.ch}Compilation order matters: when a Kotlin file depends on a Java file, Kotlin must be compiled first because Java cannot see Kotlin sources, only compiled .class files.
Resource compilation relies on AAPT2. The resource‑ID stability is ensured by adding the --emit-ids flag:
processResourcesTask.aaptOptions.additionalParameters("--emit-ids", idRecordPath)Resource compilation commands:
// Resource compilation
aapt2 compile ${资源文件全路径} -o ${资源文件编译产物输出目录}
// APK generation
aapt2 link ${.flat资源文件路径} -o ${目标apk路径} --manifest AndroidManifest.xmlValues resources that have been merged in a full build are re‑processed in an offline Gradle task to keep IDs consistent.
4.5 Artifact Loading
The loading step uses hot‑fix principles (similar to Tinker) but simplified: the generated Dex and resource bundles are loaded at runtime without complex diffing. After loading, the temporary artifacts are deleted to allow a clean state for the next iteration.
5 User Experience
An IDE plugin (distributed as a local Savitar.jar ) adds a toolbar button to Android Studio. Clicking the button opens a window that shows the compilation, packaging, and deployment process, including any error messages.
6 Q&A Highlights
Kotlinx synthetic imports : The plugin must pass the Android extensions compiler plugin to kotlinc via -Xplugin=lib/android-extensions-compiler.jar and appropriate -P parameters.
Why Shell scripts? Shell scripts run directly on macOS, making it easy to invoke the required tools and debug the workflow.
Kotlinc environment : If required Kotlin compiler dependencies are missing, Savitar automatically downloads them from an internal server.
7 Results and Outlook
After adopting Savitar, incremental build time dropped from an average of 110 seconds to 15 seconds – an 8× speedup. Over 10,000 uses within the company saved roughly 260 hours of compilation time. Future plans include support for dynamic code generation, native libraries, and a generalized architecture that can be open‑sourced for the broader Android community.
For further reading, the article lists several related posts on Android Dex files, Weex framework, componentization, and high‑availability server‑side rendering.
Youzan Coder
Official Youzan tech channel, delivering technical insights and occasional daily updates from the Youzan tech team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.