Z tego artykułu dowiesz się, jak skonfigurować warianty kompilacji, aby tworzyć różne wersje aplikacji z jednego projektu, oraz jak prawidłowo zarządzać zależnościami i konfiguracjami podpisywania.
Każdy wariant kompilacji reprezentuje inną wersję aplikacji, którą możesz utworzyć. Możesz na przykład utworzyć jedną wersję aplikacji, która będzie bezpłatna i będzie zawierać ograniczony zestaw treści, oraz drugą, płatną wersję, która będzie zawierać więcej treści. Możesz też tworzyć różne wersje aplikacji przeznaczone na różne urządzenia, w zależności od poziomu interfejsu API lub innych cech urządzenia.
Warianty kompilacji są wynikiem użycia przez Gradle określonego zestawu reguł do łączenia ustawień, kodu i zasobów skonfigurowanych w typach kompilacji i wariantach usługi. Chociaż nie konfigurujesz bezpośrednio wariantów kompilacji, konfigurujesz typy kompilacji i warianty usługi, które je tworzą.
Na przykład wariant usługi „demo” może określać pewne funkcje i wymagania dotyczące urządzenia, takie jak niestandardowy kod źródłowy, zasoby i minimalne poziomy interfejsu API, podczas gdy typ kompilacji „debug” stosuje inne ustawienia kompilacji i pakowania, takie jak opcje debugowania i klucze podpisywania. Wariant kompilacji, który łączy te 2 elementy, to wersja „demoDebug” aplikacji. Zawiera ona kombinację konfiguracji i zasobów uwzględnionych w wariancie usługi „demo”, typie kompilacji „debug” i zbiorze źródeł main/.
Konfigurowanie typów kompilacji
Typy kompilacji możesz tworzyć i konfigurować w bloku android pliku build.gradle.kts na poziomie modułu. Gdy tworzysz nowy moduł, Android Studio automatycznie tworzy typy kompilacji do debugowania i publikacji. Chociaż typ kompilacji do debugowania nie pojawia się w pliku konfiguracji kompilacji, Android Studio konfiguruje go za pomocą debuggable
true. Umożliwia to debugowanie aplikacji na bezpiecznych urządzeniach z Androidem i konfiguruje podpisywanie aplikacji za pomocą ogólnego magazynu kluczy debugowania.
Jeśli chcesz dodać lub zmienić określone ustawienia, możesz dodać do konfiguracji typ kompilacji do debugowania. W tym przykładzie określono
applicationIdSuffix dla typu kompilacji do debugowania i skonfigurowano typ kompilacji „staging”, który jest inicjowany przy użyciu ustawień z typu kompilacji do debugowania:
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" } } }
Dynamiczny
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" } } }
Uwaga: gdy wprowadzisz zmiany w pliku konfiguracji kompilacji, Android Studio wymaga zsynchronizowania projektu z nową konfiguracją. Aby zsynchronizować projekt, kliknij Sync Now (Synchronizuj teraz) na pasku powiadomień, który pojawi się po wprowadzeniu zmiany, lub kliknij Sync Project (Synchronizuj projekt)
na pasku narzędzi. Jeśli Android Studio wykryje błędy w konfiguracji, pojawi się okno Messages (Wiadomości) z opisem problemu.
Więcej informacji o wszystkich właściwościach, które możesz konfigurować za pomocą typów kompilacji, znajdziesz w dokumentacji
BuildType.
Konfigurowanie wariantów usługi
Tworzenie wariantów usługi jest podobne do tworzenia typów kompilacji. Dodaj warianty usługi do bloku productFlavors w konfiguracji kompilacji i uwzględnij odpowiednie ustawienia.
Warianty usługi obsługują te same właściwości co defaultConfig, ponieważ defaultConfig należy do klasy
ProductFlavor. Oznacza to, że możesz podać konfigurację podstawową dla wszystkich wersji w bloku defaultConfig, a każda wersja może zmienić dowolną z tych wartości domyślnych, np. applicationId. Więcej informacji o identyfikatorze aplikacji znajdziesz w artykule Ustawianie identyfikatora aplikacji.
Uwaga: nadal musisz określić nazwę pakietu za pomocą atrybutu package w pliku manifestu main/. Musisz też użyć tej nazwy pakietu w kodzie źródłowym, aby odwołać się do klasy R lub rozwiązać problem z rejestracją powiązanej aktywności lub usługi. Dzięki temu możesz używać applicationId, aby nadać każdemu wariantowi usługi unikalny identyfikator na potrzeby pakowania i dystrybucji bez konieczności zmiany kodu źródłowego.
Wszystkie rodzaje muszą należeć do nazwanego wymiaru rodzajów, który jest grupą rodzajów usługi. Musisz przypisać wszystkie rodzaje do wymiaru rodzajów. W przeciwnym razie wystąpi ten błąd kompilacji.
Error: All flavors must now belong to a named flavor dimension. The flavor 'flavor_name' is not assigned to a flavor dimension.
Jeśli dany moduł określa tylko 1 wymiar rodzajów, wtyczka Androida do obsługi Gradle automatycznie przypisuje do niego wszystkie rodzaje modułu.
Poniższy przykładowy kod tworzy wymiar rodzajów o nazwie „version” i dodaje warianty usługi „demo” i „full”. Te wersje mają własne
applicationIdSuffix i 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" } } }
Dynamiczny
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" } } }
Uwaga: jeśli masz starszą aplikację (utworzoną przed sierpniem 2021 roku), którą rozpowszechniasz w Google Play za pomocą plików APK, i chcesz ją rozpowszechniać w Google Play za pomocą obsługi wielu plików APK, przypisz tę samą wartość applicationId do wszystkich wariantów i nadaj każdemu z nich inną wartość versionCode. Aby rozpowszechniać różne warianty aplikacji jako osobne aplikacje w Google Play, musisz przypisać do każdego wariantu inny identyfikator applicationId.
Po utworzeniu i skonfigurowaniu wariantów usługi kliknij Synchronizuj teraz na pasku powiadomień. Po zakończeniu synchronizacji Gradle automatycznie tworzy warianty kompilacji na podstawie typów kompilacji i wariantów usługi oraz nadaje im nazwy zgodnie z <product-flavor><Build-Type>. Jeśli na przykład utworzysz warianty usługi „demo” i „full” oraz zachowasz domyślne typy kompilacji „debug” i „release”, Gradle utworzy te warianty kompilacji:
-
demoDebug -
demoRelease -
fullDebug -
fullRelease
Aby wybrać wariant kompilacji, który chcesz skompilować i uruchomić, kliknij Build (Kompilacja) > Select Build Variant (Wybierz wariant kompilacji) i wybierz wariant kompilacji z menu. Aby zacząć dostosowywać każdy wariant kompilacji za pomocą jego własnych funkcji i zasobów, musisz utworzyć zbiory źródeł i nimi zarządzać zgodnie z opisem na tej stronie.
Zmienianie identyfikatora aplikacji w przypadku wariantów kompilacji
Gdy tworzysz plik APK lub AAB aplikacji, narzędzia do kompilacji oznaczają aplikację identyfikatorem aplikacji zdefiniowanym w bloku defaultConfig w pliku build.gradle.kts, jak w tym przykładzie. Jeśli jednak chcesz utworzyć różne wersje aplikacji, które mają się wyświetlać jako osobne strony w Sklepie Google Play, np. wersję „bezpłatną” i „pro”, musisz utworzyć osobne warianty kompilacji, z których każdy ma inny identyfikator aplikacji.
W takim przypadku zdefiniuj każdy wariant kompilacji jako osobny wariant usługi. W przypadku każdej wersji w bloku productFlavors możesz ponownie zdefiniować właściwość applicationId lub dołączyć segment do domyślnego identyfikatora aplikacji za pomocą applicationIdSuffix, jak pokazano tutaj:
Kotlin
android { defaultConfig { applicationId = "com.example.myapp" } productFlavors { create("free") { applicationIdSuffix = ".free" } create("pro") { applicationIdSuffix = ".pro" } } }
Dynamiczny
android { defaultConfig { applicationId "com.example.myapp" } productFlavors { free { applicationIdSuffix ".free" } pro { applicationIdSuffix ".pro" } } }
W ten sposób identyfikator aplikacji dla „bezpłatnego” wariantu usługi to „com.example.myapp.free”.
Możesz też użyć applicationIdSuffix, aby dołączyć segment na podstawie typu kompilacji, jak pokazano tutaj:
Kotlin
android { ... buildTypes { getByName("debug") { applicationIdSuffix = ".debug" } } }
Dynamiczny
android { ... buildTypes { debug { applicationIdSuffix ".debug" } } }
Gradle stosuje konfigurację typu kompilacji po wariancie usługi, więc identyfikator aplikacji w wariancie kompilacji „free debug” to „com.example.myapp.free.debug”. Jest to przydatne, gdy chcesz mieć na tym samym urządzeniu zarówno kompilację do debugowania, jak i kompilację do publikacji, ponieważ żadne 2 aplikacje nie mogą mieć tego samego identyfikatora aplikacji.
Jeśli masz starszą aplikację (utworzoną przed sierpniem 2021 r.), którą rozpowszechniasz w Google Play za pomocą plików APK, i chcesz używać tych samych informacji o aplikacji do rozpowszechniania wielu plików APK, z których każdy jest przeznaczony na inną konfigurację urządzenia, np. poziom interfejsu API, musisz użyć tego samego identyfikatora aplikacji w przypadku każdego wariantu kompilacji, ale nadać każdemu plikowi APK inny versionCode. Więcej informacji znajdziesz w artykule o obsłudze wielu plików APK. Nie ma to wpływu na publikowanie przy użyciu pakietów AAB, ponieważ domyślnie używają one jednego artefaktu z jednym kodem wersji i identyfikatorem aplikacji.
Wskazówka: jeśli musisz odwołać się do identyfikatora aplikacji w pliku manifestu, możesz użyć zmiennej ${applicationId} w dowolnym atrybucie manifestu. Podczas kompilacji Gradle zastępuje ten tag rzeczywistym identyfikatorem aplikacji. Więcej informacji znajdziesz w sekcji Wstrzykiwanie zmiennych kompilacji do pliku manifestu.
Łączenie wielu wariantów usługi z wymiarami rodzajów
W niektórych przypadkach możesz połączyć konfiguracje z wielu wariantów usługi. Możesz na przykład utworzyć różne konfiguracje dla „pełnej” i „demonstracyjnej” wersji usługi, które są oparte na poziomie interfejsu API. Aby to zrobić, wtyczka Androida do obsługi Gradle umożliwia utworzenie wielu grup wariantów usługi jako wymiarów rodzajów.
Podczas kompilowania aplikacji Gradle łączy konfigurację wariantu usługi z każdego zdefiniowanego wymiaru rodzajów z konfiguracją typu kompilacji, aby utworzyć ostateczny wariant kompilacji. Gradle nie łączy wariantów usługi, które należą do tego samego wymiaru rodzajów.
Ten przykładowy kod używa właściwości
flavorDimensions do utworzenia wymiaru rodzajów „mode” w celu grupowania wariantów usługi „full” i „demo” oraz wymiaru rodzajów „api” w celu grupowania konfiguracji wariantów usługi na podstawie poziomu interfejsu 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" ... } } } ...
Dynamiczny
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" ... } } } ...
Liczba wariantów kompilacji tworzonych przez Gradle jest równa iloczynowi liczby rodzajów w każdym wymiarze rodzajów i liczby skonfigurowanych typów kompilacji. Gdy Gradle nazywa poszczególne warianty kompilacji lub odpowiadające im artefakty, najpierw pojawiają się warianty usługi należące do wymiaru rodzajów o wyższym priorytecie, potem te z wymiarów o niższym priorytecie, a na końcu typ kompilacji.
Na przykładzie poprzedniej konfiguracji kompilacji Gradle tworzy łącznie 12 wariantów kompilacji o takim schemacie nazewnictwa:
- Wariant kompilacji:
[minApi24, minApi23, minApi21][Demo, Full][Debug, Release] - Odpowiedni pakiet APK:
app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk - Na przykład
- Wariant kompilacji:
minApi24DemoDebug - Odpowiadający mu plik APK:
app-minApi24-demo-debug.apk
Oprócz katalogów zbiorów źródeł, które możesz utworzyć dla poszczególnych wariantów usługi i wariantów kompilacji, możesz też utworzyć katalogi zbiorów źródeł dla każdej kombinacji wariantów usługi. Możesz na przykład utworzyć i dodać źródła Java do katalogu src/demoMinApi24/java/, a Gradle będzie ich używać tylko podczas kompilowania wariantu, który łączy te 2 warianty usługi.
Zbiory źródeł utworzone dla kombinacji wariantów usługi mają wyższy priorytet niż zbiory źródeł należące do poszczególnych wariantów usługi. Więcej informacji o zbiorach źródeł i o tym, jak Gradle scala zasoby, znajdziesz w sekcji o tworzeniu zbiorów źródeł.
Filtrowanie wariantów
Gradle tworzy wariant kompilacji dla każdej możliwej kombinacji skonfigurowanych przez Ciebie wariantów usługi i typów kompilacji. Mogą jednak istnieć pewne warianty kompilacji, których nie potrzebujesz lub które nie mają sensu w kontekście Twojego projektu. Aby usunąć niektóre konfiguracje wariantów kompilacji, utwórz filtr wariantów w pliku build.gradle.kts na poziomie modułu.
Załóżmy, że na podstawie konfiguracji kompilacji z poprzedniej sekcji planujesz obsługiwać tylko interfejsy API na poziomie 23 i wyższym w przypadku wersji demonstracyjnej aplikacji. Możesz użyć bloku
variantFilter, aby odfiltrować wszystkie konfiguracje wariantów kompilacji, które łączą warianty usługi „minApi21” i „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 } } } ...
Dynamiczny
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) } } } ...
Gdy dodasz filtr wariantów do konfiguracji kompilacji i klikniesz Synchronizuj teraz na pasku powiadomień, Gradle zignoruje wszystkie warianty kompilacji, które spełniają określone przez Ciebie warunki. Warianty kompilacji nie będą już widoczne w menu po kliknięciu Kompilacja > Wybierz wariant kompilacji na pasku menu lub Warianty kompilacji
na pasku okien narzędzi.
Tworzenie zbiorów źródeł
Domyślnie Android Studio tworzy main/
zbiór źródeł i katalogi dla wszystkich elementów, które chcesz udostępniać we wszystkich wariantach kompilacji. Możesz jednak utworzyć nowe zbiory źródeł, aby dokładnie kontrolować, które pliki Gradle kompiluje i pakuje w przypadku określonych typów kompilacji, wariantów usługi, kombinacji wariantów usługi (gdy używasz wymiarów rodzajów) i wariantów kompilacji.
Możesz na przykład zdefiniować podstawowe funkcje w zbiorze źródeł main/ i użyć zbiorów źródeł wariantów usługi, aby zmienić markę aplikacji dla różnych klientów, lub uwzględnić specjalne uprawnienia i funkcje logowania tylko w przypadku wariantów kompilacji, które używają typu kompilacji do debugowania.
Gradle oczekuje, że pliki i katalogi zbioru źródeł będą uporządkowane w określony sposób, podobnie jak w przypadku zbioru źródeł main/. Na przykład Gradle oczekuje, że pliki klas Kotlin lub Java, które są specyficzne dla typu kompilacji „debug”, będą znajdować się w katalogach src/debug/kotlin/ lub src/debug/java/.
Wtyczka Androida do obsługi Gradle udostępnia przydatne zadanie Gradle, które pokazuje, jak uporządkować pliki dla każdego typu kompilacji, wariantu usługi i wariantu kompilacji. Na przykład ten fragment danych wyjściowych zadania opisuje, gdzie Gradle oczekuje znalezienia określonych plików dla typu kompilacji „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]
Aby wyświetlić te dane wyjściowe, wykonaj te czynności:
- Na pasku okien narzędzi kliknij Gradle.
Kliknij MyApplication > Tasks > android i dwukrotnie kliknij sourceSets.
Aby zobaczyć folder Tasks, musisz zezwolić Gradle na utworzenie listy zadań podczas synchronizacji. Aby to zrobić:
- Kliknij File > Settings > Experimental (Plik > Ustawienia > Eksperymentalne) (Android Studio > Settings > Experimental (Android Studio > Ustawienia > Eksperymentalne) na macOS).
- Odznacz Do not build Gradle task list during Gradle sync (Nie twórz listy zadań Gradle podczas synchronizacji Gradle).
- Gdy Gradle wykona zadanie, otworzy się okno Uruchom, w którym wyświetlą się dane wyjściowe.
Uwaga: dane wyjściowe zadania pokazują też, jak porządkować zbiory źródeł plików, których chcesz używać do przeprowadzania testów aplikacji, np. zbiory źródeł testowych test/ i androidTest/.
Gdy tworzysz nowy wariant kompilacji, Android Studio nie tworzy katalogów zbiorów źródeł, ale udostępnia kilka opcji, które mogą Ci pomóc. Aby na przykład utworzyć tylko katalog java/ dla typu kompilacji „debug”:
- Otwórz panel Project (Projekt) i wybierz widok Project (Projekt) z menu u góry panelu.
- Otwórz aplikację
MyProject/app/src/. - Kliknij prawym przyciskiem myszy katalog
srci wybierz New (Nowy) > Directory (Katalog). - W menu w sekcji Zbiory źródeł Gradle wybierz full/java.
- Naciśnij Enter.
Android Studio utworzy katalog zbioru źródeł dla typu kompilacji do debugowania, a następnie utworzy w nim katalog java/. Możesz też pozwolić Android Studio na utworzenie katalogów, gdy dodasz do projektu nowy plik dla konkretnego wariantu kompilacji.
Aby na przykład utworzyć plik XML z wartościami dla typu kompilacji „debug”:
- W panelu Project (Projekt) kliknij prawym przyciskiem myszy katalog
srci wybierz New (Nowy) > XML > Values XML File (Plik XML z wartościami). - Wpisz nazwę pliku XML lub zachowaj nazwę domyślną.
- W menu obok opcji Docelowy zbiór źródeł wybierz debug.
- Kliknij Zakończ.
Ponieważ jako docelowy zbiór źródeł został określony typ kompilacji „debug”, Android Studio automatycznie tworzy niezbędne katalogi podczas tworzenia pliku XML. Wynikowa struktura katalogów wygląda jak na rysunku 1.
Rysunek 1. Nowe katalogi zbiorów źródeł dla typu kompilacji „debug”.
Aktywne zbiory źródeł mają zielony wskaźnik na ikonie, który informuje, że są aktywne. Zbiór źródeł debug ma sufiks [main], który wskazuje, że zostanie on scalony ze zbiorem źródeł main.
W ten sam sposób możesz też utworzyć katalogi zbiorów źródeł dla wariantów usługi, np. src/demo/, i wariantów kompilacji, np. src/demoDebug/. Możesz też utworzyć zbiory źródeł testowych kierowane na konkretne warianty kompilacji, np. src/androidTestDemoDebug/. Więcej informacji znajdziesz w artykule o testowaniu zbiorów źródeł.
Zmienianie domyślnych konfiguracji zbiorów źródeł
Jeśli masz źródła, które nie są uporządkowane w domyślnej strukturze plików zbioru źródeł oczekiwanej przez Gradle, jak opisano w poprzedniej sekcji dotyczącej tworzenia zbiorów źródeł, możesz użyć bloku
sourceSets, aby zmienić miejsce, w którym Gradle szuka plików dla każdego komponentu zbioru źródeł.
Blok sourceSets musi znajdować się w bloku android. Nie musisz przenosić plików źródłowych. Wystarczy, że podasz Gradle ścieżki względne do pliku build.gradle.kts na poziomie modułu, w których Gradle może znaleźć pliki dla każdego komponentu zbioru źródeł. Aby dowiedzieć się, które komponenty możesz skonfigurować i czy możesz je zmapować na wiele plików lub katalogów, zapoznaj się z dokumentacją interfejsu API wtyczki Androida do obsługi Gradle.
Ten przykładowy kod mapuje źródła z katalogu app/other/ na określone komponenty zbioru źródeł main i zmienia katalog główny zbioru źródeł 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") ... } } ...
Dynamiczny
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' ... } } } ...
Pamiętaj, że katalog źródłowy może należeć tylko do jednego zbioru źródeł. Nie możesz na przykład udostępniać tych samych źródeł testowych zbiorom źródeł test i androidTest. Dzieje się tak, ponieważ Android Studio tworzy osobne moduły IntelliJ dla każdego zbioru źródeł i nie obsługuje zduplikowanych katalogów głównych treści w zbiorach źródeł.
Kompilowanie ze zbiorami źródeł
Katalogi zbiorów źródeł mogą zawierać kod i zasoby, które chcesz spakować tylko w przypadku określonych konfiguracji. Jeśli na przykład tworzysz wariant kompilacji „demoDebug”, który jest iloczynem kartezjańskim wariantu usługi „demo” i typu kompilacji „debug”, Gradle sprawdza te katalogi i nadaje im następujące priorytety:
-
src/demoDebug/(zbiór źródeł wariantu kompilacji) -
src/debug/(zbiór źródeł typu kompilacji) -
src/demo/(zbiór źródeł wariantu usługi) -
src/main/(główny zbiór źródeł)
Zbiory źródeł utworzone dla kombinacji wariantów usługi muszą zawierać wszystkie wymiary rodzajów. Na przykład zbiór źródeł wariantu kompilacji musi być połączeniem typu kompilacji i wszystkich wymiarów rodzajów. Scalanie kodu i zasobów obejmujących foldery, które obejmują wiele, ale nie wszystkie wymiary rodzajów, nie jest obsługiwane.
Jeśli połączysz kilka wariantów usługi, priorytet między nimi jest określany przez wymiar rodzajów, do którego należą. Podczas wyświetlania wymiarów rodzajów za pomocą właściwości
android.flavorDimensions warianty usługi są traktowane priorytetowo w kolejności, w jakiej są deklarowane ich wymiary. Wcześniejsze wymiary mają pierwszeństwo przed kolejnymi. Dodatkowo zbiory źródeł utworzone dla kombinacji wariantów usługi mają wyższy priorytet niż zbiory źródeł należące do pojedynczego wariantu usługi.
Kolejność priorytetów określa, który zbiór źródeł ma wyższy priorytet, gdy Gradle łączy kod i zasoby. Katalog zbioru źródeł demoDebug/ prawdopodobnie zawiera pliki, które są specyficzne dla tego wariantu kompilacji. Jeśli demoDebug/ zawiera plik, który jest również zdefiniowany w debug/, Gradle używa pliku ze zbioru źródeł demoDebug/. Podobnie Gradle nadaje plikom w zbiorach źródeł typu kompilacji i wariantu usługi wyższy priorytet niż tym samym plikom w main/.
Gradle uwzględnia tę kolejność priorytetów podczas stosowania tych reguł kompilacji:
- Cały kod źródłowy w katalogach
kotlin/lubjava/jest kompilowany razem, aby wygenerować pojedyncze dane wyjściowe.Uwaga: w przypadku danego wariantu kompilacji Gradle zgłasza błąd kompilacji, jeśli napotka co najmniej 2 katalogi zbiorów źródeł, które mają zdefiniowaną tę samą klasę Kotlin lub Java. Na przykład podczas tworzenia aplikacji do debugowania nie można zdefiniować zarówno
src/debug/Utility.kt, jak isrc/main/Utility.kt, ponieważ Gradle sprawdza oba te katalogi podczas procesu kompilacji i zgłasza błąd „zduplikowana klasa”. Jeśli chcesz mieć różne wersje plikuUtility.ktdla różnych typów kompilacji, każdy typ kompilacji musi definiować własną wersję pliku i nie może jej uwzględniać w zbiorze źródełmain/. - Pliki manifestu są scalane w jeden plik. Priorytet jest nadawany w takiej samej kolejności jak na liście w poprzednim przykładzie. Na przykład ustawienia pliku manifestu dla typu kompilacji zastępują ustawienia pliku manifestu dla wariantu usługi. Więcej informacji znajdziesz w artykule o scalaniu plików manifestu.
- Pliki w katalogach
values/są scalane. Jeśli 2 pliki mają tę samą nazwę, np. 2 plikistrings.xml, priorytet jest nadawany w tej samej kolejności co na liście w poprzednim przykładzie. Na przykład wartości zdefiniowane w pliku w zbiorze źródeł typu kompilacji zastępują wartości zdefiniowane w tym samym pliku w wariancie usługi. - Zasoby w katalogach
res/iasset/są pakowane razem. Jeśli w co najmniej 2 zbiorach źródeł zdefiniowano zasoby o tej samej nazwie, priorytet jest nadawany w takiej samej kolejności jak na liście w poprzednim przykładzie. - Gradle nadaje zasobom i manifestom zawartym w zależnościach modułu biblioteki najniższy priorytet podczas tworzenia aplikacji.
Deklarowanie zależności
Aby skonfigurować zależność dla konkretnego wariantu kompilacji lub zbioru źródeł testowych, przed słowem kluczowym Implementation dodaj nazwę wariantu kompilacji lub zbioru źródeł testowych, jak w tym przykładzie:
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") }
Dynamiczny
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' }
Więcej informacji o konfigurowaniu zależności znajdziesz w artykule Dodawanie zależności kompilacji.
Używanie zarządzania zależnościami z uwzględnieniem wariantów
Wtyczka Androida do obsługi Gradle w wersji 3.0.0 lub nowszej zawiera nowy mechanizm zależności, który automatycznie dopasowuje warianty podczas korzystania z biblioteki. Na przykład wariant debug aplikacji automatycznie wykorzystuje wariant debug biblioteki. Działa to również w przypadku wersji: wariant freeDebug aplikacji będzie używać wariantu freeDebug biblioteki.
Aby wtyczka mogła dokładnie dopasowywać warianty, musisz podać dopasowania zastępcze, jak opisano w następnej sekcji, na wypadek gdyby bezpośrednie dopasowanie nie było możliwe.
Załóżmy na przykład, że aplikacja konfiguruje typ kompilacji o nazwie „staging”, ale jedna z jej zależności biblioteki nie. Gdy wtyczka spróbuje utworzyć wersję „przejściową” aplikacji, nie będzie wiedziała, której wersji biblioteki użyć, i wyświetli się komunikat o błędzie podobny do tego:
Error:Failed to resolve: Could not resolve project :mylibrary.
Required by:
project :app
Rozwiązywanie błędów kompilacji związanych z dopasowywaniem wariantów
Wtyczka zawiera elementy DSL, które pomagają kontrolować, jak Gradle rozwiązuje sytuacje, w których bezpośrednie dopasowanie wariantu między aplikacją a zależnością nie jest możliwe.
Poniżej znajdziesz listę problemów związanych z dopasowywaniem zależności uwzględniającym warianty oraz sposoby ich rozwiązywania za pomocą właściwości DSL:
Aplikacja zawiera typ kompilacji, którego nie ma zależność biblioteki.
Na przykład aplikacja zawiera typ kompilacji „staging”, ale zależność zawiera tylko typy kompilacji „debug” i „release”.
Pamiętaj, że nie ma problemu, gdy zależność biblioteki zawiera typ kompilacji, którego nie ma Twoja aplikacja. Dzieje się tak, ponieważ wtyczka nigdy nie żąda tego typu kompilacji od zależności.
Użyj
matchingFallbacks, aby określić alternatywne dopasowania dla danego typu kompilacji, jak pokazano tutaj: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") } } }
Dynamiczny
// 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'] } } }
W przypadku danego wymiaru rodzajów, który istnieje zarówno w aplikacji, jak i w jej zależności biblioteki, aplikacja zawiera rodzaje, których nie ma w bibliotece.
Na przykład zarówno aplikacja, jak i jej zależności biblioteki zawierają wymiar rodzajów „tier”. Jednak wymiar „poziom” w aplikacji obejmuje wersje „bezpłatna” i „płatna”, a zależność obejmuje tylko wersje „demo” i „płatna” dla tego samego wymiaru.
Pamiętaj, że w przypadku danego wymiaru rodzajów, który istnieje zarówno w aplikacji, jak i w jej zależnościach bibliotecznych, nie ma problemu, gdy biblioteka zawiera wariant usługi, którego nie ma w aplikacji. Dzieje się tak, ponieważ wtyczka nigdy nie żąda tej wersji od zależności.
Użyj
matchingFallbacks, aby określić alternatywne dopasowania dla „bezpłatnego” wariantu usługi aplikacji, jak pokazano tutaj: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") } } }
Dynamiczny
// 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'] } } }
Zależność biblioteki zawiera wymiar rodzajów, którego nie ma w aplikacji.
Na przykład zależność biblioteki zawiera wersje dla wymiaru „minApi”, ale aplikacja zawiera wersje tylko dla wymiaru „tier”. Gdy chcesz utworzyć wersję „freeDebug” aplikacji, wtyczka nie wie, czy użyć wersji „minApi23Debug” czy „minApi18Debug” zależności.
Pamiętaj, że nie ma problemu, gdy aplikacja zawiera wymiar rodzajów, którego nie ma zależność biblioteki. Dzieje się tak, ponieważ wtyczka dopasowuje wersje tylko tych wymiarów, które występują w zależności. Jeśli na przykład zależność nie zawiera wymiaru dla ABI, wersja „freeX86Debug” aplikacji będzie używać wersji „freeDebug” zależności.
Użyj
missingDimensionStrategyw blokudefaultConfig, aby określić domyślną wersję, którą wtyczka ma wybrać z każdego brakującego wymiaru, jak pokazano w tym przykładzie. Możesz też zastąpić wybrane opcje w blokuproductFlavors, aby każda wersja mogła określać inną strategię dopasowywania w przypadku brakującego wymiaru.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") {} } }
Dynamiczny
// 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 {} } }
Więcej informacji znajdziesz w sekcjach matchingFallbacks i missingDimensionStrategy w dokumentacji DSL wtyczki Androida do obsługi Gradle.
Konfigurowanie ustawień podpisywania
Gradle nie podpisuje pliku APK ani AAB kompilacji do publikacji, chyba że wyraźnie zdefiniujesz konfigurację podpisywania tej kompilacji. Jeśli nie masz jeszcze klucza podpisywania, wygeneruj klucz przesyłania i magazyn kluczy za pomocą Android Studio.
Aby ręcznie skonfigurować konfiguracje podpisywania dla typu kompilacji do publikacji za pomocą konfiguracji kompilacji Gradle:
- Utwórz magazyn kluczy. Magazyn kluczy to plik binarny zawierający zestaw kluczy prywatnych. Magazyn kluczy musisz przechowywać w bezpiecznym miejscu.
- Utwórz klucz prywatny. Klucz prywatny służy do podpisywania aplikacji na potrzeby dystrybucji i nigdy nie jest dołączany do aplikacji ani ujawniany nieupoważnionym osobom trzecim.
-
Dodaj konfigurację podpisywania do pliku
build.gradle.ktsna poziomie modułu:Kotlin
... android { ... defaultConfig {...} signingConfigs { create("release") { storeFile = file("myreleasekey.keystore") storePassword = "password" keyAlias = "MyReleaseKey" keyPassword = "password" } } buildTypes { getByName("release") { ... signingConfig = signingConfigs.getByName("release") } } }
Dynamiczny
... android { ... defaultConfig {...} signingConfigs { release { storeFile file("myreleasekey.keystore") storePassword "password" keyAlias "MyReleaseKey" keyPassword "password" } } buildTypes { release { ... signingConfig signingConfigs.release } } }
Uwaga: umieszczanie haseł do klucza wersji i magazynu kluczy w pliku kompilacji nie jest dobrą praktyką w zakresie bezpieczeństwa. Zamiast tego skonfiguruj plik kompilacji tak, aby uzyskiwał te hasła ze zmiennych środowiskowych lub aby proces kompilacji prosił o ich podanie.
Aby uzyskać te hasła ze zmiennych środowiskowych:
Kotlin
storePassword = System.getenv("KSTOREPWD") keyPassword = System.getenv("KEYPWD")
Dynamiczny
storePassword System.getenv("KSTOREPWD") keyPassword System.getenv("KEYPWD")
Możesz też wczytać magazyn kluczy z lokalnego pliku właściwości. Ze względów bezpieczeństwa nie dodawaj tego pliku do kontroli źródła. Zamiast tego skonfiguruj go lokalnie dla każdego dewelopera. Więcej informacji znajdziesz w artykule Usuwanie informacji o podpisywaniu z plików kompilacji.
Po zakończeniu tego procesu możesz rozpowszechniać aplikację i opublikować ją w Google Play.
Ostrzeżenie: przechowuj magazyn kluczy i klucz prywatny w bezpiecznym miejscu i pamiętaj o tworzeniu ich bezpiecznych kopii zapasowych. Jeśli używasz podpisywania aplikacji przez Google Play i utracisz klucz przesyłania, możesz poprosić o jego zresetowanie w Konsoli Play. Jeśli publikujesz aplikację bez podpisywania aplikacji przez Google Play (w przypadku aplikacji utworzonych przed sierpniem 2021 r.) i utracisz klucz podpisywania aplikacji, nie będziesz mieć możliwości publikowania aktualizacji, ponieważ wszystkie wersje aplikacji trzeba podpisywać przy użyciu tego samego klucza.
Podpisywanie aplikacji na Wear OS
Podczas publikowania aplikacji na Wear OS zarówno plik APK na zegarek, jak i opcjonalny plik APK na telefon muszą być podpisane tym samym kluczem. Więcej informacji o pakowaniu i podpisywaniu aplikacji na Wear OS znajdziesz w artykule Pakowanie i rozpowszechnianie aplikacji na Wear.