Site icon AppTractor

Чистим Android-проект просто и правильно

Зачем нужна расширенная очистка

При разработке Android-приложений дисковое пространство может быстро загрязняться результатами сборки, кэшами Gradle и конфигурационными файлами IDE.

По умолчанию команда Android Studio «Build → Clean Project» удаляет не все. Могут накапливаться кэши, артефакты из модулей, остатки файлов Gradle и результаты сборки.

В этой статье представлен один скрипт, который комплексно решает все эти проблемы, с фантастическим выводом консоли, показывающим использование пространства до и после. Мы рассмотрим macOS/Linux (с помощью Bash) и Windows (с помощью Batch), а также проясним, как Lite-чистка скрипта сравнивается с Build → Clean Project с точки зрения освобождаемого дискового пространства и объема проекта.

Как и все, что «удаляет» содержимое, используйте скрипт с осторожностью.

Почему бы просто не использовать Build → Clean Project?

  1. Ограниченная область применения: Как правило, он нацелен только на папки сборки того модуля(ей), который(ые) в данный момент открыт(ы) в IDE.
  2. Пропускает Gradle: При этом .gradle остается нетронутым; вы не сможете освободить это место или исправить поврежденные кэши.
  3. Нет групповой очистки: Если у вас несколько проектов в одной папке (например, ~/AndroidStudioProjects), вам придется открывать и очищать каждый проект вручную.

Легкая и глубокая очистка

Мы определяем два уровня очистки в нашем скрипте:

Lite Cleanup

Deep Cleanup

Как Lite Cleanup сравнивается с Build → Clean Project?

Освобождение дискового пространства:

Гибкость и автоматизация:

Никаких кэшей Gradle:

Итог

Если вы привыкли к Build → Clean Project на однопроектной, одномодульной установке, то Lite cleanup примерно то же самое с точки зрения экономии дискового пространства.

Однако наш сценарий более удобен (особенно для многомодульной/массовой очистки) и легко расширяется до более глубокой очистки, когда это необходимо.

1. Универсальный сценарий для macOS/Linux

Ниже представлен единый Bash сценарий , поддерживающий следующие параметры:

Single vs. Bulk

Lite vs. Deep

Почему Deep по умолчанию?

1. Версия для macOS/Linux (Bash)

Сохраните следующий скрипт под именем android-cleanup.sh, сделайте его исполняемым (chmod +x android-cleanup.sh) и поместите его в любое удобное для вас место (например, ~/Scripts).

#!/bin/bash
#
# Usage Examples:
#   1) Deep cleanup (no params):
#       ./android-cleanup.sh
#   2) Lite cleanup (standard):
#       ./android-cleanup.sh lite
#   3) Deep bulk cleanup in current folder:
#       ./android-cleanup.sh bulk
#   4) Lite bulk cleanup:
#       ./android-cleanup.sh bulk lite
#
# Merged script:
#  - Deep is default (removes .gradle, etc.), skipping gradlew clean
#  - "lite" mode => standard, calls gradlew clean, doesn't remove .gradle
#  - "bulk" => clean multiple subfolders
#  - Lists discovered modules, cleans "buildSrc", shows largest dirs after cleanup
#

################################
# Parse Script Arguments
################################
IS_BULK=false
IS_DEEP=true   # default is deep unless user passes 'lite'

for arg in "$@"; do
  case "$arg" in
    bulk)
      IS_BULK=true
      ;;
    lite)
      IS_DEEP=false
      ;;
    deep)
      IS_DEEP=true
      ;;
    *)
      echo "Unknown argument: $arg"
      echo "Usage: $0 [bulk] [lite] [deep]"
      echo " - (no args) => deep mode"
      echo " - bulk => process subfolders"
      echo " - lite => standard cleanup (build only, plus gradlew clean)"
      echo " - deep => ensures we do a deep cleanup"
      exit 1
      ;;
  esac
done

################################
# Print Which Mode We're In
################################
if [ "$IS_DEEP" = true ]; then
  echo "Using DEEP cleanup mode..."
else
  echo "Using STANDARD (lite) cleanup mode..."
fi

################################
# Configure Directories to Clean
################################
if [ "$IS_DEEP" = true ]; then
  directories_to_clean=(
    "build"              # Root project build
    "app/build"          # Main app module
    ".gradle"            # Root Gradle cache
    "*/build"            # Any module's build directory
    "buildSrc/.gradle"   # BuildSrc gradle cache
    "buildSrc/build"     # BuildSrc build directory
    # Uncomment if you also want to remove .idea:
    # ".idea"
  )
else
  # Lite mode => no .gradle removal
  directories_to_clean=(
    "build"
    "app/build"
    "*/build"            # Include all modules' build dirs
    "buildSrc/build"     # BuildSrc build directory
    # buildSrc/.gradle intentionally omitted for standard mode
  )
fi

################################
# Helper Function: Clean a Single Project
################################
clean_single_project() {
  local project_path="$1"

  echo "----------------------------------------"
  echo "📁 Cleaning project: $project_path"

  cd "$project_path" || {
    echo "Could not enter $project_path; skipping."
    return
  }

  echo "----------------------------------------"
  echo "📊 Space usage before cleanup:"
  du -sh . 2>/dev/null

  # List discovered modules (just for info)
  echo "🔍 Detected modules (via build.gradle / build.gradle.kts):"
  find . \( -name "build.gradle" -o -name "build.gradle.kts" \) \
    | sed 's/\/build\.gradle.*$//' \
    | sort -u \
    | while read -r module; do
        if [ "$module" != "." ]; then
          # remove "./" if present
          mod="${module#./}"
          echo "   - $mod"
        fi
      done

  # In lite mode => run Gradle clean if present
  # In deep mode => skip Gradle clean to avoid downloads
  if [ "$IS_DEEP" = false ]; then
    echo "----------------------------------------"
    echo "🧹 Running Gradle clean (lite mode)..."
    if [ -f "./gradlew" ]; then
      ./gradlew clean
    else
      echo "⚠️  Warning: No gradlew found in current directory"
    fi
  else
    echo "----------------------------------------"
    echo "🧹 Skipping Gradle clean (deep mode)..."
  fi

  # Remove build directories using 'find' to handle wildcards
  echo "----------------------------------------"
  echo "🗑️  Removing build directories..."
  for dir in "${directories_to_clean[@]}"; do
    # We'll search for exact matches to the basename, but also
    # ensure the full path ends with the $dir pattern
    find . -type d -path "*/$dir" -exec sh -c '
      echo "   Removing: $1"
      rm -rf "$1"
    ' sh {} \; 2>/dev/null
  done

  # Show final space usage
  echo "----------------------------------------"
  echo "✨ Cleanup complete!"
  echo "📊 Space usage after cleanup:"
  du -sh . 2>/dev/null

  # Largest remaining directories - helpful for debugging
  echo "📊 Largest remaining directories (top 5):"
  # On macOS, 'du -d 2' might be 'du -n 2' or 'du -depth=2' depending on version
  # We'll assume GNU/BSD style works:
  du -h -d 2 . 2>/dev/null | sort -hr | head -n 5

  echo "----------------------------------------"
  cd - >/dev/null || return
}

################################
# Main Logic: Single or Bulk
################################
if [ "$IS_BULK" = true ]; then
  echo "Performing BULK cleanup in $(pwd)"
  for project_dir in */; do
    if [ -d "$project_dir" ]; then
      clean_single_project "$project_dir"
    fi
  done
  echo "Bulk cleanup complete!"
else
  echo "Performing SINGLE-PROJECT cleanup in $(pwd)"
  clean_single_project "$(pwd)"
fi

Использование в macOS/Linux

2. Версия для Windows (PowerShell)

Сохраните файл android-cleanup.ps1 и запустите его с помощью PowerShell (или Windows Terminal). Если заблокировано политикой, можно обойти ее с помощью:

powershell.exe -ExecutionPolicy Bypass -File .\android-cleanup.ps1

Политика выполнения PowerShell

По умолчанию PowerShell может запретить выполнение скриптов, если они не подписаны:

Вариант 1:

Временно обойдите с помощью

powershell.exe -ExecutionPolicy Bypass -File .\android-cleanup.ps1

Вариант 2:

Сделайте Set-ExecutionPolicy RemoteSigned (или Unrestricted), чтобы разрешить запуск локальных скриптов.

Убедитесь, что вы доверяете сценарию, прежде чем изменять эти политики.

Param(
    [Parameter(ValueFromRemainingArguments=$true)]
    [String[]] $Args
)

# Default: DEEP cleanup
$IS_BULK = $false
$IS_DEEP = $true

# Parse arguments (bulk, lite, deep)
foreach ($arg in $Args) {
    switch ($arg.ToLower()) {
        'bulk' { $IS_BULK = $true }
        'lite' { $IS_DEEP = $false }
        'deep' { $IS_DEEP = $true }
        default {
            Write-Host "Unknown argument: $arg"
            Write-Host "Usage: ./android-cleanup.ps1 [bulk] [lite] [deep]"
            Write-Host " - (no args) => deep cleanup"
            Write-Host " - bulk => process subfolders"
            Write-Host " - lite => standard cleanup (build only, plus gradlew clean)"
            Write-Host " - deep => ensures deep cleanup"
            exit
        }
    }
}

if ($IS_DEEP) {
    Write-Host "Using DEEP cleanup mode..."
} else {
    Write-Host "Using STANDARD (lite) cleanup mode..."
}

# Configure directories to clean
$DirectoriesToClean = @()
if ($IS_DEEP) {
    # Deep => remove .gradle, buildSrc caches, etc.
    $DirectoriesToClean += "build"
    $DirectoriesToClean += "app/build"
    $DirectoriesToClean += ".gradle"
    $DirectoriesToClean += "buildSrc/.gradle"
    $DirectoriesToClean += "buildSrc/build"
    $DirectoriesToClean += "*/build"  # wildcard submodules
    # Uncomment if you also want to remove .idea:
    # $DirectoriesToClean += ".idea"
}
else {
    # Lite => keep .gradle, just remove build outputs
    $DirectoriesToClean += "build"
    $DirectoriesToClean += "app/build"
    $DirectoriesToClean += "buildSrc/build"
    $DirectoriesToClean += "*/build"
}

########################################################################
# Clean-SingleProject - does NOT change directory if we're already there
########################################################################
function Clean-SingleProject {
    param($ProjectPath)

    Write-Host "----------------------------------------"
    Write-Host "📁 Cleaning project: $ProjectPath"

    # If in bulk mode, we need to enter each subfolder. Otherwise, do nothing.
    if ($IS_BULK) {
        # Attempt to switch into project directory
        try {
            Push-Location -LiteralPath $ProjectPath -ErrorAction Stop
        }
        catch {
            Write-Host "Could not enter $ProjectPath; skipping."
            return
        }
    }
    else {
        # Single-project mode => assume we are ALREADY inside the project folder
        # Do not change directory, matching mac script behavior.
    }

    Write-Host "----------------------------------------"
    Write-Host "📊 Space usage before cleanup:"
    # Approximate 'du -sh .' via summing file sizes
    Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue | 
        Measure-Object -Property Length -Sum |
        ForEach-Object {
            $sizeMB = [math]::Round($_.Sum / 1MB, 2)
            Write-Host "$sizeMB MB total (approx)"
        }

    # Detect modules by build.gradle / build.gradle.kts
    Write-Host "🔍 Detected modules (via build.gradle / build.gradle.kts):"
    $gradleFiles = Get-ChildItem -Recurse -Include build.gradle,build.gradle.kts -File -ErrorAction SilentlyContinue
    if ($gradleFiles) {
        $modules = $gradleFiles |
            ForEach-Object { $_.DirectoryName } |
            Sort-Object -Unique

        foreach ($modPath in $modules) {
            # Convert to relative path
            $rel = Resolve-Path -LiteralPath $modPath
            $cwd = Get-Location
            $relStr = $rel -replace [regex]::Escape($cwd.Path + "\\"), ".\"
            Write-Host "   - $relStr"
        }
    }
    else {
        Write-Host "   (No gradle files found)"
    }

    # If in lite mode => run Gradle clean
    if (-not $IS_DEEP) {
        Write-Host "----------------------------------------"
        Write-Host "🧹 Running Gradle clean (lite mode)..."
        if (Test-Path "./gradlew.bat") {
            Start-Process "./gradlew.bat" "clean" -NoNewWindow -Wait
        }
        elseif (Test-Path "./gradlew") {
            # Possibly a gradlew shell script
            Start-Process "powershell" "./gradlew clean" -NoNewWindow -Wait
        }
        else {
            Write-Host "⚠️  Warning: No gradlew found in current directory"
        }
    }
    else {
        Write-Host "----------------------------------------"
        Write-Host "🧹 Skipping Gradle clean (deep mode)..."
    }

    Write-Host "----------------------------------------"
    Write-Host "🗑️  Removing build directories..."
    foreach ($dirPattern in $DirectoriesToClean) {
        if ($dirPattern -like '*/build') {
            # search all subfolders named "build"
            $buildDirs = Get-ChildItem -Directory -Recurse -Filter build -ErrorAction SilentlyContinue
            foreach ($bDir in $buildDirs) {
                Write-Host "   Removing: $($bDir.FullName)"
                Remove-Item -LiteralPath $bDir.FullName -Recurse -Force -ErrorAction SilentlyContinue
            }
        }
        else {
            # direct path
            if (Test-Path $dirPattern) {
                $fullPath = Resolve-Path $dirPattern -ErrorAction SilentlyContinue
                if ($fullPath) {
                    Write-Host "   Removing: $($fullPath.ToString())"
                    Remove-Item $fullPath -Recurse -Force -ErrorAction SilentlyContinue
                }
            }
        }
    }

    Write-Host "----------------------------------------"
    Write-Host "✨ Cleanup complete!"
    Write-Host "📊 Space usage after cleanup:"
    Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue | 
        Measure-Object -Property Length -Sum |
        ForEach-Object {
            $sizeMB = [math]::Round($_.Sum / 1MB, 2)
            Write-Host "$sizeMB MB total (approx)"
        }

    Write-Host "📊 Largest remaining directories (top 5):"
    Get-ChildItem -Directory -Recurse -ErrorAction SilentlyContinue |
        ForEach-Object {
            $subTotal = ($_ | Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue |
                Measure-Object -Property Length -Sum).Sum
            [PSCustomObject]@{
                Path   = $_.FullName
                SizeMB = [math]::Round($subTotal / 1MB, 2)
            }
        } | Sort-Object SizeMB -Descending | Select-Object -First 5 |
        ForEach-Object {
            Write-Host ("   {0} MB  {1}" -f $_.SizeMB, $_.Path)
        }

    Write-Host "----------------------------------------"

    # If we entered in bulk mode, pop back out
    if ($IS_BULK) {
        Pop-Location | Out-Null
    }
}

# Main logic
if ($IS_BULK) {
    Write-Host "Performing BULK cleanup in $(Get-Location)"
    $subfolders = Get-ChildItem -Directory
    foreach ($folder in $subfolders) {
        Clean-SingleProject $folder.FullName
    }
    Write-Host "Bulk cleanup complete!"
}
else {
    Write-Host "Performing SINGLE-PROJECT cleanup in $(Get-Location)"
    # We'll call Clean-SingleProject with the same folder, but no directory change
    Clean-SingleProject (Get-Location)
}

Использование в Windows

Отдельное спасибо Джахангиру Джади за помощь на этапе создания и тестирования PowerShell-версии скрипта.

Пример использования скрипта

Lite для одного проекта (Mac)

Когда вы указываете lite для отдельного проекта, он эффективно вызывает ./gradlew clean за вас, не открывая проект в Android Studio. Вы увидите вывод о том, какие модули были обнаружены и сколько места было освобождено.

% ~/scripts/android-cleaner.sh lite
Using STANDARD (lite) cleanup mode...
Performing SINGLE-PROJECT cleanup in /Users/ianyfantakis/AndroidStudioProjects/SampleProject
----------------------------------------
📁 Cleaning project: /Users/ianyfantakis/AndroidStudioProjects/SampleProject
----------------------------------------
📊 Space usage before cleanup:
219M .
🔍 Detected modules (via build.gradle / build.gradle.kts):
   - app
----------------------------------------
🧹 Running Gradle clean (lite mode)...

BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
----------------------------------------
🗑️  Removing build directories...
----------------------------------------
✨ Cleanup complete!
📊 Space usage after cleanup:
5.8M .
📊 Largest remaining directories (top 5):
5.8M .
3.8M ./.gradle/8.9
3.8M ./.gradle
1.3M ./.git
1.2M ./.git/objects
----------------------------------------

Как видите, проект уменьшился с 219 до 5.8 МБ.

Deep для одного проекта (Mac)

Если мы ничего не указываем, по умолчанию используется режим глубокой очистки. Это значительно быстрее, поскольку не выполняется скрипт Gradle, и папка .gradle удаляется, освобождая еще больше места.

% ~/scripts/android-cleaner.sh     
Using DEEP cleanup mode...
Performing SINGLE-PROJECT cleanup in /Users/ianyfantakis/AndroidStudioProjects/SampleProject
----------------------------------------
📁 Cleaning project: /Users/ianyfantakis/AndroidStudioProjects/SampleProject
----------------------------------------
📊 Space usage before cleanup:
218M .
🔍 Detected modules (via build.gradle / build.gradle.kts):
   - app
----------------------------------------
🧹 Skipping Gradle clean (deep mode)...
----------------------------------------
🗑️  Removing build directories...
   Removing: ./app/build
   Removing: ./.gradle
----------------------------------------
✨ Cleanup complete!
📊 Space usage after cleanup:
2.0M .
📊 Largest remaining directories (top 5):
2.0M .
1.3M ./.git
1.2M ./.git/objects
588K ./app
560K ./app/src
----------------------------------------

Мы перешли с 218 до 2 МБ — огромная экономия.

Массовая Lite очистка (Mac)

% ~/scripts/android-cleaner.sh bulk lite
Using STANDARD (lite) cleanup mode...
Performing BULK cleanup in /Users/ianyfantakis/AndroidStudioProjects
----------------------------------------
📁 Cleaning project: CameraRecorder/
----------------------------------------
📊 Space usage before cleanup:
 92M .
🔍 Detected modules (via build.gradle / build.gradle.kts):
   - app
----------------------------------------
🧹 Running Gradle clean (lite mode)...

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
----------------------------------------
🗑️  Removing build directories...
----------------------------------------
✨ Cleanup complete!
📊 Space usage after cleanup:
1.7M .
📊 Largest remaining directories (top 5):
1.7M .
1.4M ./.gradle
1.2M ./.gradle/8.4
204K ./.gradle/buildOutputCleanup
160K ./app
----------------------------------------
----------------------------------------
📁 Cleaning project: ColorChanger1 copy 2/
----------------------------------------
📊 Space usage before cleanup:
 44M .
🔍 Detected modules (via build.gradle / build.gradle.kts):
   - app
----------------------------------------
🧹 Running Gradle clean (lite mode)...
Starting a Gradle Daemon (subsequent builds will be faster)

BUILD SUCCESSFUL in 4s
2 actionable tasks: 2 executed
----------------------------------------
🗑️  Removing build directories...
----------------------------------------
✨ Cleanup complete!
📊 Space usage after cleanup:
5.4M .
📊 Largest remaining directories (top 5):
5.4M .
4.7M ./.gradle
3.5M ./.gradle/7.2
1.0M ./.gradle/buildOutputCleanup
468K ./.idea
----------------------------------------

Массовая Deep очистка (Mac)

% ~/scripts/android-cleaner.sh bulk
Using DEEP cleanup mode...
Performing BULK cleanup in /Users/ianyfantakis/AndroidStudioProjects
----------------------------------------
📁 Cleaning project: CameraRecorder/
----------------------------------------
📊 Space usage before cleanup:
 92M .
🔍 Detected modules (via build.gradle / build.gradle.kts):
   - app
----------------------------------------
🧹 Skipping Gradle clean (deep mode)...
----------------------------------------
🗑️  Removing build directories...
   Removing: ./app/build
   Removing: ./.gradle
----------------------------------------
✨ Cleanup complete!
📊 Space usage after cleanup:
312K .
📊 Largest remaining directories (top 5):
312K .
160K ./app
148K ./app/src
 64K ./gradle/wrapper
 64K ./gradle
----------------------------------------
----------------------------------------
📁 Cleaning project: ColorChanger/
----------------------------------------
📊 Space usage before cleanup:
 44M .
🔍 Detected modules (via build.gradle / build.gradle.kts):
   - app
----------------------------------------
🧹 Skipping Gradle clean (deep mode)...
----------------------------------------
🗑️  Removing build directories...
   Removing: ./app/build
   Removing: ./build
   Removing: ./.gradle
----------------------------------------
✨ Cleanup complete!
📊 Space usage after cleanup:
752K .
📊 Largest remaining directories (top 5):
752K .
468K ./.idea
388K ./.idea/libraries
180K ./app
160K ./app/src
----------------------------------------

Массовая Deep очистка (Windows PowerShell)

PS D:\TestProjects> .\android-cleanup.ps1 bulk
Using DEEP cleanup mode...
Performing BULK cleanup in D:\TestProjects
----------------------------------------
📁 Cleaning project: D:\TestProjects\LogMyCareCarer
----------------------------------------
📊 Space usage before cleanup:
852.48 MB total (approx)
🔍 Detected modules (via build.gradle / build.gradle.kts):
   - D:\TestProjects\LogMyCareCarer
   - D:\TestProjects\LogMyCareCarer\app
----------------------------------------
🧹 Skipping Gradle clean (deep mode)...
----------------------------------------
🗑️  Removing build directories...
   Removing: D:\TestProjects\LogMyCareCarer\build
   Removing: D:\TestProjects\LogMyCareCarer\app\build
   Removing: D:\TestProjects\LogMyCareCarer\.gradle
   Removing: D:\TestProjects\LogMyCareCarer\app\build
   Removing: D:\TestProjects\LogMyCareCarer\app\build
----------------------------------------
✨ Cleanup complete!
📊 Space usage after cleanup:
36.95 MB total (approx)
📊 Largest remaining directories (top 5):
   35.91 MB  D:\TestProjects\LogMyCareCarer\app
   25.73 MB  D:\TestProjects\LogMyCareCarer\app\release
   8.52 MB  D:\TestProjects\LogMyCareCarer\app\src
   8.52 MB  D:\TestProjects\LogMyCareCarer\app\src\main
   4.21 MB  D:\TestProjects\LogMyCareCarer\app\src\main\res
----------------------------------------
----------------------------------------
📁 Cleaning project: D:\TestProjects\LogMyCareManager
----------------------------------------
📊 Space usage before cleanup:
566.32 MB total (approx)
🔍 Detected modules (via build.gradle / build.gradle.kts):
   - D:\TestProjects\LogMyCareManager
   - D:\TestProjects\LogMyCareManager\app
----------------------------------------
🧹 Skipping Gradle clean (deep mode)...
----------------------------------------
🗑️  Removing build directories...
   Removing: D:\TestProjects\LogMyCareManager\build
   Removing: D:\TestProjects\LogMyCareManager\app\build
   Removing: D:\TestProjects\LogMyCareManager\.gradle
   Removing: D:\TestProjects\LogMyCareManager\app\build
   Removing: D:\TestProjects\LogMyCareManager\app\build
----------------------------------------
✨ Cleanup complete!
📊 Space usage after cleanup:
24.74 MB total (approx)
📊 Largest remaining directories (top 5):
   24.63 MB  D:\TestProjects\LogMyCareManager\app
   17.52 MB  D:\TestProjects\LogMyCareManager\app\release
   5.11 MB  D:\TestProjects\LogMyCareManager\app\src\main
   5.11 MB  D:\TestProjects\LogMyCareManager\app\src
   3.5 MB  D:\TestProjects\LogMyCareManager\app\src\main\res
----------------------------------------
PS D:\TestProjects>

Итого

При использовании глубокой очистки по умолчанию этот скрипт освобождает максимум места без лишних загрузок Gradle. Если вам нужна только build-only очистка (как обычная «Gradle clean»), используйте lite. Опционально добавьте bulk для очистки нескольких подпапок (полезно, если вы храните много проектов Android в одной директории).

Финальные мысли

Наслаждайтесь удобством одного скрипта, который значительно превосходит встроенную в Android Studio функцию Clean Project, удаляя кэш, поддерживая сценарии с несколькими модулями или несколькими проектами и автоматизируя все в массовом режиме. Счастливой уборки!

Источник

Exit mobile version