Debug and optimize Android/Gradle build performance. Use when builds are slow, investigating CI/CD performance, analyzing build scans, or identifying compilation bottlenecks.
From android-dev-toolsnpx claudepluginhub adzcsx2/android-claude-skills --plugin android-dev-toolsThis skill uses the workspace's default tool permissions.
README.mdSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
中文环境要求
本技能运行在中文环境下,请遵循以下约定:
- 面向用户的回复、注释、提示信息必须使用中文
- AI 内部处理过程可以使用英文
- 所有生成的文件必须使用 UTF-8 编码
Read and analyze the following files:
gradle.properties - Gradle configurationbuild.gradle / build.gradle.kts - Root build fileapp/build.gradle / app/build.gradle.kts - App modulesettings.gradle / settings.gradle.kts - Settingsgradle/wrapper/gradle-wrapper.properties - Gradle versionCRITICAL: UTF-8 Encoding Check for gradle.properties
Before modifying gradle.properties, MUST check and ensure UTF-8 encoding:
# Method 1: Check file encoding using file command (macOS/Linux)
file -b --mime-encoding gradle.properties
# Method 2: Check for non-UTF-8 characters
grep -P '[^\x00-\x7F]' gradle.properties
# Method 3: Try reading as UTF-8
python3 -c "open('gradle.properties', 'r', encoding='utf-8').read()"
If the file is NOT UTF-8 encoded (e.g., GBK, GB2312, Shift-JIS):
Backup the original file first:
cp gradle.properties gradle.properties.backup
Detect current encoding:
# On macOS/Linux
file -b --mime-encoding gradle.properties
# Or use Python to detect
python3 -c "import chardet; print(chardet.detect(open('gradle.properties', 'rb').read())['encoding'])"
Convert to UTF-8:
# Method 1: Using iconv (macOS/Linux)
# Replace SOURCE_ENCODING with detected encoding (e.g., GBK, GB2312, Shift-JIS)
iconv -f SOURCE_ENCODING -t UTF-8 gradle.properties > gradle.properties.utf8
mv gradle.properties.utf8 gradle.properties
# Method 2: Using Python
python3 << 'EOF'
import chardet
# Detect encoding
with open('gradle.properties', 'rb') as f:
raw = f.read()
detected = chardet.detect(raw)
source_encoding = detected['encoding']
print(f"[信息] 检测到编码: {source_encoding}")
# Convert to UTF-8
content = raw.decode(source_encoding)
with open('gradle.properties', 'w', encoding='utf-8') as f:
f.write(content)
print(f"[信息] 已将 gradle.properties 转换为 UTF-8 编码")
EOF
Verify conversion:
file -b --mime-encoding gradle.properties
# Should output: utf-8
Note: Always keep the backup file until you've verified the build still works correctly.
CRITICAL: Check Compatibility Before Enabling Optimizations
Before proposing android.nonTransitiveRClass=true or android.nonFinalResIds=true, MUST check:
ButterKnife Usage - Search for @BindView annotations:
grep -r "@BindView" --include="*.java" --include="*.kt"
android.nonFinalResIds=false (ButterKnife requires final R.id)Cross-Module R References - Check if library modules reference R resources from other modules:
# Check library modules for R.style, R.layout, R.id references to external resources
grep -r "R\.\(style\|layout\|id\|drawable\|color\)" --include="*.java" --include="*.kt" AndroidLibraries/
android.nonTransitiveRClass=false (library modules need transitive R)Create a diagnostic table comparing current vs optimal:
| # | Optimization | Current Status | Issue | Risk |
|---|---|---|---|---|
| 1 | Configuration Cache | ❌/✅ | ... | Low |
| 2 | Build Cache | ❌/✅ | ... | None |
| 3 | Parallel Execution | ❌/✅ | ... | None |
| 4 | JVM Heap | ⚠️/✅ | ... | None |
| 5 | Non-Transitive R | ❌/✅ | ... | - |
| 6 | Non-Final Res IDs | ❌/✅ | ... | - |
| 7 | Kapt → KSP | ❌/✅ | ... | Medium |
| 8 | Dynamic Dependencies | ⚠️/✅ | ... | Low |
| 9 | Repository Order | ❌/✅ | ... | - |
| 10 | ButterKnife Check | ❌/✅ | 如果有@BindView则不能启用nonFinalResIds | - |
| 11 | Cross-Module R Check | ❌/✅ | 如果有跨模块R引用则不能启用nonTransitiveRClass | - |
Plan A: Zero Risk (Configuration Only)
gradle.propertiesPlan B: Low Risk (Configuration + Dependency Fixes)
Plan C: Medium Risk (Plan B + KSP Migration)
CRITICAL: Present the plan and WAIT for user approval before making any changes.
./gradlew assembleDebug --scan
./gradlew assembleDebug --profile
# Opens report in build/reports/profile/
./gradlew assembleDebug --info | grep -E "^\:.*"
# Or view in Android Studio: Build > Analyze APK Build
| Phase | What Happens | Common Issues |
|---|---|---|
| Initialization | settings.gradle evaluated | Too many include() statements |
| Configuration | All build.gradle files evaluated | Expensive plugins, eager task creation |
| Execution | Tasks run based on inputs/outputs | Cache misses, non-incremental tasks |
Build scan → Performance → Build timeline
Detection: Search for latest.release, +, or x.x.+ in dependencies
// BAD: Forces resolution every build
implementation 'com.example:lib:latest.release'
implementation 'com.example:lib:+'
implementation 'com.example:lib:1.0.+'
// GOOD: Fixed version
implementation 'com.example:lib:1.2.3'
Detection: Same library with different versions
// BAD: Inconsistent versions
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
// GOOD: Unified version
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
Detection: org.gradle.parallel is commented or set to false
# BAD: Parallel disabled
# org.gradle.parallel=true
# GOOD: Parallel enabled
org.gradle.parallel=true
Detection: JVM heap < 3GB for medium/large projects
# BAD: Too small for modern projects
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# GOOD: Adequate memory with GC optimization
org.gradle.jvmargs=-Xmx4096m -XX:+UseParallelGC -Dfile.encoding=UTF-8
Detection: org.gradle.caching not set or set to false
# GOOD: Enable build cache
org.gradle.caching=true
Detection: Using kapt without incremental settings
# GOOD: Enable incremental kapt
kapt.incremental.apt=true
kapt.use.worker.api=true
Detection: Search for @BindView annotations in Java/Kotlin files
grep -r "@BindView" --include="*.java" --include="*.kt" .
Issue: ButterKnife's @BindView annotation requires final R.id constants
// This will FAIL if android.nonFinalResIds=true
@BindView(R.id.iv_avatar) // Error: 元素值必须为常量表达式
ImageView avatarImage;
Solution:
android.nonFinalResIds=falseDetection: Check if library modules reference R resources from other modules
# Check library modules for external R references
grep -rn "R\.\(style\|layout\|id\|drawable\|color\)" --include="*.java" --include="*.kt" AndroidLibraries/ | grep -v "import.*\.R;"
Issue: With nonTransitiveRClass=true, each module only sees its own R resources
// In WheelPicker module, this will FAIL if nonTransitiveRClass=true
// and Animations_FadeInAndOut is defined in app module
wheelWindow.setAnimationStyle(R.style.Animations_FadeInAndOut); // Error: 找不到符号
Solution:
android.nonTransitiveRClass=falseCaches configuration phase across builds (AGP 8.0+):
# gradle.properties
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn
Reuses task outputs across builds and machines:
# gradle.properties
org.gradle.caching=true
Build independent modules simultaneously:
# gradle.properties
org.gradle.parallel=true
Allocate more memory for large projects:
# gradle.properties
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC
Reduces R class size and compilation (AGP 8.0+ default):
# gradle.properties
android.nonTransitiveRClass=true
KSP is 2x faster than kapt for Kotlin:
// Before (slow) - Groovy DSL
kapt 'com.google.dagger:hilt-compiler:2.51.1'
// After (fast) - Groovy DSL
ksp 'com.google.dagger:hilt-compiler:2.51.1'
// Before (slow) - Kotlin DSL
kapt("com.google.dagger:hilt-compiler:2.51.1")
// After (fast) - Kotlin DSL
ksp("com.google.dagger:hilt-compiler:2.51.1")
Pin dependency versions:
// BAD: Forces resolution every build
implementation "com.example:lib:+"
implementation "com.example:lib:1.0.+"
// GOOD: Fixed version
implementation "com.example:lib:1.2.3"
Put most-used repositories first:
// settings.gradle (Groovy DSL)
dependencyResolutionManagement {
repositories {
google() // First: Android dependencies
mavenCentral() // Second: Most libraries
// Third-party repos last
maven { url 'https://jitpack.io' }
}
}
Composite builds are faster than project() for large monorepos:
// settings.gradle (Groovy DSL)
includeBuild("shared-library") {
dependencySubstitution {
substitute(module("com.example:shared")).using(project(":"))
}
}
# gradle.properties
kapt.incremental.apt=true
kapt.use.worker.api=true
Don't read files or make network calls during configuration:
// BAD: Runs during configuration
def version = file("version.txt").text
// GOOD: Defer to execution (Groovy DSL)
def version = providers.fileContents(layout.projectDirectory.file("version.txt")).asText
Avoid create(), use register():
// BAD: Eagerly configured
tasks.create("myTask") { ... }
// GOOD: Lazily configured
tasks.register("myTask") { ... }
# ============================================
# JVM Memory Configuration
# ============================================
org.gradle.jvmargs=-Xmx4096m -XX:+UseParallelGC -Dfile.encoding=UTF-8
# ============================================
# Parallel Compilation
# ============================================
org.gradle.parallel=true
# ============================================
# Build Cache
# ============================================
org.gradle.caching=true
# ============================================
# Daemon Configuration
# ============================================
org.gradle.daemon=true
# ============================================
# Configure on Demand
# ============================================
org.gradle.configureondemand=true
# ============================================
# AndroidX
# ============================================
android.useAndroidX=true
android.enableJetifier=true
# ============================================
# Kotlin Compilation Optimization
# ============================================
kotlin.code.style=official
kotlin.incremental=true
kotlin.incremental.java=true
# ============================================
# Kapt Incremental Compilation
# ============================================
kapt.incremental.apt=true
kapt.use.worker.api=true
# ============================================
# Android Specific Optimization
# ============================================
# ⚠️ PRE-CHECK REQUIRED before enabling these:
# 1. android.nonTransitiveRClass=true
# - Check for cross-module R references in library modules
# - If found, set to false or refactor to move resources
# 2. android.nonFinalResIds=true
# - Check for @BindView (ButterKnife) usage
# - If found, set to false or migrate to ViewBinding
# Enable ONLY after verifying no cross-module R references
android.nonTransitiveRClass=true
# Enable ONLY after verifying no ButterKnife usage
android.nonFinalResIds=true
Symptoms: Build scan shows long "Configuring build" time
Causes & Fixes:
| Cause | Fix |
|---|---|
| Eager task creation | Use tasks.register() instead of tasks.create() |
| buildSrc with many dependencies | Migrate to Convention Plugins with includeBuild |
| File I/O in build scripts | Use providers.fileContents() |
| Network calls in plugins | Cache results or use offline mode |
Symptoms: :app:compileDebugKotlin takes too long
Causes & Fixes:
| Cause | Fix |
|---|---|
| Non-incremental changes | Avoid build.gradle changes that invalidate cache |
| Large modules | Break into smaller feature modules |
| Excessive kapt usage | Migrate to KSP |
| Kotlin compiler memory | Increase kotlin.daemon.jvmargs |
Symptoms: Tasks always rerun despite no changes
Causes & Fixes:
| Cause | Fix |
|---|---|
| Unstable task inputs | Use @PathSensitive, @NormalizeLineEndings |
| Absolute paths in outputs | Use relative paths |
Missing @CacheableTask | Add annotation to custom tasks |
| Different JDK versions | Standardize JDK across environments |
// settings.gradle (Groovy DSL)
buildCache {
local { enabled = true }
remote(HttpBuildCache) {
url = 'https://cache.example.com/'
push = System.getenv("CI") == "true"
credentials {
username = System.getenv("CACHE_USER")
password = System.getenv("CACHE_PASS")
}
}
}
For advanced build analytics:
// settings.gradle (Groovy DSL)
plugins {
id "com.gradle.develocity" version "3.17"
}
develocity {
buildScan {
termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
termsOfUseAgree = "yes"
publishing.onlyIf { System.getenv("CI") != null }
}
}
# Skip tests for UI-only changes
./gradlew assembleDebug -x test -x lint
# Only run affected module tests
./gradlew :feature:login:test
After optimizations, verify:
nonFinalResIds=true)nonTransitiveRClass=true)Problem: After enabling android.nonFinalResIds=true, ButterKnife @BindView annotations fail with "元素值必须为常量表达式" (element value must be a constant expression).
Root Cause: nonFinalResIds makes R.id fields non-final, but annotation parameters require compile-time constants.
Solution: Check for @BindView usage before enabling:
grep -r "@BindView" --include="*.java" --include="*.kt" .
If found, either:
android.nonFinalResIds=falseProblem: After enabling android.nonTransitiveRClass=true, library modules fail with "找不到符号" (symbol not found) when referencing R resources from other modules.
Root Cause: nonTransitiveRClass makes each module's R class contain only its own resources, not resources from dependencies.
Example:
// In WheelPicker module, fails if Animations_FadeInAndOut is in app module
wheelWindow.setAnimationStyle(R.style.Animations_FadeInAndOut);
Solution: Check for cross-module R references before enabling:
grep -rn "R\.\(style\|layout\|id\|drawable\|color\)" --include="*.java" --include="*.kt" AndroidLibraries/ | grep -v "import.*\.R;"
If found, either:
android.nonTransitiveRClass=false