Настройка вариантов сборки

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

Каждый вариант сборки представляет собой отдельную версию приложения, которую вы можете создать. Например, вы можете создать бесплатную версию приложения с ограниченным набором контента и платную версию с расширенным набором. Вы также можете создать разные версии приложения для разных устройств в зависимости от уровня API или других характеристик.

Варианты сборки – это результат применения Gradle определенного набора правил для объединения настроек, кода и ресурсов, заданных в типах сборки и версиях продукта. Хотя вы не настраиваете варианты сборки напрямую, вы настраиваете типы сборки и версии продукта, которые их формируют.

Например, версия продукта demo может иметь определенные функции и требования к устройству, такие как собственный исходный код, ресурсы и минимальные уровни API, а тип сборки debug – другие настройки сборки и упаковки, например параметры отладки и ключи подписи. Вариант сборки, который объединяет эти два элемента, – это версия demoDebug вашего приложения. Она включает комбинацию конфигураций и ресурсов, входящих в версию продукта demo, тип сборки debug и набор исходных файлов main/.

Как настроить типы сборки

Вы можете создавать и настраивать типы сборки в блоке android файла build.gradle.kts на уровне модуля. При создании нового модуля Android Studio автоматически создает типы сборки для отладки и выпуска. Хотя тип отладочной сборки не указан в файле конфигурации сборки, Android Studio настраивает его с помощью debuggable true. Это позволит вам отлаживать приложение на защищенных устройствах Android и настроить подпись приложения с помощью общего хранилища ключей отладки.

Если вы хотите добавить или изменить определенные настройки, добавьте в конфигурацию тип отладочной сборки. В следующем примере задается applicationIdSuffix для отладочной сборки и настраивается промежуточная сборка, которая инициализируется с использованием настроек отладочной сборки:

Kotlin

android {
    defaultConfig {
        manifestPlaceholders["hostName"] = "www.example.com"
        ...
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
        }

        getByName("debug") {
            applicationIdSuffix = ".debug"
            isDebuggable = true
        }

        /**
         * The `initWith` property lets you copy configurations from other build types,
         * then configure only the settings you want to change. This one copies the debug build
         * type, and then changes the manifest placeholder and application ID.
         */
        create("staging") {
            initWith(getByName("debug"))
            manifestPlaceholders["hostName"] = "internal.example.com"
            applicationIdSuffix = ".debugStaging"
        }
    }
}

Яркий

android {
    defaultConfig {
        manifestPlaceholders = [hostName:"www.example.com"]
        ...
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug {
            applicationIdSuffix ".debug"
            debuggable true
        }

        /**
         * The `initWith` property lets you copy configurations from other build types,
         * then configure only the settings you want to change. This one copies the debug build
         * type, and then changes the manifest placeholder and application ID.
         */
        staging {
            initWith debug
            manifestPlaceholders = [hostName:"internal.example.com"]
            applicationIdSuffix ".debugStaging"
        }
    }
}

Примечание. После изменения файла конфигурации сборки Android Studio потребует синхронизировать проект с новой конфигурацией. Чтобы синхронизировать проект, нажмите Sync Now (Синхронизировать) на панели уведомлений, которая появляется при внесении изменений, или нажмите Sync Project (Синхронизировать проект) на панели инструментов. Если Android Studio обнаружит ошибки в конфигурации, появится окно Messages (Сообщения) с описанием проблемы.

Чтобы узнать больше о свойствах, которые можно настроить с помощью типов сборки, ознакомьтесь со справочной статьей BuildType.

Как настроить версии продукта

Версии продукта создаются так же, как и типы сборки. Добавьте версии продукта в блок productFlavors в конфигурации сборки и укажите нужные настройки. Версии продукта поддерживают те же свойства, что и defaultConfig, поскольку defaultConfig относится к классу ProductFlavor. Это означает, что вы можете указать базовую конфигурацию для всех вариантов в блоке defaultConfig, а каждый вариант может изменить любое из этих значений по умолчанию, например applicationId. Подробнее о том, как задать идентификатор приложения…

Примечание. Вам по-прежнему нужно указать название пакета с помощью атрибута package в файле манифеста main/. Это же название пакета нужно использовать в исходном коде для ссылки на класс R или для разрешения регистрации относительных действий или сервисов. Это позволяет использовать applicationId, чтобы присвоить каждой версии продукта уникальный идентификатор для упаковки и распространения, не меняя исходный код.

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

  Error: All flavors must now belong to a named flavor dimension.
  The flavor 'flavor_name' is not assigned to a flavor dimension.

Если в модуле указана только одна группа вариантов, плагин Android Gradle автоматически назначает все варианты модуля этой группе.

В следующем примере кода создается группа вариантов под названием version и добавляются версии продукта demo и full. У этих версий есть собственные applicationIdSuffix и versionNameSuffix:

Kotlin

android {
    ...
    defaultConfig {...}
    buildTypes {
        getByName("debug"){...}
        getByName("release"){...}
    }
    // Specifies one flavor dimension.
    flavorDimensions += "version"
    productFlavors {
        create("demo") {
            // Assigns this product flavor to the "version" flavor dimension.
            // If you are using only one dimension, this property is optional,
            // and the plugin automatically assigns all the module's flavors to
            // that dimension.
            dimension = "version"
            applicationIdSuffix = ".demo"
            versionNameSuffix = "-demo"
        }
        create("full") {
            dimension = "version"
            applicationIdSuffix = ".full"
            versionNameSuffix = "-full"
        }
    }
}

Яркий

android {
    ...
    defaultConfig {...}
    buildTypes {
        debug{...}
        release{...}
    }
    // Specifies one flavor dimension.
    flavorDimensions "version"
    productFlavors {
        demo {
            // Assigns this product flavor to the "version" flavor dimension.
            // If you are using only one dimension, this property is optional,
            // and the plugin automatically assigns all the module's flavors to
            // that dimension.
            dimension "version"
            applicationIdSuffix ".demo"
            versionNameSuffix "-demo"
        }
        full {
            dimension "version"
            applicationIdSuffix ".full"
            versionNameSuffix "-full"
        }
    }
}

Примечание. Если у вас есть устаревшее приложение (созданное до августа 2021 года), которое вы распространяете в Google Play с помощью APK-файлов, то для распространения приложения с поддержкой нескольких APK-файлов в Google Play присвойте одно и то же значение applicationId всем вариантам и укажите для каждого варианта разные значения versionCode. Чтобы распространять разные варианты приложения как отдельные приложения в Google Play, вам нужно назначить каждому варианту свое значение applicationId.

После создания и настройки версий продукта нажмите Синхронизировать на панели уведомлений. После завершения синхронизации Gradle автоматически создаст варианты сборки на основе типов сборки и версий продукта и присвоит им названия в соответствии с шаблоном <product-flavor><Build-Type>. Например, если вы создали версии продукта demo и full и оставили типы сборки debug и release, заданные по умолчанию, Gradle создаст следующие варианты сборки:

  • demoDebug
  • demoRelease
  • fullDebug
  • fullRelease

Чтобы выбрать вариант сборки, нажмите Build (Сборка) > Select Build Variant (Выбрать вариант сборки) и выберите нужный вариант в меню. Чтобы начать настройку каждого варианта сборки с его собственными функциями и ресурсами, вам нужно создать наборы исходных файлов и управлять ими, как описано на этой странице.

Как изменить идентификатор приложения для вариантов сборки

Когда вы создаете APK-файл или набор App Bundle для приложения, инструменты сборки помечают приложение идентификатором, указанным в блоке defaultConfig файла build.gradle.kts, как показано в примере ниже. Однако если вы хотите создать разные версии приложения, которые будут представлены в Google Play как отдельные продукты (например, бесплатная и профессиональная версии), вам нужно создать отдельные варианты сборки, у каждого из которых будет свой идентификатор приложения.

В этом случае определите каждый вариант сборки как отдельную версию продукта. Для каждого варианта в блоке productFlavors можно переопределить свойство applicationId или добавить сегмент к идентификатору приложения по умолчанию с помощью applicationIdSuffix, как показано ниже.

Kotlin

android {
    defaultConfig {
        applicationId = "com.example.myapp"
    }
    productFlavors {
        create("free") {
            applicationIdSuffix = ".free"
        }
        create("pro") {
            applicationIdSuffix = ".pro"
        }
    }
}

Яркий

android {
    defaultConfig {
        applicationId "com.example.myapp"
    }
    productFlavors {
        free {
            applicationIdSuffix ".free"
        }
        pro {
            applicationIdSuffix ".pro"
        }
    }
}

Таким образом, идентификатор приложения для бесплатной версии продукта будет выглядеть так: com.example.myapp.free.

Вы также можете использовать applicationIdSuffix, чтобы добавить сегмент на основе типа сборки, как показано ниже.

Kotlin

android {
    ...
    buildTypes {
        getByName("debug") {
            applicationIdSuffix = ".debug"
        }
    }
}

Яркий

android {
    ...
    buildTypes {
        debug {
            applicationIdSuffix ".debug"
        }
    }
}

Поскольку Gradle применяет конфигурацию типа сборки после версии продукта, идентификатор приложения для варианта сборки free debug – com.example.myapp.free.debug. Это удобно, если вы хотите установить на одно устройство и отладочную, и конечную сборку, поскольку у двух приложений не может быть одинакового идентификатора приложения.

Если у вас есть устаревшее приложение (созданное до августа 2021 года), которое вы распространяете в Google Play с помощью APK-файлов, и вы хотите использовать одну и ту же страницу приложения для распространения нескольких APK-файлов, каждый из которых предназначен для определенной конфигурации устройства, например уровня API, то вы должны использовать один и тот же идентификатор приложения для каждого варианта сборки, но присвоить каждому APK-файлу свой versionCode. Подробнее о поддержке нескольких APK-файлов… Публикация с помощью наборов App Bundle не затронута, поскольку в этом случае по умолчанию используется один артефакт с одним кодом версии и идентификатором приложения.

Совет. Если вам нужно сослаться на идентификатор приложения в файле манифеста, вы можете использовать плейсхолдер ${applicationId} в любом атрибуте манифеста. Во время сборки Gradle заменяет этот тег фактическим идентификатором приложения. Подробнее о том, как добавить переменные сборки в манифест…

Как объединить несколько версий продукта с помощью групп вариантов

В некоторых случаях может потребоваться объединить конфигурации из нескольких версий продукта. Например, вы можете создать разные конфигурации для полной и демоверсии продукта на основе уровня API. Для этого плагин Android Gradle позволяет создавать несколько групп вариантов продукта в виде групп вариантов.

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

В примере кода ниже используется свойство flavorDimensions, чтобы создать группу вариантов mode для объединения версий full и demo, а также группу вариантов api для объединения конфигураций версий на основе уровня API.

Kotlin

android {
  ...
  buildTypes {
    getByName("debug") {...}
    getByName("release") {...}
  }

  // Specifies the flavor dimensions you want to use. The order in which you
  // list the dimensions determines their priority, from highest to lowest,
  // when Gradle merges variant sources and configurations. You must assign
  // each product flavor you configure to one of the flavor dimensions.
  flavorDimensions += listOf("api", "mode")

  productFlavors {
    create("demo") {
      // Assigns this product flavor to the "mode" flavor dimension.
      dimension = "mode"
      ...
    }

    create("full") {
      dimension = "mode"
      ...
    }

    // Configurations in the "api" product flavors override those in "mode"
    // flavors and the defaultConfig block. Gradle determines the priority
    // between flavor dimensions based on the order in which they appear next
    // to the flavorDimensions property, with the first dimension having a higher
    // priority than the second, and so on.
    create("minApi24") {
      dimension = "api"
      minSdk = 24
      // To ensure the target device receives the version of the app with
      // the highest compatible API level, assign version codes in increasing
      // value with API level.
      versionCode = 30000 + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi24"
      ...
    }

    create("minApi23") {
      dimension = "api"
      minSdk = 23
      versionCode = 20000  + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi23"
      ...
    }

    create("minApi21") {
      dimension = "api"
      minSdk = 21
      versionCode = 10000  + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi21"
      ...
    }
  }
}
...

Яркий

android {
  ...
  buildTypes {
    debug {...}
    release {...}
  }

  // Specifies the flavor dimensions you want to use. The order in which you
  // list the dimensions determines their priority, from highest to lowest,
  // when Gradle merges variant sources and configurations. You must assign
  // each product flavor you configure to one of the flavor dimensions.
  flavorDimensions "api", "mode"

  productFlavors {
    demo {
      // Assigns this product flavor to the "mode" flavor dimension.
      dimension "mode"
      ...
    }

    full {
      dimension "mode"
      ...
    }

    // Configurations in the "api" product flavors override those in "mode"
    // flavors and the defaultConfig block. Gradle determines the priority
    // between flavor dimensions based on the order in which they appear next
    // to the flavorDimensions property, with the first dimension having a higher
    // priority than the second, and so on.
    minApi24 {
      dimension "api"
      minSdkVersion 24
      // To ensure the target device receives the version of the app with
      // the highest compatible API level, assign version codes in increasing
      // value with API level.

      versionCode 30000 + android.defaultConfig.versionCode
      versionNameSuffix "-minApi24"
      ...
    }

    minApi23 {
      dimension "api"
      minSdkVersion 23
      versionCode 20000  + android.defaultConfig.versionCode
      versionNameSuffix "-minApi23"
      ...
    }

    minApi21 {
      dimension "api"
      minSdkVersion 21
      versionCode 10000  + android.defaultConfig.versionCode
      versionNameSuffix "-minApi21"
      ...
    }
  }
}
...

Количество вариантов сборки, создаваемых Gradle, равно произведению количества вариантов в каждой группе вариантов и количества настроенных вами типов сборки. Когда Gradle присваивает названия вариантам сборки или соответствующим артефактам, версии продукта, относящиеся к группе вариантов с более высоким приоритетом, указываются первыми, за ними следуют версии из групп с более низким приоритетом, а затем – тип сборки.

Используя предыдущую конфигурацию сборки в качестве примера, Gradle создает в общей сложности 12 вариантов сборки со следующей схемой именования:

  • Вариант сборки: [minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
  • Соответствующий APK-файл: app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
  • Например,
    Вариант сборки: minApi24DemoDebug
    Соответствующий APK-файл: app-minApi24-demo-debug.apk

Помимо каталогов наборов исходных файлов, которые можно создать для каждой отдельной версии продукта и варианта сборки, вы также можете создать каталоги наборов исходных файлов для каждой комбинации версий продукта. Например, вы можете создать и добавить исходные файлы Java в каталог src/demoMinApi24/java/, и Gradle будет использовать их только при сборке варианта, который объединяет эти две версии продукта.

Наборы исходных файлов, созданные для комбинаций версий продукта, имеют более высокий приоритет, чем наборы исходных файлов, принадлежащие каждой отдельной версии продукта. Подробнее о наборах исходных файлов и о том, как Gradle объединяет ресурсы, рассказывается в разделе Как создавать наборы исходных файлов.

Как фильтровать варианты

Gradle создает вариант сборки для каждой возможной комбинации версий продукта и типов сборки, которые вы настраиваете. Однако некоторые варианты сборки могут быть не нужны или не подходить для вашего проекта. Чтобы удалить определенные конфигурации вариантов сборки, создайте фильтр вариантов в файле build.gradle.kts на уровне модуля.

Используя конфигурацию сборки из предыдущего раздела в качестве примера, предположим, что вы планируете поддерживать только уровни API 23 и выше для демоверсии приложения. Вы можете использовать блок variantFilter, чтобы отфильтровать все конфигурации вариантов сборки, которые объединяют версии продукта minApi21 и demo:

Kotlin

android {
  ...
  buildTypes {...}

  flavorDimensions += listOf("api", "mode")
  productFlavors {
    create("demo") {...}
    create("full") {...}
    create("minApi24") {...}
    create("minApi23") {...}
    create("minApi21") {...}
  }
}

androidComponents {
    beforeVariants { variantBuilder ->
        // To check for a certain build type, use variantBuilder.buildType == "<buildType>"
        if (variantBuilder.productFlavors.containsAll(listOf("api" to "minApi21", "mode" to "demo"))) {
            // Gradle ignores any variants that satisfy the conditions above.
            variantBuilder.enable = false
        }
    }
}
...

Яркий

android {
  ...
  buildTypes {...}

  flavorDimensions "api", "mode"
  productFlavors {
    demo {...}
    full {...}
    minApi24 {...}
    minApi23 {...}
    minApi21 {...}
  }

  variantFilter { variant ->
      def names = variant.flavors*.name
      // To check for a certain build type, use variant.buildType.name == "<buildType>"
      if (names.contains("minApi21") && names.contains("demo")) {
          // Gradle ignores any variants that satisfy the conditions above.
          setIgnore(true)
      }
  }
}
...

После того как вы добавите фильтр вариантов в конфигурацию сборки и нажмете Sync Now (Синхронизировать) на панели уведомлений, Gradle будет игнорировать все варианты сборки, соответствующие указанным условиям. Варианты сборки больше не будут показываться в меню, если вы нажмете Build (Сборка) > Select Build Variant (Выбрать вариант сборки) в строке меню или Build Variants (Варианты сборки) на панели окон инструментов.

Как создать наборы исходных файлов

По умолчанию Android Studio создает main/ набор исходных файлов и каталоги для всего, что вы хотите использовать во всех вариантах сборки. Однако вы можете создавать новые наборы исходных файлов, чтобы точно контролировать, какие файлы Gradle компилирует и упаковывает для определенных типов сборки, версий продукта, комбинаций версий продукта (при использовании групп вариантов) и вариантов сборки.

Например, вы можете определить базовую функциональность в наборе исходных файлов main/ и использовать наборы исходных файлов версий продукта, чтобы изменить брендинг приложения для разных клиентов или включить специальные разрешения и функции ведения журнала только для вариантов сборки, использующих тип отладочной сборки.

Gradle ожидает, что файлы и каталоги набора исходных файлов будут организованы определенным образом, аналогично набору исходных файлов main/. Например, Gradle ожидает, что файлы классов Kotlin или Java, относящиеся к типу сборки debug, будут находиться в каталогах src/debug/kotlin/ или src/debug/java/.

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

------------------------------------------------------------
Project :app
------------------------------------------------------------

...

debug
----
Compile configuration: debugCompile
build.gradle name: android.sourceSets.debug
Java sources: [app/src/debug/java]
Kotlin sources: [app/src/debug/kotlin, app/src/debug/java]
Manifest file: app/src/debug/AndroidManifest.xml
Android resources: [app/src/debug/res]
Assets: [app/src/debug/assets]
AIDL sources: [app/src/debug/aidl]
RenderScript sources: [app/src/debug/rs]
JNI sources: [app/src/debug/jni]
JNI libraries: [app/src/debug/jniLibs]
Java-style resources: [app/src/debug/resources]

Чтобы посмотреть эти выходные данные, выполните следующие действия:

  1. На панели окон инструментов нажмите Gradle.
  2. Перейдите в раздел MyApplication > Tasks > android и дважды нажмите sourceSets.

    Чтобы увидеть папку Tasks, необходимо разрешить Gradle создавать список задач во время синхронизации. Для этого выполните следующие действия:

    1. Нажмите File (Файл) > Settings (Настройки) > Experimental (Экспериментальные) (Android Studio > Settings (Настройки) > Experimental (Экспериментальные) в macOS).
    2. Снимите флажок Do not build Gradle task list during Gradle sync (Не создавать список задач Gradle во время синхронизации Gradle).
  3. После выполнения задачи Gradle откроется окно Run (Запуск) с выходными данными.

Примечание. В выходных данных задачи также показано, как организовать наборы исходных файлов, которые вы хотите использовать для тестирования приложения, например наборы исходных файлов для тестирования test/ и androidTest/.

При создании нового варианта сборки Android Studio не создает каталоги наборов исходных файлов, но предлагает несколько вариантов, которые могут вам помочь. Например, чтобы создать каталог java/ только для типа сборки debug, выполните следующую команду:

  1. Откройте панель Project (Проект) и выберите режим просмотра Project (Проект) в меню в верхней части панели.
  2. Перейдите в приложение "MyProject/app/src/".
  3. Нажмите правой кнопкой мыши на каталог src и выберите New (Создать) > Directory (Каталог).
  4. В меню Gradle Source Sets (Наборы исходных файлов Gradle) выберите full/java.
  5. Нажмите Ввод.

Android Studio создаст каталог набора исходных файлов для отладочной сборки, а затем в нем – каталог java/. Кроме того, Android Studio может создать каталоги автоматически, когда вы добавляете в проект новый файл для определенного варианта сборки.

Например, чтобы создать XML-файл значений для типа сборки debug, выполните следующие действия:

  1. На панели Project (Проект) нажмите правой кнопкой мыши на каталог src и выберите New (Создать) > XML > Values XML File (XML-файл значений).
  2. Введите название XML-файла или оставьте название по умолчанию.
  3. В меню рядом с пунктом Target Source Set (Целевой набор исходных файлов) выберите debug (отладка).
  4. Нажмите Готово.

Поскольку в качестве целевого набора исходных файлов был указан тип сборки debug, Android Studio автоматически создает необходимые каталоги при создании XML-файла. Полученная структура каталогов показана на рисунке 1.

Рисунок 1. Новые каталоги наборов исходных файлов для типа сборки debug.

Активные наборы исходных файлов отмечены зеленым индикатором на значке. К набору исходных файлов debug добавляется суффикс [main], чтобы показать, что он будет объединен с набором исходных файлов main.

По такому же принципу можно создать каталоги наборов исходных файлов для версий продукта, например src/demo/, и вариантов сборки, например src/demoDebug/. Кроме того, вы можете создать наборы исходных файлов для тестирования, предназначенные для определенных вариантов сборки, например src/androidTestDemoDebug/. Подробнее о наборах исходных файлов для тестирования…

Как изменить конфигурации набора исходных файлов по умолчанию

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

Блок sourceSets должен находиться внутри блока android. Вам не нужно перемещать исходные файлы. Достаточно указать в Gradle пути к ним относительно файла build.gradle.kts на уровне модуля. Чтобы узнать, какие компоненты можно настроить и можно ли сопоставить их с несколькими файлами или каталогами, ознакомьтесь с документацией по API плагина Android Gradle.

В примере кода ниже источники из каталога app/other/ сопоставляются с определенными компонентами набора исходных файлов main, а корневой каталог набора исходных файлов androidTest изменяется.

Kotlin

android {
  ...
  // Encapsulates configurations for the main source set.
  sourceSets.getByName("main") {
    // Changes the directory for Java sources. The default directory is
    // 'src/main/java'.
    java.setSrcDirs(listOf("other/java"))

    // If you list multiple directories, Gradle uses all of them to collect
    // sources. Because Gradle gives these directories equal priority, if
    // you define the same resource in more than one directory, you receive an
    // error when merging resources. The default directory is 'src/main/res'.
    res.setSrcDirs(listOf("other/res1", "other/res2"))

    // Note: Avoid specifying a directory that is a parent to one
    // or more other directories you specify. For example, avoid the following:
    // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
    // Specify either only the root 'other/res1' directory or only the
    // nested 'other/res1/layouts' and 'other/res1/strings' directories.

    // For each source set, you can specify only one Android manifest.
    // By default, Android Studio creates a manifest for your main source
    // set in the src/main/ directory.
    manifest.srcFile("other/AndroidManifest.xml")
    ...
  }

  // Create additional blocks to configure other source sets.
  sourceSets.getByName("androidTest") {
      // If all the files for a source set are located under a single root
      // directory, you can specify that directory using the setRoot property.
      // When gathering sources for the source set, Gradle looks only in locations
      // relative to the root directory you specify. For example, after applying the
      // configuration below for the androidTest source set, Gradle looks for Java
      // sources only in the src/tests/java/ directory.
      setRoot("src/tests")
      ...
  }
}
...

Яркий

android {
  ...
  sourceSets {
    // Encapsulates configurations for the main source set.
    main {
      // Changes the directory for Java sources. The default directory is
      // 'src/main/java'.
      java.srcDirs = ['other/java']

      // If you list multiple directories, Gradle uses all of them to collect
      // sources. Because Gradle gives these directories equal priority, if
      // you define the same resource in more than one directory, you receive an
      // error when merging resources. The default directory is 'src/main/res'.
      res.srcDirs = ['other/res1', 'other/res2']

      // Note: Avoid specifying a directory that is a parent to one
      // or more other directories you specify. For example, avoid the following:
      // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
      // Specify either only the root 'other/res1' directory or only the
      // nested 'other/res1/layouts' and 'other/res1/strings' directories.

      // For each source set, you can specify only one Android manifest.
      // By default, Android Studio creates a manifest for your main source
      // set in the src/main/ directory.
      manifest.srcFile 'other/AndroidManifest.xml'
      ...
    }

    // Create additional blocks to configure other source sets.
    androidTest {

      // If all the files for a source set are located under a single root
      // directory, you can specify that directory using the setRoot property.
      // When gathering sources for the source set, Gradle looks only in locations
      // relative to the root directory you specify. For example, after applying the
      // configuration below for the androidTest source set, Gradle looks for Java
      // sources only in the src/tests/java/ directory.
      setRoot 'src/tests'
      ...
    }
  }
}
...

Обратите внимание, что каталог с исходными файлами может принадлежать только к одному набору исходных файлов. Например, нельзя использовать одни и те же тестовые исходные файлы в наборах test и androidTest. Это связано с тем, что Android Studio создает отдельные модули IntelliJ для каждого набора исходных файлов и не поддерживает дублирование корневых каталогов контента в наборах исходных файлов.

Как создавать сборки с наборами исходных файлов

В каталогах наборов исходных файлов можно хранить код и ресурсы, которые должны быть упакованы только с определенными конфигурациями. Например, если вы создаете вариант сборки demoDebug, который является кросс-продуктом версии продукта demo и типа сборки debug, Gradle просматривает эти каталоги и присваивает им следующий приоритет:

  1. src/demoDebug/ (набор исходных файлов варианта сборки)
  2. src/debug/ (набор исходных файлов для типа сборки)
  3. src/demo/ (набор исходных файлов версии продукта)
  4. src/main/ (основной набор исходных файлов)

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

Если вы объединяете несколько версий продукта, приоритет между ними определяется группой вариантов, к которой они относятся. При перечислении групп вариантов с помощью свойства android.flavorDimensions приоритет отдается вариантам в том порядке, в котором объявлены их группы. Группы, указанные раньше, имеют приоритет над последующими. Кроме того, наборы исходных файлов, созданные для комбинаций версий продукта, имеют более высокий приоритет, чем наборы исходных файлов, принадлежащие отдельной версии продукта.

Порядок приоритета определяет, какой набор исходных файлов имеет более высокий приоритет, когда Gradle объединяет код и ресурсы. Поскольку каталог набора исходных файлов demoDebug/, скорее всего, содержит файлы, относящиеся к этому варианту сборки, если demoDebug/ включает файл, который также определен в debug/, Gradle использует файл в наборе исходных файлов demoDebug/. Аналогично, Gradle присваивает файлам в наборах исходных файлов для типа сборки и версии продукта более высокий приоритет, чем тем же файлам в main/. Gradle учитывает этот порядок приоритета при применении следующих правил сборки:

  • Весь исходный код в каталогах kotlin/ или java/ компилируется вместе для создания одного выходного файла.

    Примечание. Если в варианте сборки Gradle обнаружит два или более каталогов наборов исходных файлов, в которых определен один и тот же класс Kotlin или Java, то возникнет ошибка сборки. Например, при создании отладочного приложения нельзя одновременно определить src/debug/Utility.kt и src/main/Utility.kt, поскольку Gradle просматривает оба этих каталога в процессе сборки и выдает ошибку "дублирующийся класс". Если вам нужны разные версии файла Utility.kt для разных типов сборки, то для каждого типа сборки необходимо определить свою версию файла и не включать ее в набор исходных файлов main/.

  • Манифесты объединяются в один. Приоритет отдается в том же порядке, что и в списке в предыдущем примере. Например, настройки манифеста для типа сборки переопределяют настройки манифеста для версии продукта. Подробнее об объединении манифестов…
  • Файлы в каталогах values/ объединяются. Если у двух файлов одинаковое название, например strings.xml, приоритет определяется в том же порядке, что и в списке из предыдущего примера. Например, значения, определенные в файле в наборе исходных файлов типа сборки, переопределяют значения, определенные в том же файле в версии продукта.
  • Ресурсы в каталогах res/ и asset/ упаковываются вместе. Если в двух или более наборах исходных файлов определены ресурсы с одинаковым названием, приоритет отдается в том же порядке, что и в списке в предыдущем примере.
  • При сборке приложения Gradle присваивает ресурсам и манифестам, включенным в зависимости модулей библиотек, самый низкий приоритет.

Объявление зависимостей

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

Kotlin

dependencies {
    // Adds the local "mylibrary" module as a dependency to the "free" flavor.
    "freeImplementation"(project(":mylibrary"))

    // Adds a remote binary dependency only for local tests.
    testImplementation("junit:junit:4.12")

    // Adds a remote binary dependency only for the instrumented test APK.
    androidTestImplementation("com.android.support.test.espresso:espresso-core:3.6.1")
}

Яркий

dependencies {
    // Adds the local "mylibrary" module as a dependency to the "free" flavor.
    freeImplementation project(":mylibrary")

    // Adds a remote binary dependency only for local tests.
    testImplementation 'junit:junit:4.12'

    // Adds a remote binary dependency only for the instrumented test APK.
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.6.1'
}

Подробнее о том, как добавить зависимости сборки…

Используйте управление зависимостями с учетом вариантов

В плагине Android Gradle версии 3.0.0 и выше есть новый механизм зависимостей, который автоматически сопоставляет варианты при использовании библиотеки. Например, вариант debug приложения автоматически использует вариант debug библиотеки. Это также работает при использовании разновидностей: вариант freeDebug приложения будет использовать вариант freeDebug библиотеки.

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

Например, в вашем приложении настроен тип сборки staging, но в одной из зависимостей библиотеки его нет. Когда плагин попытается создать тестовую версию приложения, он не сможет определить, какую версию библиотеки использовать, и вы увидите сообщение об ошибке, похожее на следующее:

Error:Failed to resolve: Could not resolve project :mylibrary.
Required by:
    project :app

Как устранить ошибки сборки, связанные с сопоставлением вариантов

Плагин включает элементы DSL, которые помогают управлять тем, как Gradle разрешает ситуации, в которых прямое совпадение вариантов между приложением и зависимостью невозможно.

Ниже приведен список проблем, связанных с сопоставлением зависимостей с учетом вариантов, и способы их решения с помощью свойств DSL.

  • В приложении используется тип сборки, которого нет в зависимости библиотеки.

    Например, в вашем приложении есть тип сборки "staging", а в зависимости – только "debug" и "release".

    Обратите внимание, что если зависимость библиотеки включает тип сборки, которого нет в вашем приложении, то проблем не возникает. Это связано с тем, что плагин никогда не запрашивает этот тип сборки из зависимости.

    Используйте matchingFallbacks, чтобы указать альтернативные совпадения для заданного типа сборки, как показано ниже:

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        buildTypes {
            getByName("debug") {}
            getByName("release") {}
            create("staging") {
                // Specifies a sorted list of fallback build types that the
                // plugin can try to use when a dependency does not include a
                // "staging" build type. You may specify as many fallbacks as you
                // like, and the plugin selects the first build type that's
                // available in the dependency.
                matchingFallbacks += listOf("debug", "qa", "release")
            }
        }
    }

    Яркий

    // In the app's build.gradle file.
    android {
        buildTypes {
            debug {}
            release {}
            staging {
                // Specifies a sorted list of fallback build types that the
                // plugin can try to use when a dependency does not include a
                // "staging" build type. You may specify as many fallbacks as you
                // like, and the plugin selects the first build type that's
                // available in the dependency.
                matchingFallbacks = ['debug', 'qa', 'release']
            }
        }
    }
  • Для определенной группы вариантов, которая есть и в приложении, и в зависимой библиотеке, в приложении есть варианты, которых нет в библиотеке.

    Например, и в приложении, и в зависимостях его библиотек есть группа вариантов tier. Однако параметр tier в приложении включает варианты free и paid, а зависимость – только demo и paid для того же параметра.

    Обратите внимание, что если группа вариантов существует и в приложении, и в зависимостях его библиотек, то проблем не возникнет, даже если в библиотеке есть версия продукта, которой нет в приложении. Это связано с тем, что плагин никогда не запрашивает эту разновидность из зависимости.

    Используйте matchingFallbacks, чтобы указать альтернативные соответствия для бесплатной версии продукта, как показано ниже:

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        defaultConfig{
        // Don't configure matchingFallbacks in the defaultConfig block.
        // Instead, specify fallbacks for a given product flavor in the
        // productFlavors block, as shown below.
      }
        flavorDimensions += "tier"
        productFlavors {
            create("paid") {
                dimension = "tier"
                // Because the dependency already includes a "paid" flavor in its
                // "tier" dimension, you don't need to provide a list of fallbacks
                // for the "paid" flavor.
            }
            create("free") {
                dimension = "tier"
                // Specifies a sorted list of fallback flavors that the plugin
                // can try to use when a dependency's matching dimension does
                // not include a "free" flavor. Specify as many
                // fallbacks as you like; the plugin selects the first flavor
                // that's available in the dependency's "tier" dimension.
                matchingFallbacks += listOf("demo", "trial")
            }
        }
    }

    Яркий

    // In the app's build.gradle file.
    android {
        defaultConfig{
        // Don't configure matchingFallbacks in the defaultConfig block.
        // Instead, specify fallbacks for a given product flavor in the
        // productFlavors block, as shown below.
      }
        flavorDimensions 'tier'
        productFlavors {
            paid {
                dimension 'tier'
                // Because the dependency already includes a "paid" flavor in its
                // "tier" dimension, you don't need to provide a list of fallbacks
                // for the "paid" flavor.
            }
            free {
                dimension 'tier'
                // Specifies a sorted list of fallback flavors that the plugin
                // can try to use when a dependency's matching dimension does
                // not include a "free" flavor. Specify as many
                // fallbacks as you like; the plugin selects the first flavor
                // that's available in the dependency's "tier" dimension.
                matchingFallbacks = ['demo', 'trial']
            }
        }
    }
  • Зависимость библиотеки включает группу вариантов, которой нет в вашем приложении.

    Например, зависимость библиотеки включает разновидности для параметра minApi, а ваше приложение – только для параметра tier. Если вы хотите создать версию freeDebug, плагин не сможет определить, какую версию зависимости использовать – minApi23Debug или minApi18Debug.

    Обратите внимание, что если в приложении есть группа вариантов, которой нет в зависимости библиотеки, то проблем не возникнет. Это связано с тем, что плагин сопоставляет разновидности только тех параметров, которые существуют в зависимости. Например, если зависимость не включает параметр для ABI, то версия freeX86Debug вашего приложения будет использовать версию freeDebug зависимости.

    Используйте missingDimensionStrategy в блоке defaultConfig, чтобы указать вариант по умолчанию, который плагин должен выбирать для каждого отсутствующего параметра, как показано в примере ниже. Вы также можете переопределить выбранные варианты в блоке productFlavors, чтобы для каждого варианта можно было указать другую стратегию сопоставления для отсутствующего параметра.

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        defaultConfig{
        // Specifies a sorted list of flavors that the plugin can try to use from
        // a given dimension. This tells the plugin to select the "minApi18" flavor
        // when encountering a dependency that includes a "minApi" dimension.
        // You can include additional flavor names to provide a
        // sorted list of fallbacks for the dimension.
        missingDimensionStrategy("minApi", "minApi18", "minApi23")
        // Specify a missingDimensionStrategy property for each
        // dimension that exists in a local dependency but not in your app.
        missingDimensionStrategy("abi", "x86", "arm64")
        }
        flavorDimensions += "tier"
        productFlavors {
            create("free") {
                dimension = "tier"
                // You can override the default selection at the product flavor
                // level by configuring another missingDimensionStrategy property
                // for the "minApi" dimension.
                missingDimensionStrategy("minApi", "minApi23", "minApi18")
            }
            create("paid") {}
        }
    }

    Яркий

    // In the app's build.gradle file.
    android {
        defaultConfig{
        // Specifies a sorted list of flavors that the plugin can try to use from
        // a given dimension. This tells the plugin to select the "minApi18" flavor
        // when encountering a dependency that includes a "minApi" dimension.
        // You can include additional flavor names to provide a
        // sorted list of fallbacks for the dimension.
        missingDimensionStrategy 'minApi', 'minApi18', 'minApi23'
        // Specify a missingDimensionStrategy property for each
        // dimension that exists in a local dependency but not in your app.
        missingDimensionStrategy 'abi', 'x86', 'arm64'
        }
        flavorDimensions 'tier'
        productFlavors {
            free {
                dimension 'tier'
                // You can override the default selection at the product flavor
                // level by configuring another missingDimensionStrategy property
                // for the 'minApi' dimension.
                missingDimensionStrategy 'minApi', 'minApi23', 'minApi18'
            }
            paid {}
        }
    }

Дополнительную информацию можно найти в разделах matchingFallbacks и missingDimensionStrategy справочника по DSL плагина Android Gradle.

Настройте параметры подписи

Gradle не подписывает APK- или AAB-файл конечной сборки, если вы не задали для нее конфигурацию подписи. Если у вас ещё нет ключа подписи, создайте ключ загрузки и хранилище ключей в Android Studio.

Чтобы вручную задать конфигурации подписи для конечной сборки с помощью конфигураций сборки Gradle, выполните следующие действия:

  1. Создайте хранилище ключей. Хранилище ключей – это двоичный файл, содержащий набор закрытых ключей. Храните его в безопасном месте.
  2. Создайте закрытый ключ. Закрытый ключ используется для подписания приложения перед распространением. Он не включается в приложение и не передается третьим лицам.
  3. Добавьте конфигурацию подписи в файл build.gradle.kts на уровне модуля:

    Kotlin

    ...
    android {
        ...
        defaultConfig {...}
        signingConfigs {
            create("release") {
                storeFile = file("myreleasekey.keystore")
                storePassword = "password"
                keyAlias = "MyReleaseKey"
                keyPassword = "password"
            }
        }
        buildTypes {
            getByName("release") {
                ...
                signingConfig = signingConfigs.getByName("release")
            }
        }
    }

    Яркий

    ...
    android {
        ...
        defaultConfig {...}
        signingConfigs {
            release {
                storeFile file("myreleasekey.keystore")
                storePassword "password"
                keyAlias "MyReleaseKey"
                keyPassword "password"
            }
        }
        buildTypes {
            release {
                ...
                signingConfig signingConfigs.release
            }
        }
    }

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

Чтобы получить эти пароли из переменных среды:

Kotlin

storePassword = System.getenv("KSTOREPWD")
keyPassword = System.getenv("KEYPWD")

Яркий

storePassword System.getenv("KSTOREPWD")
keyPassword System.getenv("KEYPWD")

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

После этого вы сможете распространять свое приложение и опубликовать его в Google Play.

Внимание! Храните закрытый ключ и хранилище ключей в безопасном месте и создайте их резервные копии. Если вы используете функцию подписания приложений в Google Play и потеряли ключ загрузки, то можете запросить сброс в Play Console. Если вы публикуете приложение без функции подписания приложений в Google Play (для приложений, созданных до августа 2021 года) и потеряете ключ подписи приложения, то не сможете публиковать обновления, поскольку все версии приложения должны быть подписаны одним и тем же ключом.

Подписание приложений для Wear OS

При публикации приложений для Wear OS APK-файлы для часов и телефонов (необязательно) должны быть подписаны одним и тем же ключом. Подробнее о пакетах и распространении приложений для Wear OS…