Skonfiguruj warianty kompilacji

Na tej stronie pokazujemy, jak skonfigurować warianty kompilacji, aby tworzyć różne wersje aplikacji z jednego projektu, oraz jak poprawnie zarządzać zależnościami i konfiguracjami podpisywania.

Każdy wariant kompilacji reprezentuje inną wersję aplikacji, którą możesz skompilować. Możesz na przykład utworzyć jedną wersję aplikacji, która jest bezpłatna z ograniczonym zestawem treści, i drugą wersję płatną, która zawiera więcej treści. Możesz też tworzyć różne wersje aplikacji kierowane na różne urządzenia, w zależności od poziomu interfejsu API lub innych wersji urządzeń.

Warianty kompilacji są wynikiem działania Gradle za pomocą określonego zestawu reguł łączących ustawienia, kod i zasoby skonfigurowane w typach kompilacji i rodzajach produktów. Chociaż nie konfigurujesz wersji kompilacji bezpośrednio, możesz skonfigurować typy kompilacji i typy produktów, które z nich korzystają.

Na przykład „demonstracyjny” rodzaj usługi może określać pewne funkcje i wymagania dotyczące urządzeń, takie jak niestandardowy kod źródłowy, zasoby i minimalne poziomy interfejsu API, natomiast typ kompilacji „debug” stosuje różne ustawienia kompilacji i pakietu, takie jak opcje debugowania i klucze podpisywania. Wariant kompilacji, który łączy te 2 elementy, to wersja „demoDebug” Twojej aplikacji. Obejmuje kombinację konfiguracji i zasobów uwzględnionych w rodzaju usługi „demonstracja”, typie kompilacji „debug” i zbiorze źródłowym main/.

Skonfiguruj typy 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 debugowania i kompilacji. Mimo że 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 konfigurowanie podpisywania 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. Poniższy przykład wskazuje applicationIdSuffix dla typu kompilacji do debugowania i konfiguruje „przejściowy” typ kompilacji, który jest inicjowany z użyciem ustawień 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"
        }
    }
}

Odlotowe

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 wprowadzasz zmiany w pliku konfiguracji kompilacji, Android Studio wymaga zsynchronizowania projektu z nową konfiguracją. Aby zsynchronizować projekt, kliknij Synchronizuj teraz na pasku powiadomień, który pojawia się po wprowadzeniu zmiany, lub kliknij Synchronizuj projekt na pasku narzędzi. Jeśli Android Studio wykryje błędy w konfiguracji, pojawi się okno Wiadomości z opisem problemu.

Więcej informacji o wszystkich właściwościach, które można konfigurować za pomocą typów kompilacji, znajdziesz w dokumentacji BuildType.

Skonfiguruj smaki produktów

Tworzenie smaków produktów przypomina tworzenie typów kompilacji. Dodaj rodzaje usług do bloku productFlavors w konfiguracji kompilacji i uwzględnij wybrane ustawienia. Różne rodzaje produktów obsługują te same właściwości co defaultConfig, ponieważ defaultConfig należy do klasy ProductFlavor. Oznacza to, że możesz podać podstawową konfigurację dla wszystkich odmian w bloku defaultConfig, a każdy rodzaj 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ć relatywną aktywność lub rejestrację usługi. Dzięki temu możesz użyć applicationId, aby nadać każdemu rodzajowi produktu unikalny identyfikator opakowania i dystrybucji bez konieczności zmiany kodu źródłowego.

Wszystkie smaki muszą należeć do nazwanego wymiaru smaku, który jest grupą smaków produktu. Do wymiaru smaku musisz przypisać wszystkie smaki. W przeciwnym razie pojawi się poniższy 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 w danym module określono tylko 1 wymiar rodzaju, wtyczka Androida do obsługi Gradle automatycznie przypisuje do tego wymiaru wszystkie rodzaje modułu.

Poniższy przykładowy kod tworzy wymiar rodzaju „wersja” i dodaje informacje o rodzajach produktów „demo” i „pełne”. Te smaki mają własne właściwości 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"
        }
    }
}

Odlotowe

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 r.), którą rozpowszechniasz za pomocą plików APK w Google Play, aby dystrybuować aplikację z wykorzystaniem obsługi wielu plików APK w Google Play, przypisz tę samą wartość applicationId do wszystkich wariantów, a każdym z nich przypisz inny versionCode. Aby dystrybuować różne warianty aplikacji jako oddzielne aplikacje w Google Play, do każdego z nich musisz przypisać inny atrybut applicationId.

Po utworzeniu i skonfigurowaniu rodzajów produktów kliknij Synchronizuj teraz na pasku powiadomień. Po zakończeniu synchronizacji Gradle automatycznie tworzy warianty kompilacji na podstawie typów kompilacji i rodzajów produktów oraz nadaje im nazwy zgodnie z <product-flavor><Build-Type>. Jeśli na przykład utworzysz wersje „demograficzne” i „pełne” oraz zachowasz domyślne typy kompilacji „debugowanie” i „wersja”, Gradle utworzy te wersje kompilacji:

  • demoDebug
  • demoRelease
  • fullDebug
  • fullRelease

Aby wybrać wariant kompilacji do skompilowania i uruchomienia, kliknij Kompilacja > Wybierz wariant kompilacji i wybierz wariant kompilacji z menu. Aby zacząć dostosowywać każdy wariant kompilacji za pomocą własnych funkcji i zasobów, musisz utworzyć zestawy źródeł i nimi zarządzać w sposób opisany na tej stronie.

Zmienianie identyfikatora aplikacji dla wariantów kompilacji

Podczas tworzenia pakietu APK lub pakietu aplikacji na Androida narzędzia do kompilacji tagują aplikację identyfikatorem aplikacji zdefiniowanym w bloku defaultConfig w pliku build.gradle.kts, jak pokazano w poniższym przykładzie. Jeśli jednak chcesz utworzyć różne wersje aplikacji, które będą wyświetlać się jako osobne informacje w Sklepie Google Play (np. „bezpłatna” i „pro”), musisz utworzyć osobne warianty kompilacji, z których każdy będzie miał inny identyfikator aplikacji.

W takim przypadku zdefiniuj każdą wersję kompilacji jako osobny rodzaj produktów. W przypadku każdego rodzaju w bloku productFlavors możesz ponownie zdefiniować właściwość applicationId lub zamiast tego dołączyć segment do domyślnego identyfikatora aplikacji za pomocą applicationIdSuffix w taki sposób:

Kotlin

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

Odlotowe

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

W ten sposób identyfikator aplikacji dla rodzaju produktów „bezpłatny” to „com.example.mojaaplikacja.free”.

Możesz też użyć polecenia applicationIdSuffix, by dołączyć segment na podstawie typu kompilacji, jak w tym przykładzie:

Kotlin

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

Odlotowe

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

Gradle stosuje konfigurację typu kompilacji po rodzaju usługi, więc identyfikator aplikacji dla wariantu kompilacji „free debug” to „com.example.myapp.free.debug”. Jest to przydatne, gdy chcesz, aby zarówno debugowanie, jak i kompilacja wersji były dostępne na tym samym urządzeniu, ponieważ nie ma 2 aplikacji o tym samym identyfikatorze.

Jeśli masz starszą aplikację (utworzoną przed sierpniem 2021 r.), którą rozpowszechniasz za pomocą plików APK w Google Play, i chcesz rozprowadzać te same informacje o aplikacji do dystrybucji wielu plików APK, które są kierowane na różne konfiguracje urządzeń, np. na poziom interfejsu API, musisz użyć tego samego identyfikatora aplikacji w każdym wariancie kompilacji, ale nadać każdemu pakietowi APK inny plik versionCode. Więcej informacji znajdziesz w artykule o obsłudze wielu plików APK. Nie ma to wpływu na publikowanie z użyciem pakietów aplikacji na Androida, ponieważ domyślnie używa on jednego artefaktu, który domyślnie używa 1 kodu wersji i identyfikatora aplikacji.

Wskazówka: jeśli w pliku manifestu chcesz odwołać się do identyfikatora aplikacji, 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 artykule Wstawianie zmiennych kompilacji do pliku manifestu.

Łącz różne smaki produktów z wymiarami smakowymi

W niektórych przypadkach możesz zechcieć połączyć konfiguracje z wielu odmian usług. Możesz na przykład utworzyć różne konfiguracje dla „pełnych” i „wersji demonstracyjnych” rodzajów produktów, które będą oparte na poziomie interfejsu API. W tym celu wtyczka Androida do obsługi Gradle umożliwia utworzenie wielu grup smaków produktów jako wymiarów smaku.

Podczas tworzenia aplikacji Gradle łączy konfigurację rodzaju produktu z każdego zdefiniowanego przez Ciebie wymiaru smaku z konfiguracją typu kompilacji, aby utworzyć ostateczny wariant kompilacji. Gradle nie łączy smaków produktów należących do tych samych wymiarów smaku.

Następujący przykładowy kod korzysta z właściwości flavorDimensions do utworzenia wymiaru smaku „tryb”, który pogrupuje odmiany „pełne” i „demo”; oraz wymiar „api” umożliwiający pogrupowanie konfiguracji typów produktów 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"
      ...
    }
  }
}
...

Odlotowe

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 odmian w każdym wymiarze rodzaju oraz liczby skonfigurowanych typów kompilacji. Gdy Gradle nadaje nazwę każdemu wariantowi kompilacji lub odpowiadającemu jej artefaktowi, jako pierwsze pojawiają się rodzaje usług należące do wymiaru rodzaju o wyższym priorytecie, a potem te z wymiarów o niższym priorytecie, a po nim typ kompilacji.

Na podstawie przykładowej poprzedniej konfiguracji kompilacji Gradle tworzy łącznie 12 wariantów kompilacji o tym schemacie nazewnictwa:

  • Wariant kompilacji: [minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
  • Powiązany plik APK: app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
  • Na przykład
    Wariant kompilacji: minApi24DemoDebug
    Odpowiedni plik APK: app-minApi24-demo-debug.apk

Oprócz tworzenia katalogów zbiorów źródłowych dla każdego rodzaju produktu i wariantu kompilacji możesz też tworzyć katalogi zbiorów źródłowych dla każdej kombinacji smaków produktów. Możesz na przykład utworzyć i dodać do katalogu src/demoMinApi24/java/ źródła Java, a Gradle używa tych źródeł tylko podczas tworzenia wariantu, który łączy te 2 rodzaje produktów.

Zbiory źródłowe, które tworzysz dla kombinacji smaków produktów, mają wyższy priorytet niż zbiory źródłowe, które należą do poszczególnych smaków produktów. Więcej informacji o zbiorach źródłowych i o tym, jak Gradle scala zasoby, znajdziesz w sekcji o tworzeniu zbiorów źródłowych.

Filtruj warianty

Gradle tworzy wariant kompilacji dla każdej możliwej kombinacji skonfigurowanych typów usług i typów kompilacji. Mogą jednak występować pewne warianty kompilacji, których nie potrzebujesz lub które nie mają sensu w kontekście Twojego projektu. Aby usunąć określone konfiguracje wariantów kompilacji, utwórz filtr wariantów w pliku build.gradle.kts na poziomie modułu.

Na przykładzie konfiguracji kompilacji z poprzedniej sekcji załóżmy, że w wersji demonstracyjnej aplikacji zamierzasz obsługiwać tylko interfejs API na poziomie 23 i wyższym. Możesz użyć bloku variantFilter, aby odfiltrować wszystkie konfiguracje wersji kompilacji, które łączą rodzaje usług „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
        }
    }
}
...

Odlotowe

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 do konfiguracji kompilacji filtr wariantu 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ż wyświetlane w menu po kliknięciu Kompilacja > Wybierz wariant kompilacji na pasku menu lub Utwórz warianty na pasku okna narzędzi.

Tworzenie zbiorów źródłowych

Domyślnie Android Studio tworzy zbiór źródłowy i katalogi main/ na potrzeby wszystkiego, co chcesz udostępniać między wszystkimi wariantami kompilacji. Możesz jednak tworzyć nowe zbiory źródłowe, aby kontrolować, które pliki Gradle skompiluje i pakiety pod kątem określonych typów kompilacji, rodzajów usług, kombinacji smaków produktów (w przypadku wymiarów rodzajów) i wariantów kompilacji.

Możesz na przykład zdefiniować podstawowe funkcje w zbiorze źródłowym main/ i użyć zbiorów źródeł typów produktów, aby zmienić markę aplikacji dla różnych klientów. Możesz też dodać specjalne uprawnienia i funkcję logowania tylko w przypadku wersji kompilacji, które korzystają z typu kompilacji do debugowania.

Gradle oczekuje, że pliki i katalogi zbioru źródłowego będą uporządkowane w określony sposób, podobnie jak w zbiorze źródłowym main/. Na przykład Gradle oczekuje, że pliki klas Kotlin lub Javy, które są specyficzne dla typu kompilacji „debugowanie”, 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 w przypadku poszczególnych typów kompilacji, rodzajów usług i wariantów kompilacji. Na przykład ten przykład z danych wyjściowych zadania opisuje, gdzie Gradle spodziewa się znaleźć określone pliki typu kompilacji „debugowanie”:

------------------------------------------------------------
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ć dane wyjściowe, wykonaj te czynności:

  1. Kliknij Gradle na pasku okna narzędzi.
  2. Wybierz MyApplication > Tasks > android i kliknij dwukrotnie sourceSets.

    Aby zobaczyć folder Tasks, musisz pozwolić Gradle na utworzenie listy zadań podczas synchronizacji. Aby to zrobić:

    1. Kliknij Plik > Ustawienia > Eksperymentalne (Android Studio > Ustawienia > Eksperymentalne w systemie macOS).
    2. Odznacz Nie kompiluj listy zadań Gradle podczas synchronizacji Gradle.
  3. Po wykonaniu zadania Gradle otworzy się okno Uruchom z wynikiem.

Uwaga: w danych wyjściowych zadania znajdziesz też informacje o tym, jak uporządkować zestawy źródłowe dla plików, których chcesz użyć do testowania aplikacji, np. testowe zestawy źródeł test/ i androidTest/.

Gdy tworzysz nowy wariant kompilacji, Android Studio nie tworzy za Ciebie katalogów zbioru źródłowego, ale udostępnia kilka opcji, które mogą Ci pomóc. Aby na przykład utworzyć tylko katalog java/ dla typu kompilacji „debug”:

  1. Otwórz panel Projekt i z menu u góry wybierz widok Projekt.
  2. Wejdź na MyProject/app/src/.
  3. Kliknij prawym przyciskiem myszy katalog src i wybierz New (Nowy) > Directory (Katalog).
  4. W menu Zestawy źródeł Gradle wybierz full/java.
  5. Naciśnij Enter.

Android Studio tworzy katalog zbioru źródłowego dla typu kompilacji do debugowania, a potem tworzy w nim katalog java/. Android Studio może też utworzyć katalogi za Ciebie, gdy dodasz do projektu nowy plik na potrzeby określonego wariantu kompilacji.

Aby na przykład utworzyć plik XML z wartościami dla typu kompilacji „debugowanie”:

  1. W panelu Projekt kliknij prawym przyciskiem myszy katalog src i wybierz Nowy > XML > Plik XML wartości.
  2. Wpisz nazwę pliku XML lub zachowaj nazwę domyślną.
  3. W menu obok pozycji Docelowy zestaw źródeł wybierz debuguj.
  4. Kliknij Zakończ.

Typ kompilacji „debugowanie” został określony jako docelowy zbiór źródeł, dlatego Android Studio automatycznie tworzy niezbędne katalogi podczas tworzenia pliku XML. Powstała struktura katalogów wygląda jak na ilustracji 1.

Rysunek 1. Nowe katalogi zbioru źródłowego dla kompilacji „debugowanie”.

Aktywne zestawy źródeł mają zielony wskaźnik na ikonie wskazujący, że są aktywne. Zbiór źródłowy debug ma przyrostek [main], co wskazuje, że zostanie scalony ze zbiorem źródłowym main.

W ten sam sposób możesz też tworzyć katalogi zbiorów źródłowych dla różnych rodzajów produktów, np. src/demo/, oraz wariantów kompilacji, np. src/demoDebug/. Możesz też tworzyć testowe zbiory źródłowe, które są kierowane na określone warianty kompilacji, np. src/androidTestDemoDebug/. Więcej informacji znajdziesz w artykule o testowaniu zbiorów źródłowych.

Zmień konfiguracje domyślnego zestawu źródeł

Jeśli masz źródła, które nie są uporządkowane według domyślnej struktury plików zestawu źródeł oczekiwanej przez Gradle zgodnie z opisem w poprzedniej sekcji dotyczącej tworzenia zbiorów źródłowych, możesz użyć bloku sourceSets, aby zmienić miejsce, w którym Gradle zbiera pliki na potrzeby poszczególnych komponentów zbioru źródłowego.

Blok sourceSets musi znajdować się w bloku android. Nie musisz zmieniać lokalizacji plików źródłowych. Wystarczy, że podasz ścieżki narzędzia Gradle w stosunku do pliku build.gradle.kts na poziomie modułu, gdzie Gradle może znaleźć pliki każdego komponentu zbioru źródłowego. Aby dowiedzieć się, które komponenty możesz skonfigurować i czy można je zmapować na wiele plików lub katalogów, zapoznaj się z dokumentacją interfejsu API wtyczki Android Gradle.

Poniższy przykładowy kod mapuje źródła z katalogu app/other/ na określone komponenty zbioru źródłowego main i zmienia katalog główny zbioru źródłowego 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")
      ...
  }
}
...

Odlotowe

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ódłowego. Nie możesz na przykład udostępnić tych samych źródeł testu zarówno zbiorom źródłowym test, jak i androidTest. Wynika to z faktu, że Android Studio tworzy osobne moduły IntelliJ dla każdego zbioru źródłowego i nie może obsługiwać zduplikowanych katalogów głównych treści w zbiorach źródłowych.

Tworzenie z użyciem zbiorów źródłowych

Możesz użyć katalogów zbioru źródłowego, w którym znajdziesz kod i zasoby, które chcesz spakować tylko z określonymi konfiguracjami. Jeśli na przykład tworzysz wariant kompilacji „demoDebug”, który jest iloczynem typu „demoDebug” (demonstracji) i typu kompilacji „debugowanie”, Gradle przeszuka te katalogi i nadaje im następujący priorytet:

  1. src/demoDebug/ (zestaw źródeł wariantów kompilacji)
  2. src/debug/ (zestaw źródłowy typu kompilacji)
  3. src/demo/ (zestaw źródeł smaku produktu)
  4. src/main/ (zestaw głównego źródła)

Zbiory źródłowe utworzone dla kombinacji smaków produktów muszą zawierać wszystkie wymiary smaków. Na przykład zbiór źródeł wersji kompilacji musi być kombinacją typu kompilacji i wszystkich wymiarów rodzaju. Scalanie kodu i zasobów obejmujących foldery, które obejmują wiele wymiarów typów, nie jest obsługiwane.

Jeśli połączysz kilka smaków produktu, priorytet każdego z nich będzie określany na podstawie wymiaru smaku, do którego należą. Gdy podajesz wymiary smaku za pomocą właściwości android.flavorDimensions, smaki produktów należące do pierwszego wymiaru smaku mają wyższy priorytet niż te należące do drugiego wymiaru smaku itd. Dodatkowo zbiory źródłowe tworzone dla kombinacji smaków produktów mają wyższy priorytet niż zbiory źródłowe należące do poszczególnych smaków produktów.

Kolejność priorytetów określa, który zbiór źródłowy ma wyższy priorytet, gdy Gradle łączy kod i zasoby. Katalog zbioru źródłowego demoDebug/ prawdopodobnie zawiera pliki, które są specyficzne dla danego wariantu kompilacji, dlatego jeśli demoDebug/ zawiera plik, który jest również zdefiniowany w debug/, Gradle używa pliku ze zbioru źródłowego demoDebug/. Podobnie Gradle nadaje plikom typu kompilacji, a źródło typu produktu ma wyższy priorytet niż te same pliki w main/. Gradle uwzględnia tę kolejność priorytetów podczas stosowania tych reguł kompilacji:

  • Cały kod źródłowy w katalogach kotlin/ lub java/ jest skompilowany razem w celu wygenerowania jednego wyniku.

    Uwaga: w przypadku danego wariantu kompilacji Gradle zgłasza błąd kompilacji, gdy natrafi na co najmniej 2 katalogi zbioru źródłowego, które mają zdefiniowaną tę samą klasę Kotlin lub Java. Na przykład podczas tworzenia aplikacji do debugowania nie można zdefiniować jednocześnie elementów src/debug/Utility.kt i src/main/Utility.kt, ponieważ Gradle sprawdza te katalogi podczas kompilacji i zwraca błąd „zduplikowanej klasy”. Jeśli chcesz używać różnych wersji obiektu Utility.kt dla różnych typów kompilacji, każdy typ kompilacji musi definiować własną wersję pliku i nie uwzględniać go w zbiorze źródłowym main/.

  • Pliki manifestu są scalone w jeden plik manifestu. Priorytety są nadawane w tej samej kolejności co lista w poprzednim przykładzie. Oznacza to, że ustawienia pliku manifestu dla danego typu kompilacji zastępują ustawienia pliku manifestu dla określonego rodzaju produktów i tak dalej. Więcej informacji znajdziesz w artykule o scalaniu manipulacji.
  • Pliki w katalogach values/ są scalane ze sobą. Jeśli 2 pliki mają tę samą nazwę, np. 2 pliki strings.xml, priorytet jest przyznawany w takiej samej kolejności jak lista w poprzednim przykładzie. Oznacza to, że wartości zdefiniowane w pliku w zbiorze źródłowym typu kompilacji zastępują wartości zdefiniowane w tym samym pliku w rodzaju produktu itd.
  • Zasoby w katalogach res/ i asset/ są spakowane razem. Jeśli w co najmniej 2 zbiorach źródłowych istnieją zasoby o tej samej nazwie, priorytet jest przyznawany w takiej samej kolejności jak lista w poprzednim przykładzie.
  • Gradle nadaje aplikacjom i plikom manifestu dołączonym do zależności modułu biblioteki najniższy priorytet podczas tworzenia aplikacji.

Deklarowanie zależności

Aby skonfigurować zależność do określonego wariantu kompilacji lub testowego zbioru źródłowego, poprzedź nazwę wariantu kompilacji lub zestawu źródeł testów przed słowem kluczowym Implementation, jak pokazano 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.5.1")
}

Odlotowe

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.5.1'
}

Więcej informacji o konfigurowaniu zależności znajdziesz w artykule Dodawanie zależności kompilacji.

Zarządzanie 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. Oznacza to, że wariant debug aplikacji automatycznie używa wariantu debug biblioteki itd. Działa on też w przypadku korzystania z rodzajów: wariant freeDebug aplikacji używa wariantu freeDebug biblioteki.

Aby wtyczka dokładnie pasowała warianty, musisz podać pasujące wartości zastępcze w sposób opisany w następnej sekcji przy przypadkach, gdy bezpośrednie dopasowanie nie jest możliwe.

Załóżmy na przykład, że Twoja aplikacja konfiguruje typ kompilacji o nazwie „etap przejściowy”, ale jedna z zależności biblioteki tego nie robi. Gdy wtyczka spróbuje utworzyć „testową” wersję aplikacji, nie będzie wiedzieć, 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

Naprawianie błędów kompilacji związanych z dopasowaniem wariantów

Wtyczka zawiera elementy DSL, które pomagają kontrolować sposób, w jaki Gradle rozwiązuje sytuacje, gdy bezpośrednie dopasowanie wariantu aplikacji do zależności nie jest możliwe.

Poniżej znajdziesz listę problemów związanych z dopasowywaniem zależności uwzględniających warianty oraz sposoby ich rozwiązywania przy użyciu właściwości DSL:

  • Aplikacja zawiera typ kompilacji, którego nie ma zależność biblioteczna.

    Na przykład aplikacja zawiera typ kompilacji „staging”, ale zależność obejmuje tylko typy kompilacji „debug” i „release”.

    Pamiętaj, że problem nie występuje, gdy zależność biblioteki zawiera typ kompilacji, którego nie ma Twoja aplikacja. Dzieje się tak, ponieważ wtyczka nigdy nie żąda tego typu kompilacji z zależności.

    Użyj matchingFallbacks, aby określić alternatywne dopasowania dla danego typu kompilacji w następujący sposób:

    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")
            }
        }
    }

    Odlotowe

    // 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 rodzaju, który istnieje zarówno w zależności od aplikacji, jak i jej biblioteki, aplikacja zawiera rodzaje, których nie ma biblioteka.

    Na przykład zarówno zależności aplikacji, jak i jej biblioteki zawierają wymiar „Poziom”. Wymiar „poziom” w aplikacji obejmuje jednak wersje „bezpłatne” i „płatne”, ale zależność obejmuje tylko wersje „demograficzne” i „płatne” dla tego samego wymiaru.

    Pamiętaj, że w przypadku danego wymiaru rodzaju, który istnieje zarówno w zależnościach aplikacji, jak i jej bibliotek, nie występuje problem, jeśli biblioteka zawiera rodzaj produktu, którego nie ma Twoja aplikacja. Dzieje się tak, ponieważ wtyczka nigdy nie żąda tego rodzaju z zależności.

    Użyj atrybutu matchingFallbacks, aby określić alternatywne odpowiedniki dla „bezpłatnego” rodzaju produktów, jak 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")
            }
        }
    }
    

    Odlotowe

    // 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 obejmuje wymiar smaku, którego nie ma Twoja aplikacja.

    Na przykład zależność z biblioteką obejmuje rodzaje w wymiarze „minApi”, ale Twoja aplikacja zawiera smaki tylko dla wymiaru „poziom”. Jeśli chcesz utworzyć wersję aplikacji „freeDebug”, wtyczka nie wie, czy użyć wersji „minApi23Debug” czy „minApi18Debug”.

    Pamiętaj, że problem występuje, gdy aplikacja zawiera wymiar smaku, którego nie ma zależność od biblioteki. Wynika to z faktu, że wtyczka pasuje tylko do rodzajów wymiarów, które występują w zależności. Jeśli na przykład zależność nie zawiera wymiaru dla interfejsów ABI, wersja „freeX86Debug” aplikacji użyje wersji „freeDebug”.

    Użyj missingDimensionStrategy w bloku defaultConfig, aby określić domyślny rodzaj wtyczki do wyboru dla każdego brakującego wymiaru, jak pokazano w tym przykładzie. Możesz też zastąpić ustawienia w bloku productFlavors, aby każdy rodzaj mógł określać inną strategię dopasowania dla 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") {}
        }
    }
    

    Odlotowe

    // 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.

Skonfiguruj ustawienia podpisywania

Gradle nie podpisuje pliku APK ani pakietu AAB Twojej wersji, chyba że wyraźnie zdefiniujesz konfigurację podpisywania tej kompilacji. Jeśli nie masz jeszcze klucza podpisywania, wygeneruj klucz przesyłania i magazyn kluczy, używając Android Studio.

Aby ręcznie skonfigurować konfiguracje podpisywania dla typu kompilacji wersji za pomocą konfiguracji kompilacji Gradle:

  1. Utwórz magazyn kluczy. Magazyn kluczy to plik binarny zawierający zestaw kluczy prywatnych. Magazyn kluczy musisz przechowywać w bezpiecznym miejscu.
  2. Utwórz klucz prywatny. Klucz prywatny służy do podpisywania aplikacji do dystrybucji. Nigdy nie jest dołączany do aplikacji ani ujawniany nieupoważnionym osobom.
  3. Dodaj konfigurację podpisywania do pliku build.gradle.kts na 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")
            }
        }
    }

    Odlotowe

    ...
    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ą dotyczącą bezpieczeństwa. Zamiast tego skonfiguruj plik kompilacji tak, aby pobierał te hasła ze zmiennych środowiskowych, lub poproś proces kompilacji o ich podanie.

Aby uzyskać te hasła ze zmiennych środowiskowych:

Kotlin

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

Odlotowe

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

Magazyn kluczy możesz też załadować z pliku właściwości lokalnych. Ze względów bezpieczeństwa nie dodawaj tego pliku do elementu sterującego źródła. Zamiast tego skonfiguruj ją 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, a także twórz ich kopie zapasowe. Jeśli korzystasz z podpisywania aplikacji przez Google Play i stracisz klucz przesyłania, w Konsoli Play możesz poprosić o zresetowanie urządzenia. Jeśli publikujesz aplikację bez podpisywania aplikacji przez Google Play (w przypadku aplikacji utworzonych przed sierpniem 2021 roku) i utracisz klucz podpisywania, nie będziesz mieć możliwości publikowania aktualizacji aplikacji, ponieważ zawsze musisz podpisywać wszystkie jej wersje tym samym kluczem.

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.