빌드 변형 구성

이 페이지에서는 빌드 변형을 구성하여 단일 프로젝트에서 다양한 버전의 앱을 만드는 방법과 종속 항목 및 서명 구성을 올바르게 관리하는 방법을 보여줍니다.

빌드 변형은 빌드할 수 있는 다른 버전의 앱을 나타냅니다. 예를 들어, 콘텐츠가 제한된 무료 버전의 앱을 빌드하고, 더 많은 콘텐츠가 포함된 유료 버전의 앱을 빌드할 수 있습니다. 또한, API 수준이나 기타 기기 변형에 따라 다양한 기기를 타겟팅하는 여러 버전의 앱을 빌드할 수도 있습니다.

빌드 변형은 Gradle이 특정 규칙 세트를 사용하여 빌드 유형과 제품 버전에 구성된 설정, 코드, 리소스를 조합한 결과입니다. 개발자가 빌드 변형을 직접 구성하는 것은 아니며, 빌드 변형을 형성하는 빌드 유형과 제품 버전을 구성하는 것입니다.

예를 들어, 'demo' 제품 버전에서는 특정 기능과 기기 요구사항(예: 맞춤 소스 코드, 리소스, 최소 API 수준)을 지정할 수 있는 반면, 'debug' 빌드 유형에서는 다양한 빌드 및 패키징 설정(예: 디버그 옵션, 서명 키)을 적용할 수 있습니다. 이 둘을 결합하는 빌드 변형이 앱의 'demoDebug' 버전이며 여기에는 'demo' 제품 버전, 'debug' 빌드 유형, main/ 소스 세트에 포함된 구성 및 리소스의 조합이 포함됩니다.

빌드 유형 구성

모듈 수준 build.gradle.kts 파일의 android 블록 내에 빌드 유형을 만들고 구성할 수 있습니다. 새 모듈을 만들면 Android 스튜디오에서 디버그 빌드 유형과 출시 빌드 유형이 자동으로 생성됩니다. 디버그 빌드 유형이 빌드 구성 파일에 표시되지 않더라도 Android 스튜디오는 debuggable true로 이 빌드 유형을 구성합니다. 이렇게 하면 보안 Android 기기에서 앱을 디버그할 수 있으며 일반 디버그 키 저장소로 앱 서명을 구성할 수 있습니다.

특정 설정을 추가하거나 변경하려면 디버그 빌드 유형을 구성에 추가하면 됩니다. 다음 샘플에서는 디버그 빌드 유형에 applicationIdSuffix를 지정하고, 디버그 빌드 유형의 설정을 사용하여 초기화된 'staging' 빌드 유형을 구성합니다.

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

Groovy

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

        debug {
            applicationIdSuffix ".debug"
            debuggable true
        }

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

참고: 개발자가 빌드 구성 파일을 변경하면 Android 스튜디오는 프로젝트를 새 구성으로 동기화하도록 요청합니다. 프로젝트를 동기화하려면 변경을 할 때 표시되는 알림바에서 Sync Now를 클릭하거나 툴바에서 Sync Project 를 클릭합니다. Android 스튜디오에서 구성 오류가 발견되면, 문제가 무엇인지 설명해주는 Messages 창이 나타납니다.

빌드 유형으로 구성할 수 있는 모든 속성에 관한 자세한 내용은 BuildType 참조를 읽어보세요.

제품 버전 구성

제품 버전을 만드는 것은 빌드 유형을 만드는 것과 비슷합니다. 제품 버전을 빌드 구성의 productFlavors 블록에 추가하고 원하는 설정을 포함합니다. 제품 버전은 defaultConfig와 동일한 속성을 지원합니다. defaultConfig가 실제로 ProductFlavor 클래스에 속하기 때문입니다. 즉, defaultConfig 블록에 모든 버전의 기본 구성을 제공할 수 있으며, 각 버전은 applicationId와 같은 모든 기본값을 변경할 수 있습니다. 애플리케이션 ID에 관한 자세한 내용은 애플리케이션 ID 설정을 참고하세요.

참고: 하지만 패키지 이름은 계속해서 main/ 매니페스트 파일에 있는 package 속성을 사용하여 지정해야 합니다. 또한 소스 코드에서 해당 패키지 이름을 사용하여 R 클래스를 참조하거나 상대적 활동 또는 서비스 등록을 확인해야 합니다. 이렇게 하면 소스 코드를 변경할 필요 없이 applicationId를 사용하여 각 제품 버전에 패키징 및 배포용 고유 ID를 제공할 수 있습니다.

모든 버전은 명명된 버전 차원에 속해 있어야 하며, 여기서 버전 차원은 제품 버전의 집합입니다. 모든 버전을 버전 차원에 할당해야 합니다. 그러지 않으면 다음과 같은 빌드 오류가 발생합니다.

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

특정 모듈에서 하나의 버전 차원만 지정한다면, Android Gradle 플러그인은 자동으로 모듈의 모든 버전을 동일한 버전 차원에 할당합니다.

다음 코드 샘플에서는 'version'이라는 버전 차원을 생성하고 제품 버전 'demo'와 'full'을 추가합니다. 이러한 버전에서는 자체적인 applicationIdSuffixversionNameSuffix를 제공합니다.

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

Groovy

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

참고: Google Play에서 APK를 사용하여 배포하는 기존 앱(2021년 8월 전에 만들어짐)이 있는 경우 Google Play에서 다중 APK 지원을 사용하여 앱을 배포하려면 모든 변형에 동일한 applicationId 값을 할당하고 각 변형에 다른 versionCode를 부여하세요. 다양한 앱 변형을 별도의 앱으로 Google Play에 배포하려면, 각 변형에 서로 다른 applicationId를 할당해야 합니다.

제품 버전을 생성하고 구성한 후, 알림바에서 Sync Now를 클릭합니다. 동기화가 완료되면 Gradle은 빌드 유형과 제품 버전에 따라 자동으로 빌드 변형을 생성하고 <product-flavor><Build-Type>에 따라 빌드 변형의 이름을 지정합니다. 예를 들어, 'demo' 및 'full' 제품 버전을 생성하고 기본 'debug' 및 'release' 빌드 유형을 유지하면 Gradle은 다음과 같은 빌드 변형을 만듭니다.

  • demoDebug
  • demoRelease
  • fullDebug
  • fullRelease

빌드하고 실행할 빌드 변형을 선택하려면 Build > Select Build Variant로 이동하여 메뉴에서 빌드 변형을 선택합니다. 자체 기능과 리소스로 각 빌드 변형을 맞춤설정하려면 이 페이지에 설명된 대로 소스 세트를 만들고 관리해야 합니다.

빌드 변형을 위해 애플리케이션 ID 변경

앱의 APK나 AAB를 빌드할 때 빌드 도구는 다음 예와 같이 build.gradle.kts 파일의 defaultConfig 블록에 정의된 애플리케이션 ID를 사용하여 앱을 태그합니다. 그러나 Google Play 스토어에서 다양한 앱 버전(예: 'free' 및 'pro' 버전)이 별도의 등록정보를 갖추도록 하려면 애플리케이션 ID가 서로 다른 별개의 빌드 변형을 만들어야 합니다.

이 경우에는 각 빌드 변형을 별도의 제품 버전으로 정의합니다. productFlavors 블록 내부의 각 버전에 applicationId 속성을 재정의하거나, 아래와 같이 applicationIdSuffix를 사용하여 기본 애플리케이션 ID에 세그먼트를 추가할 수 있습니다.

Kotlin

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

Groovy

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

이렇게 하면 'free' 제품 버전의 애플리케이션 ID는 'com.example.myapp.free'가 됩니다.

아래와 같이 빌드 유형에 따라 applicationIdSuffix를 사용하여 세그먼트를 추가할 수도 있습니다.

Kotlin

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

Groovy

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

Gradle에서는 제품 버전 뒤에 빌드 유형 구성을 적용하므로 'free debug' 빌드 변형의 애플리케이션 ID는 'com.example.myapp.free.debug'입니다. 이는 두 개의 앱이 동일한 애플리케이션 ID를 가질 수 없으므로 하나의 기기에 디버그 빌드와 출시 빌드를 모두 두려고 할 때 유용합니다.

Google Play에서 APK를 사용하여 배포하는 기존 앱(2021년 8월 전에 만들어짐)이 있고, 각각 다른 기기 설정(예: API 수준)을 타겟팅하는 다중 APK를 배포하는 데 동일한 앱 등록정보를 사용하려는 경우 각 빌드 변형에는 동일한 애플리케이션 ID를 사용해야 하지만 각 APK에는 다른 versionCode를 부여해야 합니다. 자세한 내용은 다중 APK 지원을 참고하세요. AAB를 사용하는 게시는 기본적으로 단일 버전 코드와 애플리케이션 ID를 사용하는 단일 아티팩트를 사용하므로 영향을 받지 않습니다.

도움말: 매니페스트 파일에서 애플리케이션 ID를 참조해야 할 경우 모든 매니페스트 속성에서 ${applicationId} 자리표시자를 사용할 수 있습니다. Gradle은 빌드 중에 이 태그를 실제 애플리케이션 ID로 교체합니다. 자세한 내용은 매니페스트에 빌드 변수 삽입을 참고하세요.

버전 차원과 여러 제품 버전 결합

일부 경우에는 여러 제품 버전의 구성을 조합하는 것이 좋습니다. 예를 들어, API 수준을 기준으로 'full' 및 'demo' 제품 버전에 서로 다른 구성을 생성할 수 있습니다. 이를 위해 Android Gradle 플러그인을 사용하면 여러 제품 버전 그룹을 버전 차원으로 만들 수 있습니다.

앱을 빌드할 때 Gradle은 빌드 유형 구성과 함께 최종 빌드 변형을 생성하기 위해 개발자가 정의하는 각 버전 차원에서 제품 버전 구성을 조합합니다. Gradle은 동일한 버전 차원에 속한 제품 버전을 조합하지 않습니다.

다음 코드 샘플에서는 flavorDimensions 속성을 사용하여 'full' 및 'demo' 제품 버전을 그룹화하는 'mode' 버전 차원과 API 수준을 기반으로 제품 버전 구성을 그룹화하는 'api' 버전 차원을 생성합니다.

Kotlin

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

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

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

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

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

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

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

Groovy

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

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

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

    full {
      dimension "mode"
      ...
    }

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

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

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

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

Gradle이 생성하는 빌드 변형의 개수는 각 버전 차원에 있는 버전의 수와 개발자가 구성하는 빌드 유형의 수를 곱한 값과 같습니다. Gradle이 각 빌드 변형 또는 빌드 변형에 대응하는 아티팩트의 이름을 지정하면 우선순위가 높은 버전 차원에 속한 제품 버전이 먼저 나타나고 그다음에 우선순위가 낮은 차원의 제품 버전과 빌드 유형이 차례대로 나타납니다.

이전 빌드 구성을 예로 들면 Gradle은 다음과 같은 이름 지정 체계로 빌드 변형 총 12개를 만듭니다.

  • 빌드 변형: [minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
  • 해당 APK: app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
  • 예를 들면 다음과 같습니다.
    빌드 변형: minApi24DemoDebug
    빌드 변형에 대응하는 APK: app-minApi24-demo-debug.apk

개별 제품 버전 및 빌드 변형에 생성할 수 있는 소스 세트 디렉터리 외에, 제품 버전의 각 조합에도 소스 세트 디렉터리를 만들 수 있습니다. 예를 들어, 자바 소스를 생성하여 src/demoMinApi24/java/ 디렉터리에 추가할 수 있고, Gradle은 이러한 두 제품 버전을 조합한 변형을 빌드할 때만 이러한 소스를 사용합니다.

제품 버전 조합을 위해 생성하는 소스 세트는 개별 제품 버전에 속한 소스 세트보다 우선순위가 높습니다. Gradle이 리소스를 병합하는 방식과 소스 세트에 관한 자세한 내용은 소스 세트 생성 방법에 관한 섹션을 참고하세요.

변형 필터링

Gradle은 개발자가 구성하는 제품 버전과 빌드 유형의 가능한 모든 조합을 위한 빌드 변형을 생성합니다. 하지만 필요하지 않거나 프로젝트의 컨텍스트에 맞지 않는 특정 빌드 변형이 있을 수 있습니다. 특정 빌드 변형 구성을 삭제하려면 모듈 수준 build.gradle.kts 파일에서 변형 필터를 만드세요.

이전 섹션에서 예로 든 빌드 구성을 사용할 때 앱의 데모 버전으로 API 수준 23 이상만 지원할 계획이라고 가정해 보겠습니다. variantFilter 블록을 사용하여 'minApi21' 및 'demo' 제품 버전을 조합하는 모든 빌드 변형 구성을 필터링할 수 있습니다.

Kotlin

android {
  ...
  buildTypes {...}

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

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

Groovy

android {
  ...
  buildTypes {...}

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

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

빌드 구성에 변형 필터를 추가하고 알림바에서 Sync Now를 클릭하면 Gradle은 지정된 조건을 충족하는 모든 빌드 변형을 무시합니다. 메뉴 바에서 Build > Select Build Variant를 클릭하거나 도구 창 모음에서 Build Variants 를 클릭할 때 빌드 변형이 더 이상 메뉴에 표시되지 않습니다.

소스 세트 생성

기본적으로 Android 스튜디오는 모든 빌드 변형 간에 공유하려는 모든 항목에 main/ 소스 세트 및 디렉터리를 생성합니다. 그러나 특정 빌드 유형, 제품 버전, 버전 차원을 사용하는 경우 제품 버전의 조합, 빌드 변형에 Gradle이 컴파일하고 패키징하는 파일을 정확히 제어하기 위해 새로운 소스 세트를 생성할 수 있습니다.

예를 들어, 기본적인 기능은 main/ 소스 세트에 정의하고, 제품 버전 소스 세트를 사용하여 다양한 클라이언트를 위해 앱 브랜드를 변경하거나 디버그 빌드 유형을 사용하는 빌드 변형에만 특별 권한과 로깅 기능을 포함할 수 있습니다.

Gradle은 소스 세트 파일과 디렉터리가 main/ 소스 세트와 유사하게 특정 방식으로 구성되기를 기대합니다. 예를 들어 Gradle은 'debug' 빌드 유형 전용의 Kotlin 또는 자바 클래스 파일이 src/debug/kotlin/ 또는 src/debug/java/ 디렉터리에 있을 것으로 예상합니다.

Android Gradle 플러그인은 각 빌드 유형, 제품 버전, 빌드 변형에 관한 파일 구성 방법을 보여주는 유용한 Gradle 작업을 제공합니다. 예를 들어, 다음 작업 출력 샘플은 Gradle이 'debug' 빌드 유형의 특정 파일을 찾을 것으로 예상되는 위치를 설명합니다.

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

...

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

이 출력을 보려면 다음과 같이 진행합니다.

  1. 도구 창 모음에서 Gradle을 클릭합니다.
  2. MyApplication > Tasks > android로 이동하여 sourceSets를 더블클릭합니다.

    Tasks 폴더를 보려면 동기화 중에 Gradle이 작업 목록을 빌드하도록 허용해야 합니다. 방법은 다음과 같습니다.

    1. File > Settings > Experimental (macOS는 Android 스튜디오 > Settings > Experimental)을 클릭합니다.
    2. Do not build Gradle task list during Gradle sync를 선택 해제합니다.
  3. Gradle에서 작업을 실행하면 Run 창이 열리고 출력 내용이 표시됩니다.

참고: 이 작업 출력은 앱 테스트를 실행하는 데 사용할 파일의 소스 세트(예: test/androidTest/ 테스트 소스 세트)를 구성하는 방법도 보여줍니다.

새 빌드 변형을 생성할 때 Android 스튜디오에서 소스 세트 디렉터리를 자동으로 생성하지는 않지만, 개발자에게 도움이 되는 몇 가지 옵션을 제공합니다. 예를 들어, 'debug' 빌드 유형에 java/ 디렉터리만 만들려면 다음과 같이 하면 됩니다.

  1. Project 창을 열고 창 상단의 메뉴에서 Project 보기를 선택합니다.
  2. MyProject/app/src/로 이동합니다.
  3. src 디렉터리를 마우스 오른쪽 버튼으로 클릭하고 New > Directory를 선택합니다.
  4. Gradle Source Sets에 있는 메뉴에서 full/java를 선택합니다.
  5. Enter를 누릅니다.

Android 스튜디오에서는 디버그 빌드 유형에 소스 세트 디렉터리를 생성한 다음, 그 안에 java/ 디렉터리를 생성합니다. 또는 특정 빌드 변형을 위해 프로젝트에 새 파일을 추가할 때 Android 스튜디오에 디렉터리가 생성될 수 있습니다.

예를 들어, 'debug' 빌드 유형에 Values XML 파일을 생성하려면 다음과 같이 하세요.

  1. Project 창에서 src 디렉터리를 마우스 오른쪽 버튼으로 클릭하고 New > XML > Values XML File을 선택합니다.
  2. XML 파일의 이름을 입력하거나 기본 이름을 유지합니다.
  3. Target Source Set 옆의 메뉴에서 debug를 선택합니다.
  4. Finish를 클릭합니다.

'debug' 빌드 유형이 타겟 소스 세트로 지정되었으므로 Android 스튜디오는 XML 파일을 생성할 때 필요한 디렉터리를 자동으로 생성합니다. 결과 디렉터리 구조는 그림 1과 같습니다.

그림 1. 'debug' 빌드 유형의 새로운 소스 세트 디렉터리

활성 소스 세트는 아이콘에 녹색 표시기가 있어 활성 상태임을 나타냅니다. debug 소스 세트에는 [main]이 접미사로 붙어 main 소스 세트에 병합된다는 것을 나타냅니다.

또한, 동일한 절차를 사용하여 제품 버전(예: src/demo/)과 빌드 변형(예: src/demoDebug/)의 소스 세트 디렉터리를 생성할 수도 있습니다. 그 밖에도 특정 빌드 변형을 타겟팅하는 테스트 소스 세트를 생성할 수 있습니다(예: src/androidTestDemoDebug/). 자세한 내용은 테스트 소스 세트를 참고하세요.

기본 소스 세트 구성 변경

Gradle이 예상하는 기본 소스 세트 파일 구조로 구성되지 않은 소스가 있는 경우에는 앞서 소스 세트 생성 섹션에서 설명한 것처럼 sourceSets 블록을 사용하여 Gradle이 소스 세트의 구성요소별로 파일을 수집하는 위치를 변경할 수 있습니다.

sourceSets 블록은 android 블록 내에 있어야 합니다. 소스 파일의 위치를 변경할 필요는 없습니다. 개발자는 Gradle이 각 소스 세트 구성요소의 파일을 찾을 수 있는 모듈 수준 build.gradle.kts 파일의 상대 경로를 Gradle에 제공하기만 하면 됩니다. 어떤 구성요소를 구성할 수 있는지, 이러한 구성요소를 여러 파일이나 디렉터리로 매핑할 수 있는지 알아보려면 Android Gradle 플러그인 API 참조를 확인하세요.

다음은 app/other/ 디렉터리의 소스를 main 소스 세트의 특정 구성요소로 매핑하고 androidTest 소스 세트의 루트 디렉터리를 변경하는 코드 샘플입니다.

Kotlin

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

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

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

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

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

Groovy

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

소스 디렉터리는 하나의 소스 세트에만 속할 수 있습니다. 예를 들어, 동일한 테스트 소스를 testandroidTest 소스 세트와 공유할 수 없습니다. 이는 Android 스튜디오에서 각 소스 세트에 별도의 IntelliJ 모듈을 만들며 소스 세트 전체에서 중복 콘텐츠 루트를 지원할 수 없기 때문입니다.

소스 세트로 빌드

소스 세트 디렉터리를 사용하여 특정 구성으로만 패키징하려는 코드와 리소스를 포함할 수 있습니다. 예를 들어, 'demo' 제품 버전과 'debug' 빌드 유형의 교차 제품(crossproduct)인 'demoDebug' 빌드 변형을 빌드하는 경우, Gradle은 아래의 디렉터리를 찾아서 다음과 같이 우선순위를 부여합니다.

  1. src/demoDebug/(빌드 변형 소스 세트)
  2. src/debug/(빌드 유형 소스 세트)
  3. src/demo/(제품 버전 소스 세트)
  4. src/main/(기본 소스 세트)

제품 버전의 조합을 위해 생성된 소스 세트에는 모든 버전 차원이 포함되어야 합니다. 예를 들어 빌드 변형 소스 세트는 빌드 유형과 모든 버전 차원의 조합이어야 합니다. 전부는 아니지만 여러 버전 차원을 포괄하는 폴더와 관련된 코드와 리소스 병합은 지원되지 않습니다.

여러 제품 버전을 조합할 경우 제품 버전 간의 우선순위는 제품 버전이 속한 버전 차원에 따라 결정됩니다. android.flavorDimensions 속성이 있는 버전 차원을 나열할 때 목록의 첫 번째 버전 차원에 속한 제품 버전이 두 번째 버전 차원에 속한 제품 버전보다 우선순위가 높고 그 이후로도 마찬가지입니다. 또한 제품 버전의 조합을 위해 생성하는 소스 세트의 우선순위가 개별 제품 버전에 속한 소스 세트보다 높습니다.

우선순위 순서에 따라, Gradle이 코드와 리소스를 조합할 때 어떤 소스 세트의 우선순위가 더 높은지 결정됩니다. demoDebug/ 소스 세트 디렉터리에는 빌드 변형에 관련된 파일이 포함될 가능성이 높으므로 debug/에도 정의된 파일이 demoDebug/에 포함된 경우 Gradle은 demoDebug/ 소스 세트에 있는 파일을 사용합니다. 마찬가지로, Gradle은 main/에 있는 파일보다 빌드 유형 및 제품 버전 소스 세트에 있는 동일한 파일에 더 높은 우선순위를 부여합니다. Gradle은 다음과 같은 빌드 규칙을 적용할 때 이 우선순위를 고려합니다.

  • kotlin/ 또는 java/ 디렉터리에 있는 모든 소스 코드가 함께 컴파일되어 단일 출력을 생성합니다.

    참고: 빌드 변형에 속한 두 개 이상의 소스 세트 디렉터리에 동일한 Kotlin 또는 자바 클래스가 정의되어 있다면 Gradle에서 빌드 변형의 빌드 오류가 발생합니다. 예를 들어 디버그 앱을 빌드할 때는 src/debug/Utility.ktsrc/main/Utility.kt를 모두 정의할 수 없습니다. Gradle이 빌드 프로세스 중에 이 두 디렉터리를 모두 살펴보고 '중복 클래스' 오류를 발생시키기 때문입니다. 다른 빌드 유형에 다른 버전의 Utility.kt를 원하는 경우에는 각 빌드 유형이 자체 파일 버전을 정의하고 이를 main/ 소스 세트에 포함하지 않아야 합니다.

  • 여러 매니페스트가 단일 매니페스트로 함께 병합됩니다. 이전 예의 목록과 같은 순서로 우선순위가 부여됩니다. 즉, 빌드 유형의 매니페스트 설정은 제품 버전의 매니페스트 설정보다 우선합니다. 자세히 알아보려면 매니페스트 병합을 참고하세요.
  • values/ 디렉터리의 파일도 함께 병합됩니다. 두 파일의 이름이 같다면(예: 두 개의 strings.xml 파일) 우선순위는 이전 예의 목록과 같은 순서로 부여됩니다. 즉, 빌드 유형 소스 세트의 파일에 정의된 값은 제품 버전의 동일한 파일에 정의된 값보다 우선합니다.
  • res/asset/ 디렉터리에 있는 리소스는 함께 패키징됩니다. 두 개 이상의 소스 세트에 동일한 이름으로 정의된 리소스가 있는 경우 우선순위는 이전 예의 목록과 같은 순서로 부여됩니다.
  • 앱을 빌드할 때 Gradle은 라이브러리 모듈 종속 항목에 포함된 리소스와 매니페스트에는 가장 낮은 우선순위를 부여합니다.

종속 항목 선언

특정 빌드 변형 또는 테스트 소스 세트의 종속 항목을 구성하려면 다음 예와 같이 빌드 변형 또는 테스트 소스 세트의 이름을 Implementation 키워드 앞에 붙입니다.

Kotlin

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

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

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

Groovy

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

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

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

종속 항목 구성에 관한 자세한 내용은 빌드 종속 항목 추가를 참고하세요.

변형 인식 종속 항목 관리 사용

Android Gradle 플러그인 3.0.0 이상에는 라이브러리를 사용할 때 자동으로 변형을 매칭하는 새로운 종속 항목 메커니즘이 포함되어 있습니다. 즉, 앱의 debug 변형에서 라이브러리의 debug 변형 등을 자동으로 사용합니다. 이 기능은 버전을 사용할 때도 작동하며 앱의 freeDebug 변형에서 라이브러리의 freeDebug 변형을 사용합니다.

플러그인이 정확하게 변형을 매칭하려면 다음 섹션에 설명된 대로 매칭 폴백을 제공해야 합니다(예: 직접 매칭이 불가능한 경우).

예를 들어 앱에서 'staging'이라는 빌드 유형을 구성하지만 라이브러리 종속 항목 중 하나에서는 구성하지 않는다고 가정해 보겠습니다. 앱의 'staging' 버전을 빌드하려고 시도할 때 플러그인에서는 사용할 라이브러리 버전을 알 수 없고 다음과 유사한 오류 메시지가 표시됩니다.

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

변형 매칭과 관련된 빌드 오류 해결

플러그인에는 DSL 요소가 포함되어 있어 Gradle이 앱과 종속 항목 간의 직접적인 변형 매칭이 불가능한 상황을 해결하는 방법을 제어할 수 있습니다.

다음은 변형 인식 종속 항목 매칭과 관련된 문제 목록과 DSL 속성을 사용하여 문제를 해결하는 방법입니다.

  • 라이브러리 종속 항목에는 없는 빌드 유형이 앱에 포함되어 있습니다.

    예를 들어 앱에 'staging' 빌드 유형이 포함되어 있지만 종속 항목에는 'debug'와 'release' 빌드 유형만 포함되어 있습니다.

    앱에 없는 빌드 유형이 라이브러리 종속 항목에 포함되어 있는 경우에는 문제가 없습니다. 그 이유는 플러그인이 종속 항목에서 해당 빌드 유형을 요청하지 않기 때문입니다.

    다음과 같이 matchingFallbacks를 사용하여 주어진 빌드 유형에 맞는 대체 매칭을 지정합니다.

    Kotlin

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

    Groovy

    // In the app's build.gradle file.
    android {
        buildTypes {
            debug {}
            release {}
            staging {
                // Specifies a sorted list of fallback build types that the
                // plugin can try to use when a dependency does not include a
                // "staging" build type. You may specify as many fallbacks as you
                // like, and the plugin selects the first build type that's
                // available in the dependency.
                matchingFallbacks = ['debug', 'qa', 'release']
            }
        }
    }
  • 앱과 앱의 라이브러리 종속 항목에 모두 존재하는 버전 차원의 경우 라이브러리에 없는 버전이 앱에 포함됩니다.

    예를 들어 앱과 앱의 라이브러리 종속 항목 모두에 'tier' 버전 차원이 포함되어 있습니다. 그러나 앱의 'tier' 차원에는 'free'와 'paid' 버전이 포함되어 있지만 종속 항목에는 동일한 차원의 'demo'와 'paid' 버전만 포함되어 있습니다.

    앱과 앱의 라이브러리 종속 항목에 모두 존재하는 버전 차원의 경우 앱에 없는 제품 버전이 라이브러리 종속 항목에 포함되어도 문제가 없습니다. 그 이유는 플러그인에서 종속 항목에 앱에 없는 버전을 요청하지 않기 때문입니다.

    다음과 같이 matchingFallbacks를 사용하여 앱의 'free' 제품 버전에 맞는 대체 매칭을 지정합니다.

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

    Groovy

    // In the app's build.gradle file.
    android {
        defaultConfig{
        // Don't configure matchingFallbacks in the defaultConfig block.
        // Instead, specify fallbacks for a given product flavor in the
        // productFlavors block, as shown below.
      }
        flavorDimensions 'tier'
        productFlavors {
            paid {
                dimension 'tier'
                // Because the dependency already includes a "paid" flavor in its
                // "tier" dimension, you don't need to provide a list of fallbacks
                // for the "paid" flavor.
            }
            free {
                dimension 'tier'
                // Specifies a sorted list of fallback flavors that the plugin
                // can try to use when a dependency's matching dimension does
                // not include a "free" flavor. Specify as many
                // fallbacks as you like; the plugin selects the first flavor
                // that's available in the dependency's "tier" dimension.
                matchingFallbacks = ['demo', 'trial']
            }
        }
    }
  • 라이브러리 종속 항목에 앱에 없는 버전 차원이 포함되어 있습니다.

    예를 들어 라이브러리 종속 항목에 'minApi' 차원의 버전이 포함되어 있지만 앱에는 'tier' 차원의 버전만 포함되어 있습니다. 앱의 'freeDebug' 버전을 빌드할 때 플러그인에서 'minApi23Debug' 또는 'minApi18Debug' 중 어느 종속 항목 버전을 사용할지 알 수 없습니다.

    라이브러리 종속 항목에 없는 버전 차원이 앱에 포함되어 있는 경우에는 문제가 없습니다. 그 이유는 플러그인에서 종속 항목에 있는 차원의 버전만 매칭하기 때문입니다. 예를 들어 종속 항목에 ABI의 차원이 포함되지 않은 경우 앱의 'freeX86Debug' 버전에서는 단순히 종속 항목의 'freeDebug' 버전을 사용합니다.

    다음 샘플과 같이 defaultConfig 블록의 missingDimensionStrategy를 사용하여 플러그인이 누락된 각 차원에서 선택할 기본 버전을 지정합니다. productFlavors 블록에서 선택한 내용을 재정의할 수도 있으므로 각 버전에서 누락된 차원의 다른 매칭 전략을 지정할 수 있습니다.

    Kotlin

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

    Groovy

    // 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 {}
        }
    }

자세한 내용은 Android Gradle 플러그인 DSL 참조에서 matchingFallbacksmissingDimensionStrategy를 참고하세요.

서명 설정 구성

출시 빌드의 서명 구성을 명시적으로 정의하지 않는 한 Gradle은 이 빌드의 APK나 AAB에 서명하지 않습니다. 아직 서명 키가 없다면 Android 스튜디오를 사용하여 업로드 키와 키 저장소를 생성하세요.

Gradle 빌드 구성을 사용하여 출시 빌드 유형의 서명 구성을 수동으로 구성하려면 다음을 따르세요.

  1. 키 저장소를 생성합니다. 키 저장소는 비공개 키 세트가 포함된 바이너리 파일입니다. 키 저장소는 안전하고 보안이 유지되는 장소에 보관해야 합니다.
  2. 비공개 키를 생성합니다. 비공개 키는 배포를 위해 앱에 서명하는 데 사용되고 앱에 포함되거나 승인되지 않은 서드 파티에 공개되지 않습니다.
  3. 서명 구성을 모듈 수준 build.gradle.kts 파일에 추가합니다.

    Kotlin

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

    Groovy

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

참고: 출시 키와 키 저장소의 비밀번호를 빌드 파일에 포함하는 것은 보안에 좋지 않습니다. 대신 환경 변수에서 이러한 비밀번호를 가져오도록 빌드 파일을 구성하거나 빌드 프로세스에서 이러한 비밀번호를 입력하도록 메시지를 표시합니다.

환경 변수에서 이러한 비밀번호를 가져오려면 다음과 같이 하세요.

Kotlin

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

Groovy

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

또는 로컬 속성 파일에서 키 저장소를 로드할 수 있습니다. 보안을 위해 이 파일을 소스 제어에 추가하지 마세요. 대신 개발자별로 로컬에서 설정하세요. 자세한 내용은 빌드 파일에서 서명 정보 삭제를 참고하세요.

이 프로세스를 완료한 후 앱을 배포하고 Google Play에 게시할 수 있습니다.

경고: 키 저장소와 비공개 키는 안전하고 보안이 유지되는 장소에 보관해야 하며, 안전하게 백업되어야 합니다. Play 앱 서명을 사용하다가 업로드 키를 분실하면 Play Console을 사용하여 재설정을 요청할 수 있습니다. Play 앱 서명 없이 앱(2021년 8월 이전에 생성된 앱)을 게시하는 경우 앱 서명 키를 분실하면 앱 업데이트를 게시할 수 없습니다. 항상 같은 키로 모든 버전의 앱에 서명해야 하기 때문입니다.

Wear OS 앱 서명

Wear OS 앱을 게시할 때는 시계 APK와 선택적 전화 APK에 모두 동일한 키로 서명해야 합니다. Wear OS 앱을 패키징하고 서명하는 방법에 관한 자세한 내용은 Wear 앱 패키지 및 배포를 참고하세요.