← Назад к вопросам

Для чего нужны SourceSets?

2.0 Middle🔥 201 комментариев
#Android компоненты#Тестирование

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Исторический контекст и цель SourceSets

SourceSets (исходные наборы или наборы исходного кода) — это фундаментальная концепция в системе сборки Gradle, созданная для организации исходного кода, ресурсов и зависимостей в зависимости от варианта сборки (build variant). Они появились как ответ на усложнение структуры Android-проектов, где необходимо поддерживать различные версии приложения (debug/release, разные версии API, flavors) из одного кода.

Основная цель — предоставить четкое и гибкое разделение кода и ресурсов для разных целей сборки, избегая дублирования и ручного управления файлами.

Основные варианты SourceSets в Android-проекте

По умолчанию Android Plugin for Gradle создаёт следующие стандартные SourceSets:

  • main — Ядро приложения. Содержит код и ресурсы, общие для всех вариантов сборки.
  • androidTest — Код для инструментальных тестов (Instrumentation tests), которые запускаются на устройстве/эмуляторе.
  • test — Код для модульных тестов (Unit tests), которые запускаются на JVM хоста.

При определении productFlavors (например, free, paid) и buildTypes (например, debug, release) автоматически генерируются новые SourceSets, формируя иерархию.

Иерархия слияния (Merge Hierarchy)

Это ключевой принцип работы. При сборке конкретного варианта (например, freeDebug) Gradle объединяет (мержит) файлы из нескольких SourceSets в строгом порядке приоритета. Чем ниже SourceSet в списке, тем выше приоритет его файлов при конфликтах.

Порядок для freeDebug (от низшего к высшему приоритету):

  1. src/main/ — Базовые файлы.
  2. src/free/ (flavor) — Файлы, специфичные для flavor free.
  3. src/debug/ (build type) — Файлы, специфичные для типа сборки debug.
  4. src/freeDebug/ (variant) — Файлы, специфичные именно для этого варианта.
// Пример структуры папок в проекте:
src/
├── main/               # Общий код для всех вариантов
│   ├── java/com/example/
│   ├── res/
│   └── AndroidManifest.xml
├── free/               # Специфично для flavor 'free'
│   ├── java/com/example/FreeFeature.kt
│   ├── res/values/strings.xml (переопределяет строки из main)
│   └── AndroidManifest.xml (может добавлять компоненты)
├── debug/              # Специфично для типа сборки 'debug'
│   └── java/com/example/DebugUtils.kt
└── freeDebug/          # Специфично ТОЛЬКО для варианта freeDebug
    └── java/com/example/VariantSpecificConfig.kt

Для чего нужны SourceSets? Практические применения

1. Разделение кода для разных типов сборки (Debug/Release)

Позволяют изолировать код для отладки, логирования, или инструментов разработчика, который не должен попадать в релиз.

// src/debug/java/com/example/AppInitializer.kt
class AppInitializer {
    fun init(app: Application) {
        // Инициализация только для отладки: Stetho, Timber debug tree, и т.д.
        Timber.plant(Timber.DebugTree())
        Stetho.initializeWithDefaults(app)
    }
}

2. Создание разных версий приложения (Product Flavors)

Позволяют разрабатывать несколько приложений (например, бесплатную и платную версии) в одном проекте с минимальным дублированием.

// build.gradle.kts (Module)
android {
    flavorDimensions += "version"
    productFlavors {
        create("free") {
            dimension = "version"
            applicationIdSuffix = ".free"
        }
        create("paid") {
            dimension = "version"
            applicationIdSuffix = ".paid"
        }
    }
}

// src/paid/java/com/example/PaymentService.kt - класс существует только в платной версии
class PaymentService { ... }

3. Изменение ресурсов и манифеста для разных вариантов

Можно переопределять строки, цвета, макеты, добавлять или изменять элементы AndroidManifest.xml для конкретных вариантов.

<!-- src/free/res/values/strings.xml -->
<resources>
    <string name="app_name">My App (Free)</string>
    <string name="upgrade_label">Upgrade to PRO!</string>
</resources>

<!-- src/paid/res/values/strings.xml -->
<resources>
    <string name="app_name">My App (PRO)</string>
    <string name="upgrade_label">You are a PRO user!</string>
</resources>

4. Разделение тестов

Чёткое отделение unit-тестов (test) от instrumentation- тестов (androidTest) позволяет Gradle и IDE правильно их идентифицировать и запускать.

// Пример разделения:
src/
├── main/java/com/example/Calculator.kt
├── test/java/com/example/CalculatorTest.kt        # Unit-тесты (JVM)
└── androidTest/java/com/example/CalculatorUiTest.kt # UI-тесты (Android)

5. Подключение разных зависимостей

Можно указывать библиотеки только для определённых вариантов сборки через конфигурации implementation, debugImplementation, freeImplementation и т.д.

// build.gradle.kts
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0") // Для всех
    debugImplementation("com.facebook.stetho:stetho:1.6.0") // Только для отладки
    freeImplementation("com.google.android.gms:play-services-ads:22.6.0") // Только для free flavor
}

Заключение

SourceSets — это мощный механизм абстракции, который позволяет:

  • Структурировать проект согласно логике бизнеса (flavors) и этапам разработки (build types).
  • Управлять кодом, ресурсами и манифестами через понятное наследование и переопределение.
  • Изолировать код, который не должен попадать в определённые версии приложения (например, debug:tools в release).
  • Автоматизировать сборку множества конечных APK/AAB из одной кодобазы.

Без SourceSets разработчикам пришлось бы вручную управлять копиями файлов или писать сложные скрипты для условной компиляции, что крайне error-prone. SourceSets делают этот процесс декларативным, стандартизированным и интегрированным в IDE.