다중 APK 빌드

앱을 Google Play에 게시하려면 Android App Bundle을 빌드하고 업로드해야 합니다. 그렇게 하면 Google Play가 자동으로 각 사용자의 기기 설정에 맞는 최적화된 APK를 생성하여 제공하므로 사용자는 앱을 실행하는 데 필요한 코드와 리소스만 다운로드할 수 있습니다. Google Play에 게시하지 않는다면 다중 APK를 게시하는 것이 유용하지만 각 APK를 직접 빌드, 서명, 관리해야 합니다.

가능한 모든 타겟 기기를 지원하기 위해 단일 APK를 빌드해야 하지만, 이 경우 다양한 화면 밀도 또는 애플리케이션 바이너리 인터페이스(ABI)를 지원하는 데 필요한 파일 때문에 APK가 매우 커질 수 있습니다. APK 크기를 줄이는 한 가지 방법은 특정 화면 밀도 또는 ABI를 위한 파일을 포함하는 다중 APK를 만드는 것입니다.

Gradle은 각 밀도 또는 ABI에 적합한 코드와 리소스만 포함하는 별도의 APK를 만들 수 있습니다. 이 페이지에서는 다중 APK를 생성하기 위한 빌드 구성 방법을 설명합니다. 화면 밀도나 ABI를 기반으로 하지 않는 앱의 다른 버전을 만들려면 대신 빌드 변형을 사용할 수 있습니다.

다중 APK를 위한 빌드 구성

다중 APK를 위한 빌드를 구성하려면 splits 블록을 모듈 레벨 build.gradle 파일에 추가합니다. splits 블록 내에 Gradle이 밀도별 APK를 생성해야 하는 방법을 지정하는 density 블록 또는 Gradle이 ABI별 APK를 생성해야 하는 방법을 지정하는 abi 블록을 제공해야 합니다. 밀도와 ABI 블록을 모두 제공하면 빌드 시스템에서 각 밀도와 ABI 조합을 위한 APK를 만듭니다.

화면 밀도를 위한 다중 APK 구성

화면 밀도마다 APK를 하나씩 만들려면 splits 블록 내에 density 블록을 추가합니다. density 블록에 원하는 화면 밀도와 호환 가능한 화면 크기 목록을 제공합니다. 호환 가능한 화면 크기의 목록은 각 APK의 manifest에 특정 <compatible-screens> 요소가 필요한 경우에만 사용해야 합니다.

아래 Gradle DSL 옵션은 화면 밀도를 위한 다중 APK를 구성하는 데 사용됩니다.

enable
이 요소를 true로 설정하면 Gradle은 정의한 화면 밀도를 기반으로 다중 APK를 생성합니다. 기본값은 false입니다.
exclude
Gradle이 별도 APK를 생성하지 않아야 하는 밀도 목록을 쉼표로 구분하여 지정합니다. 대부분의 밀도를 위해 APK를 생성하려면 exclude를 사용하지만 앱이 지원하지 않는 소수의 밀도는 제외해야 합니다.
reset()
화면 밀도의 기본 목록을 삭제합니다. 추가하고자 하는 밀도를 지정하기 위해 include 요소와 결합했을 경우에만 사용합니다. 다음의 스니펫은 reset()을 호출하여 목록을 삭제한 후 include를 사용하여 밀도 목록을 ldpixxhdpi에 설정합니다.
    reset()  // Clears the default list from all densities to no densities.
    include "ldpi", "xxhdpi" // Specifies the two densities we want to generate APKs for.
    
include
Gradle이 APK를 생성해야 하는 밀도의 목록을 쉼표로 구분하여 지정합니다. 정확한 밀도 목록을 지정하려면 reset()과 함께 사용해야 합니다.
compatibleScreens
쉼표로 구분된 호환 가능한 화면 크기의 목록을 지정합니다. 각 APK의 manifest에 일치하는 <compatible-screens> 노드를 삽입합니다. 이 설정을 사용하면 동일한 build.gradle 섹션에서 화면 밀도와 화면 크기 모두를 간편하게 관리할 수 있습니다. 그러나 <compatible-screens>를 사용하면 앱이 작동하는 기기 유형이 제한될 수 있습니다. 다양한 화면 크기를 지원하는 다른 방법은 다양한 화면 지원을 참조하세요.

화면 밀도를 기반으로 하는 각 APK는 APK가 지원하는 화면 유형에 대한 특정 제한이 있는 <compatible-screens> 태그를 포함하기 때문에 다중 APK를 게시하더라도 일부 새 기기는 여러 APK 필터와 일치하지 않습니다. 따라서 Gradle은 항상 모든 화면 밀도를 위한 애셋을 포함하고 <compatible-screens> 태그를 포함하지 않는 추가적인 범용 APK를 생성합니다. 이 범용 APK는 APK와 <compatible-screens> 태그가 일치하지 않는 기기를 대체하기 위해 밀도별 APK와 함께 게시해야 합니다.

다음 예에서는 ldpi, xxhdpixxxhdpi를 제외하고 지원되는 화면 범위에 나열된 화면 밀도마다 APK를 하나씩 생성합니다. 모든 밀도의 기본 목록에서 세 개의 밀도를 삭제하기 위해 exclude를 사용하여 이 작업을 완료합니다.

    android {
      ...
      splits {

        // Configures multiple APKs based on screen density.
        density {

          // Configures multiple APKs based on screen density.
          enable true

          // Specifies a list of screen densities Gradle should not create multiple APKs for.
          exclude "ldpi", "xxhdpi", "xxxhdpi"

          // Specifies a list of compatible screen size settings for the manifest.
          compatibleScreens 'small', 'normal', 'large', 'xlarge'
        }
      }
    }
    

밀도 이름과 화면 크기 이름의 목록은 다양한 화면 지원 방법을 참조하세요. 앱을 특정 화면 유형과 기기에 배포하는 방법에 대한 자세한 내용은 특정 화면에 배포를 참조하세요.

ABI를 위한 다중 APK 구성

서로 다른 ABI를 위해 별도의 APK를 만들려면 splits 블록 내에 abi 블록을 추가합니다. abi 블록에 원하는 ABI 목록을 제공합니다.

다음의 Gradle DSL 옵션은 ABI별로 다중 APK를 구성하는 데 사용됩니다.

enable
이 요소를 true로 설정하면 Gradle은 정의한 ABI를 기반으로 다중 APK를 생성합니다. 기본값은 false입니다.
exclude
Gradle이 별도 APK를 생성해서는 안 되는 ABI의 목록을 쉼표로 구분하여 지정합니다. 대부분의 ABI를 위해 APK를 생성하려면 exclude를 사용하지만 앱에서 지원하지 않는 소수의 ABI는 제외해야 합니다.
reset()
ABI의 기본 목록을 삭제합니다. 추가하고자 하는 ABI를 지정하기 위해 include 요소와 결합했을 경우에만 사용합니다. 다음의 스니펫은 reset()을 호출하여 목록을 삭제하고 난 후 include를 사용하여 ABI의 목록을 x86x86_64에 설정합니다.
    reset()  // Clears the default list from all ABIs to no ABIs.
    include "x86", "x86_64" // Specifies the two ABIs we want to generate APKs for.
    
include
Gradle이 APK를 생성해서는 안 되는 ABI의 목록을 쉼표로 구분하여 지정합니다. 정확한 ABI 목록을 지정하기 위해 반드시 reset()과 함께 사용합니다.
universalApk
만약 true이면 Gradle은 ABI별 APK 외에 범용 APK를 생성합니다. 범용 APK는 단일 APK에 모든 ABI의 코드와 리소스를 포함합니다. 기본값은 false입니다. 참고로 이 옵션은 splits.abi 블록에서만 사용할 수 있습니다. 화면 밀도에 기반한 다중 APK를 빌드할 때 Gradle은 항상 모든 화면 밀도의 코드와 리소스를 포함하는 범용 APK를 생성합니다.

다음 예에서는 x86x86_64 ABI에 하나씩 APK를 생성합니다. reset()을 사용하여 빈 ABI 목록으로 시작하고 뒤이어 각각 APK를 얻을 ABI의 목록과 함께 include를 사용하여 작업을 완료합니다.

    android {
      ...
      splits {

        // Configures multiple APKs based on ABI.
        abi {

          // Enables building multiple APKs per ABI.
          enable true

          // By default all ABIs are included, so use reset() and include to specify that we only
          // want APKs for x86 and x86_64.

          // Resets the list of ABIs that Gradle should create APKs for to none.
          reset()

          // Specifies a list of ABIs that Gradle should create APKs for.
          include "x86", "x86_64"

          // Specifies that we do not want to also generate a universal APK that includes all ABIs.
          universalApk false
        }
      }
    }
    

지원되는 ABI 목록은 지원되는 ABI를 참조하세요.

mips, mips64 및 armeabi

Gradle용 Android Plugin 3.1.0 이상 버전에서는 더 이상 기본적으로 mips, mips64armeabi ABI의 APK를 생성하지 않습니다. 그 이유는 NDK r17 이상에서는 더 이상 이 ABI를 지원되는 타겟으로 포함하지 않기 때문입니다.

먼저 Google Play Console을 확인하여 이 ABI를 타겟팅하는 앱의 APK를 다운로드하는 사용자가 있는지 확인하세요. 아무도 없다면 빌드에서 ABI를 생략할 수 있습니다. 이러한 ABI를 타겟팅하는 APK를 계속 빌드하려면 NDK r16b 이하 버전을 사용하고 아래에 표시된 대로 build.gradle 파일에 ABI를 지정해야 합니다.

    splits {
        abi {
            include 'armeabi', 'mips', 'mips64'
            ...
        }
    }
    

알려진 문제: Gradle용 Android 플러그인 3.0.1 이하 버전을 NDK r17 이상 버전과 함께 사용하고 있다면 다음 오류가 발생할 수 있습니다. Error:ABIs [mips64, armeabi, mips] are not supported for platform. 그 이유는 ABI별로 APK를 빌드할 때 기본적으로 지원되지 않는 ABI가 여전히 이전 버전의 플러그인에 포함되기 때문입니다. 이 문제를 해결하려면 플러그인을 최신 버전으로 업데이트하거나 앱의 build.gradle 파일에서 플러그인의 ABI 기본 목록을 재설정하고 아래와 같이 지원되는 ABI만 포함합니다.

    ...
    splits {
        abi {
            ...
            reset()
            include "x86", "armeabi-v7a", "arm64-v8a", "x86_64"
        }
    }
    

버전 관리 구성

기본적으로 Gradle에서 다중 APK를 생성하는 경우 각 APK는 모듈 레벨 build.gradle 파일에 지정된 것과 동일한 버전 정보를 갖습니다. Google Play 스토어에서는 버전 정보가 모두 동일한 다중 APK를 동일한 앱에 사용할 수 없으므로 Play 스토어에 업로드하기 전에 각 APK에 고유한 versionCode가 있는지 확인해야 합니다.

각 APK의 versionCode를 재정의하도록 모듈 레벨 build.gradle 파일을 구성할 수 있습니다. 다중 APK를 구성하는 각 ABI와 밀도에 고유한 숫자 값을 할당하는 매핑을 생성하여 defaultConfig 또는 productFlavors 블록 내에 정의된 버전 코드를 밀도 또는 ABI에 할당된 숫자 값과 결합한 값으로 출력 버전 코드를 재정의할 수 있습니다.

다음 예에서 x86 ABI의 APK는 versionCode가 2004이며 x86_64 ABI는 3004입니다. 큰 증분 단위(예: 1000)로 버전 코드를 할당하면 나중에 앱을 업데이트해야 하는 경우 고유한 버전 코드를 할당할 수 있습니다. 예를 들어, 다음 업데이트에서 defaultConfig.versionCode가 5를 반복한다면 Gradle은 x86 APK에 versionCode로 2005를, x86_64 APK에 3005를 할당합니다.

팁: 빌드에 범용 APK를 포함하는 경우 다른 APK의 버전 코드보다 낮은 versionCode를 범용 APK에 할당해야 합니다. Google Play 스토어는 타겟 기기와 호환되는 가장 높은 versionCode의 앱 버전을 설치하므로 범용 APK에 낮은 versionCode를 할당하면 Google Play 스토어에서 범용 APK로 대체하기 전에 APK 중 하나를 설치하려고 시도하게 됩니다. 아래 샘플 코드는 범용 APK의 기본 versionCode를 재정의하지 않는 방식으로 이 문제를 처리합니다.

    android {
      ...
      defaultConfig {
        ...
        versionCode 4
      }
      splits {
        ...
      }
    }

    // Map for the version code that gives each ABI a value.
    ext.abiCodes = ['armeabi-v7a':1, x86:2, x86_64:3]

    // For per-density APKs, create a similar map like this:
    // ext.densityCodes = ['mdpi': 1, 'hdpi': 2, 'xhdpi': 3]

    import com.android.build.OutputFile

    // For each APK output variant, override versionCode with a combination of
    // ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
    // is equal to defaultConfig.versionCode. If you configure product flavors that
    // define their own versionCode, variant.versionCode uses that value instead.
    android.applicationVariants.all { variant ->

      // Assigns a different version code for each output APK
      // other than the universal APK.
      variant.outputs.each { output ->

        // Stores the value of ext.abiCodes that is associated with the ABI for this variant.
        def baseAbiVersionCode =
                // Determines the ABI for this variant and returns the mapped value.
                project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))

        // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
        // the following code does not override the version code for universal APKs.
        // However, because we want universal APKs to have the lowest version code,
        // this outcome is desirable.
        if (baseAbiVersionCode != null) {

          // Assigns the new version code to versionCodeOverride, which changes the version code
          // for only the output APK, not for the variant itself. Skipping this step simply
          // causes Gradle to use the value of variant.versionCode for the APK.
          output.versionCodeOverride =
                  baseAbiVersionCode * 1000 + variant.versionCode
        }
      }
    }
    

대체 버전 코드 체계에 관한 더 많은 예를 보려면 버전 코드 할당을 참조하세요.

다중 APK 빌드

모듈 레벨 build.gradle 파일을 구성하여 다중 APK를 빌드한 후에는 Build > Build APK를 클릭하여 Project 창에 현재 선택된 모듈의 모든 APK를 빌드합니다. Gradle은 프로젝트의 build/outputs/apk/ 디렉터리에 각 밀도 또는 ABI를 위한 APK를 만듭니다.

Gradle은 다중 APK를 구성하는 각 밀도 또는 ABI를 위한 APK를 빌드합니다. 밀도와 ABI 모두를 위해 다중 APK를 사용하면 Gradle은 각 밀도와 ABI 조합을 위한 APK를 만듭니다. 예를 들어, 다음의 build.gradle 스니펫을 사용하면 mdpi와 hdpi 밀도 및 x86과 x86_64 ABI를 위한 다중 APK를 빌드할 수 있습니다.

    ...
      splits {
        density {
          enable true
          reset()
          include "mdpi", "hdpi"
        }
        abi {
          enable true
          reset()
          include "x86", "x86_64"
        }
      }
    

예제 구성의 출력은 아래 네 개의 APK를 포함합니다.

  • app-hdpiX86-release.apk: hdpi 밀도와 x86 ABI 전용 코드 및 리소스를 포함합니다.
  • app-hdpiX86_64-release.apk: hdpi 밀도와 x86_64 ABI 전용 코드 및 리소스를 포함합니다.
  • app-mdpiX86-release.apk: mdpi 밀도와 x86 ABI 전용 코드 및 리소스를 포함합니다.
  • app-mdpiX86_64-release.apk: mdpi 밀도와 x86_64 ABI 전용 코드 및 리소스를 포함합니다.

화면 밀도에 기반한 다중 APK를 빌드할 때 Gradle은 항상 밀도별 APK 외에 모든 밀도의 코드 및 리소스를 포함하는 범용 APK를 생성합니다. build.gradle 파일의 splits.abi 블록에 universalApk true를 지정하는 경우 ABI에 기반한 다중 APK를 빌드할 때 Gradle은 모든 ABI의 코드와 리소스를 포함하는 APK만 생성합니다.

APK 파일 이름 형식

다중 APK를 빌드할 때 Gradle은 아래와 같은 체계의 APK 파일 이름을 사용합니다.

modulename-screendensityABI-buildvariant.apk

체계 구성요소는 다음과 같습니다.

modulename
빌드 중인 모듈 이름을 지정합니다.
screendensity
화면 밀도를 위한 다중 APK를 사용하는 경우 'mdpi'와 같이 APK를 위한 화면 밀도를 지정합니다.
ABI
ABI를 위한 다중 APK를 사용하는 경우 'x86'과 같이 APK를 위한 ABI를 지정합니다. 화면 밀도와 ABI 모두를 위한 다중 APK를 사용하는 경우 Gradle은 'mdpiX86'과 같이 밀도 이름을 ABI 이름과 연결합니다. ABI별 APK에 universalApk를 사용하는 경우 Gradle은 범용 APK 파일 이름의 ABI 부분으로 'universal'을 사용합니다.
buildvariant
'debug'와 같이 빌드 중인 빌드 변형을 지정합니다.

예를 들어, 디버그 버전의 'myApp'을 위한 mdpi 화면 밀도 APK를 빌드한다면 APK 파일 이름은 myApp-mdpi-debug.apk입니다. mdpi 화면 밀도와 x86 ABI 모두를 위해 다중 APK를 빌드하도록 구성된 'myApp' 출시 버전의 APK 파일 이름은 myApp-mdpiX86-release.apk입니다.