Skip to content

Android SDK, emulators and Gradle

The android category (aliases: gradle, emulators) covers the Android toolchain of a React Native or Expo developer: emulators (AVDs) and their snapshots, SDK packages, the Gradle user home, Android Studio caches and settings, and installed JDKs.

Per-project folders such as android/build, android/app/build, android/.gradle and .cxx are project artifacts, covered in React Native projects.

Terminal window
./gradlew --stop # from any Android project: stop Gradle daemons first
lu-cleaner scan -c android # what takes space; nothing is touched
lu-cleaner clean -c android # picker: nothing selected, press a for the recommended items
lu-cleaner devices # AVDs and system images next to iOS simulators (press a for smart select)

A typical scan (sizes are illustrative):

Android 21.2 GB · 9 items · ★ 7.60 GB recommended NAME SIZE AGE RISK RECO AVD Pixel 8 · API 35 · arm64-v8a 9.80 GB 1d caution System image · API 33 · Google APIs · x86_64 3.90 GB — moderate ★ NDK 27.1.12297006 2.60 GB — moderate NDK 26.1.10909125 2.40 GB — moderate ★ Quick Boot snapshots · Pixel 8 1.90 GB 1d moderate Android Studio caches · 2024.1 1.10 GB 7mo safe ★ Gradle 8.13 caches 980 MB now moderate ! Gradle daemon running — stop it first (./gradlew --stop or pkill -f GradleDaemon) Gradle 8.3 distribution (bin) 196 MB 9mo moderate ★ JDK 11.0.24 (Zulu) · aarch64 190 MB — moderate

Here NDK 27.1.12297006 is kept because it is the newest installed NDK (or because a project uses it), the x86_64 system image cannot boot on Apple Silicon, and the Gradle 8.13 caches are left out of smart select while a daemon runs.

An SDK package or a Gradle version is only worth deleting if no project needs it. To find out, lu-cleaner reads your projects: it walks your project roots and worktree roots (see Configuration) down to max_depth levels (8 by default), skipping heavy folders such as node_modules, build and Pods, and reads these files:

File What it tells lu-cleaner
gradle/wrapper/gradle-wrapper.properties the Gradle distribution of the wrapper (distributionUrl)
build.gradle, build.gradle.kts, gradle.properties ndkVersion, buildToolsVersion, compileSdk, the CMake version, org.gradle.java.home
app.json, app.config.js, app.config.ts versions set through Expo's expo-build-properties
local.properties sdk.dir and ndk.dir
node_modules/react-native/gradle/libs.versions.toml the NDK, build-tools and compileSdk React Native builds with

When a React Native project has no node_modules, the react-native version in its package.json gives the default NDK of that release. Gradle versions installed with Homebrew or SDKMAN also count as in use.

This walk always uses your configured (or auto-detected) roots. Roots passed on the command line (--root, lu-cleaner artifacts <root>) only narrow the artifacts scan: with a single project as root, every version your other projects use would look unused.

lu-cleaner looks for Android SDKs in this order and deduplicates them by real path:

  1. $ANDROID_HOME, then $ANDROID_SDK_ROOT
  2. Android Studio settings (options/android.sdk.path.xml of each AndroidStudio* settings folder)
  3. sdk.dir in your projects' local.properties, only if that path exists (a committed file may name another machine's SDK)
  4. the default ~/Library/Android/sdk
  5. Homebrew's android-commandlinetools (/opt/homebrew/share/… and /usr/local/share/…)

Environment variables are read from the environment first, then from your shell startup files (.zshenv, .zprofile, .zshrc, .bash_profile, .bashrc, .profile), because GUI apps and AI agents do not inherit your shell's exports. Only simple assignments are understood: literal paths, ~, $HOME and variables already defined; anything computed with $(...) or backticks is ignored.

The same resolution is used for the AVD folder ($ANDROID_AVD_HOME, $ANDROID_EMULATOR_HOME/avd, $ANDROID_USER_HOME/avd, $ANDROID_PREFS_ROOT/.android/avd, then ~/.android/avd), for $GRADLE_USER_HOME (default ~/.gradle) and for $JAVA_HOME.

A common setup moves the SDK and the emulators to an external SSD and leaves symlinks behind, for example ~/Library/Android/sdk pointing to /Volumes/External/Android/sdk. lu-cleaner resolves every symlink and checks which volume the real files are on. Anything that is not on your home volume is reported, never deleted, and the symlinks themselves are never removed:

Situation What you see
Files on another volume Android SDK on External, on external volume — no internal gain
Drive not mounted on /Volumes/External, which is not mounted — nothing to clean here (link kept)
Symlink target missing dangling symlink to … — nothing to clean here (link kept)
/Volumes/External is a plain folder on the internal disk … is a plain folder on the internal disk, not a mounted volume — check it by hand
SDK on the internal disk but outside your home folder (Homebrew) measured, outside your home folder — remove it by hand if unused

An AVD folder in the same situation shows up as Android emulators (3 AVDs) on External, with the same warnings, and its emulators are listed for information only.

Why: deleting from an external drive frees nothing on the internal disk you are trying to clean, and when the drive is unmounted lu-cleaner cannot see what is there. The last two cases are outside the areas the safety guard allows. Inside an internal SDK, a single package that is a symlink is reported the same way.

An AVD is a <name>.avd folder plus a <name>.ini file next to it. lu-cleaner shows one item per AVD, for example AVD Pixel 8 · API 35 · arm64-v8a, and removes both.

AVD Risk Smart select
Regular AVD caution never: it holds installed apps, their data and snapshots
x86 or x86_64 AVD on Apple Silicon moderate yes: it cannot boot on this Mac
Broken entry (an .ini whose folder is gone) safe yes: only the .ini is removed
Running emulator as above warning emulator is running — close it first

Other warnings tell you when an AVD has no .ini (the emulator cannot see it) or when its system image is not installed (it cannot start). Recreating an AVD in Android Studio's Device Manager is quick, but its apps and data are gone.

The snapshots/ folder of an AVD holds default_boot, the RAM and device state saved on exit for Quick Boot. Deleting it only costs speed: the next launch cold-boots (30 to 90 seconds), and apps and data stay. The item, Quick Boot snapshots · Pixel 8, is moderate and recommended once stale.

If you saved named snapshots yourself, the item becomes Emulator snapshots · Pixel 8, risk caution, with a warning listing them. lu-cleaner never removes an AVD's userdata images on their own: snapshots depend on them.

NDKs, build-tools, platforms, CMake and sources follow the same keep rules:

  • Used by a project: kept. The item shows used_by (in the details panel and in --json metadata) and is never recommended.
  • Newest installed version: always kept. Set keep_latest in the configuration to keep more of the newest versions (1 by default).
  • Anything else: recommended, as long as the project walk completed.

System images follow the emulators instead: an image used by an AVD cannot be selected, and an unused one is recommended.

Package Kind (-k) Kept when Coming back
System images, system-images/android-<API>/<tag>/<abi> android-system-image an AVD uses it (then it cannot be selected at all) SDK Manager, 1.5 to 7 GB each
NDK, ndk/<version> android-ndk a project uses it, or newest AGP downloads it again (~1 GB)
Build-tools, build-tools/<version> android-build-tools a project uses it, or newest AGP downloads it automatically
Platforms, platforms/android-<N> android-platform a project's compileSdk, or newest AGP downloads it automatically
CMake, cmake/<version> android-cmake a project uses it, newest, or 3.22.1 (the AGP 8 default React Native builds with) AGP downloads the version it needs
Sources, sources/android-<N> android-sources a project's compileSdk, or newest only used to browse code in the IDE

System images are moderate like the other packages. If an AVD folder cannot be read (unmounted drive), images carry an AVD folder is not reachable — cannot tell whether an emulator uses this image and are not recommended. A folder named like 35.0.0-2 is the leftover of a failed install: safe and recommended.

Legacy and leftover SDK content:

Item Kind (-k) Risk Smart select
SDK Manager temp and partial downloads (.temp, .downloadIntermediates, temp) android-sdk-temp safe yes; skipped while Android Studio runs
Legacy SDK Tools, tools/ android-sdk-legacy-tools moderate yes, when cmdline-tools/latest is installed
Superseded cmdline-tools/<version> copies android-cmdline-tools-old moderate yes; latest is the one in use
Legacy ndk-bundle android-ndk-bundle moderate yes, unless a project pins ndk.dir or ndkPath
Intel HAXM (extras/intel) on Apple Silicon android-haxm moderate yes

x86 and x86_64 system images and AVDs cannot boot on an Apple Silicon Mac; they are typical leftovers of a migration from an Intel Mac. lu-cleaner adds cannot boot on Apple Silicon to their note and recommends them. This is a reason to delete them rather than a danger, so it is not shown as a warning.

Gradle keeps everything in its user home, $GRADLE_USER_HOME or ~/.gradle. If that folder lives on another volume, it is reported and never cleaned.

Item Kind (-k) Risk Smart select and notes
Wrapper distribution, wrapper/dists/gradle-<v>-bin (or -all) android-gradle-dist moderate recommended when no project wrapper uses it; ./gradlew re-downloads it (130 to 200 MB)
Interrupted wrapper download (no .ok marker) android-gradle-dist safe recommended, unless a .part file changed in the last hour
Per-version caches, caches/<version> android-gradle-version-cache moderate recommended when no wrapper, Homebrew or SDKMAN Gradle uses that version
Old-format transforms, jars or build cache (for example transforms-3 when transforms-4 exists) android-gradle-transforms, android-gradle-jars, android-gradle-build-cache safe recommended
Current local build cache android-gradle-build-cache safe only speeds up rebuilds; refills itself
Current transforms cache android-gradle-transforms moderate dexing, jetifier and AAR extraction are recomputed (a few extra minutes)
Dependency cache, caches/modules-2 android-gradle-modules moderate 1 to 3 GB re-downloaded by the next build; offline builds fail until then
Other shared caches (Kotlin DSL, scripts, keyrings…) android-gradle-caches-other moderate rebuilt or re-downloaded
Daemon logs, daemon/<version> android-gradle-daemon-logs safe while a daemon of that version runs, only logs older than 24 hours are proposed
Temp files and partial downloads, .tmp android-gradle-tmp safe files older than a day, which neither Gradle nor other cleaners remove; recommended
Toolchain JDKs, jdks/* android-gradle-jdk moderate re-downloaded (200 to 300 MB) when a build requests them; never recommended
Native helpers, Kotlin build reports, worker state android-gradle-misc safe re-created automatically

caches/journal-1 is never proposed: Gradle's own cache cleanup relies on it.

The last-used date of a Gradle version comes from its daemon logs, the most reliable usage signal. A distribution or cache that a project uses is not recommended as unused, but like every moderate item it is recommended once it has not been used for longer than stale_after; the next build of that project then downloads or rebuilds it. See Risk levels and smart select.

Gradle daemons are JVM processes named java, so lu-cleaner cannot block cleaning by process name the way it does for Xcode or Android Studio. Instead, when pgrep finds a GradleDaemon, the affected items get this warning and are left out of smart select:

Gradle daemon running — stop it first (./gradlew --stop or pkill -f GradleDaemon)

Version-specific items (distributions, per-version caches) are only flagged when a daemon of that version runs, or when a daemon's version cannot be read. Shared caches (transforms, jars, build cache, modules-2, other caches, toolchain JDKs, misc) are flagged whenever any daemon runs.

Why it matters: a running daemon keeps deleted files open, so their space is not freed until it exits, and a build in progress can fail halfway. clean --yes holds these items back unless you pass --risk caution, but the warning does not stop a clean you select yourself in the picker, so stop the daemons first. ./gradlew --stop stops the daemons of that project's Gradle version; pkill -f GradleDaemon stops all of them.

Item Kind (-k) Risk Notes
Caches of the installed version, ~/Library/Caches/Google/AndroidStudio<version> android-studio-cache moderate rebuilt on next launch (re-indexing takes minutes); skipped while Android Studio runs
Caches of a version no longer installed android-studio-cache safe nothing reads them; recommended
Logs, ~/Library/Logs/Google/AndroidStudio* android-studio-logs safe idea.log and crash reports
Settings of an old version, ~/Library/Application Support/Google/AndroidStudio* android-studio-config moderate only read to migrate settings on upgrade; recommended when the current version has its own settings, never while an installed version has not been started yet (it imports them at first launch)
SDK repository cache, legacy build cache and emulator crash dumps in ~/.android android-user-cache safe the contents of cache, build-cache and breakpad only

The settings folder of the version you use is never proposed. lu-cleaner finds the installed versions from the product-info.json of every Android Studio*.app in /Applications, ~/Applications and Spotlight results. If none is found (custom location, JetBrains Toolbox…), the most recently used settings folder is treated as the current one, to stay on the safe side. With keep_latest above 1, that many recently used versions count as current.

A caches folder can hold LocalHistory, the IDE's history of your file edits. It is never proposed: the item then targets the other entries of the folder.

In ~/.android, adbkey, debug.keystore and the emulator settings are never touched: they are on the guard's protected list.

Android builds need a JDK, and most machines collect several. lu-cleaner lists them all and never recommends one:

Location Installed by Handling
/Library/Java/JavaVirtualMachines system installers, Homebrew casks report only: removing it needs admin rights; the note suggests brew uninstall --cask <cask> or sudo rm -rf …
~/Library/Java/JavaVirtualMachines IDE downloads moderate
~/.sdkman/candidates/java SDKMAN moderate
~/.local/share/mise/installs/java mise moderate
~/.asdf/installs/java asdf moderate

A JDK referenced by $JAVA_HOME (environment or shell profile) or by org.gradle.java.home in ~/.gradle/gradle.properties is marked in use and cannot be selected. The details also say when a JDK is registered in Android Studio's JDK table, when it is older than JDK 17 (React Native 0.73+ needs 17), and warn about x86_64 JDKs on Apple Silicon (x86_64 JDK: runs only under Rosetta).

These fixed locations come from the catalog:

Item Location Risk
Kotlin daemon state ~/.kotlin, ~/Library/Application Support/kotlin/daemon moderate
Kotlin/Native dependencies and caches ~/.konan/dependencies, ~/.konan/cache moderate
Kotlin/Native compilers (newest kept) ~/.konan/kotlin-native-prebuilt-* moderate
Maven local repository ~/.m2/repository caution: artifacts you published locally cannot be re-downloaded
Skiko native libs (newest kept) ~/.skiko/* safe
Emulator temp files $TMPDIR/android-* safe; skipped while an emulator runs
Genymotion virtual devices ~/.Genymobile/Genymotion/deployed/* report only: delete them from Genymotion
APKs stored for Maestro ~/.maestro/apps/android/*.apk report only
  1. Stop Gradle daemons, close every emulator and quit Android Studio:

    Terminal window
    pkill -f GradleDaemon
  2. Check that your project roots cover every Android project, so "unused" can be trusted:

    Terminal window
    lu-cleaner config show
  3. Review everything, including report-only items such as an SDK on an external drive:

    Terminal window
    lu-cleaner scan -c android --all
  4. Open the picker, press a to select the recommended items (or pick with space), review them category by category, then press d:

    Terminal window
    lu-cleaner clean -c android
Terminal window
# The plan smart select would run; nothing is deleted
lu-cleaner clean --yes --smart -c android --dry-run
# Unused system images (x86 images on Apple Silicon included)
lu-cleaner clean --yes --smart -k android-system-image
# Gradle distributions and per-version caches no project needs
lu-cleaner clean --yes --smart -k android-gradle-dist,android-gradle-version-cache --dry-run
# Gradle temp files and daemon logs
lu-cleaner clean --yes -k android-gradle-tmp,android-gradle-daemon-logs

Every kind above can be passed to -k, and the provider id android selects all the kinds of the Android scanner at once. The catalog entries of the previous section have their own ids (android-maven-local, android-konan-dependencies…), listed in the catalog. Each item's metadata (used_by, kept, api, abi, version, real_path…) is available in lu-cleaner scan --json, see JSON output.