Gradle 도움말 및 레시피

Gradle과 Gradle용 Android 플러그인을 사용하면 Android 앱이나 라이브러리를 유연하게 컴파일, 빌드, 패키징할 수 있습니다. 이 페이지에는 각 빌드를 최대한 활용하는 데 유용한 팁과 구성이 정리되어 있습니다. 빌드를 더 빠르게 만드는 방법에 관해 알아보려면 빌드 속도 최적화를 참조하세요.

Gradle을 처음 사용하는 분은 빌드 구성을 읽어 기본 사항을 습득하세요. 또한 Android 플러그인의 DSL 참조 문서에서 이 페이지에 사용된 속성에 관해 자세히 알아볼 수 있습니다.

프로젝트 및 소스 관리

다음은 프로젝트의 모듈과 소스를 관리하기 위한 몇 가지 구성입니다. 프로젝트와 모듈을 만들고 관리하는 방법에 관해 자세히 알아보려면 프로젝트 개요를 참조하세요.

기본 소스 세트 구성 변경

Gradle에서 소스 세트의 각 구성요소에 관한 파일을 수집하는 위치를 변경하려면 모듈 레벨 build.gradle 파일의 sourceSets 블록을 사용할 수 있습니다.

    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']

          // When you list multiple directories, Gradle uses all of them to collect
          // sources. You should avoid specifying a directory which is a parent to one
          // or more other directories you specify.
          res.srcDirs = ['other/res1', 'other/res2']

          // For each source set, you can specify only one Android manifest.
          // The following points Gradle to a different manifest for this source set.
          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'
          ...
        }
      }
    }
    ...
    

프로젝트 범위의 속성 구성

여러 모듈이 포함된 프로젝트의 경우, 프로젝트 수준에서 속성을 정의하고 이 속성을 모든 모듈에 공유하는 것이 유용할 수도 있습니다. 최상위 build.gradle 파일의 ext 블록에 추가 속성을 추가하면 됩니다.

    buildscript {...}
    allprojects {...}

    // This block encapsulates custom properties and makes them available to all
    // modules in the project.
    ext {
        // The following are only a few examples of the types of properties you can define.
        compileSdkVersion = 28
        // You can also use this to specify versions for dependencies. Having consistent
        // versions between modules can avoid behavior conflicts.
        supportLibVersion = "28.0.0"
        ...
    }
    ...
    

동일한 프로젝트의 한 모듈에서 이들 속성에 액세스하려면 모듈 레벨 build.gradle 파일에서 다음과 같은 구문을 사용합니다.

    android {
      // Use the following syntax to access properties you define at the project level:
      // rootProject.ext.property_name
      compileSdkVersion rootProject.ext.compileSdkVersion
      ...
    }
    ...
    dependencies {
        implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
        ...
    }
    

라이브러리 및 종속성 관리

Gradle에서는 원격 라이브러리 모듈이든 로컬 라이브러리 모듈이든 상관없이 종속성 관리를 위한 강력한 메커니즘을 사용할 수 있습니다.

종속성 구성을 통해 특정 빌드를 대상으로 지정

특정 빌드 변형 소스 세트나 테스트 소스 세트에 관해서만 종속성을 원하는 경우, 종속성 구성 이름을 대문자로 지정하고 이 이름 앞에 빌드 변형이나 테스트 소스 세트의 이름을 접두사로 붙입니다.

    android {...}

    // Creates Gradle dependency configurations to use in the dependencies block.
    configurations {
      // For variants that combine a product flavor and build type, you need to
      // intitialize a placeholder for its dependency configuration.
      freeDebugRuntimeOnly{}
      ...
    }

    dependencies {
        // Adds an implementation dependency only to the "free" product flavor.
        freeImplementation 'com.google.firebase:firebase-ads:9.8.0'
        // Adds a runtimeOnly dependency only to the "freeDebug" build variant.
        freeDebugRuntimeOnly fileTree(dir: 'libs', include: ['*.jar'])
        // 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.0.2'
    }
    

다른 버전의 앱 생성

Gradle과 Android 플러그인을 사용하면 빌드 변형을 구성하여 다른 버전의 앱을 단일 모듈로부터 생성할 수 있습니다.

다중 APK 지원 구성

Android 플러그인을 사용하면 ABI나 화면 밀도를 대상으로 하는 다중 APK를 빌드할 수 있으며 Google Play의 다중 APK 지원을 활용할 수 있습니다.

화면 밀도별로 별도 APK 구성

서로 다른 화면 밀도에 관해 별도의 APK를 만들려면 모듈의 build.gradle 파일에 android.splits.density 블록을 추가합니다.

    android {
      ...
      splits {

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

          // Enables building multiple APKs.
          enable true

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

          // Alternatively, you can use the following to clear the default list of
          // screen densities and specify only the screen densities you want to build
          // APKs for:
          // reset()
          // include "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"

          // Specifies a list of compatible screen size settings. This property
          // configures the <compatible-screens> element in the manifest. You
          // typically don't need to configure this manifest property, but it's
          // important when building multiple APKs based on screen density.
          compatibleScreens 'normal', 'large', 'xlarge'
        }
      }
    }
    

ABI별로 별도 APK 구성

각 ABI에 관해 별도의 APK를 만들려면 모듈의 build.gradle 파일에 android.splits.abi 블록을 추가합니다.

    android {
      ...
      splits {

        // Configures multiple APKs based on ABI.
        abi {

          // Enables building multiple APKs.
          enable true

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

          // Specifies a list of ABIs that Gradle should create APKs for.
          include "x86", "armeabi-v7a", "mips"

          // Specify that we want to also generate a universal APK that includes all ABIs.
          universalApk true
        }
      }
    }
    

동적 버전 코드 구성

Gradle이 프로젝트에 관해 APK를 생성할 때 기본적으로 각 APK는 모듈 레벨 build.gradle 파일에 지정된 것과 동일한 버전 정보를 가집니다. Google Play 스토어에서는 동일한 버전 정보를 가진 동일 앱에 관해 다중 APK를 허용하지 않으므로, Play 스토어에 업로드하기 전에 각 APK가 고유한 versionCode를 가지는지 확인해야 합니다.

그렇게 하려면 빌드 시간에 각 APK에 다른 버전을 할당하는 맞춤형 빌드 로직을 사용할 수 있습니다. 예를 들어, 각 ABI에 관해 별도 APK를 만들 경우 자동 APK 버전 관리는 다음과 같습니다.

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

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

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

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

여러 제품 버전의 조합

여러 제품 버전의 구성을 조합하고 싶을 때도 있을 것입니다. 이 작업을 위해 Gradle용 Android 플러그인을 사용하면 버전 차원이라는 제품 버전 그룹을 생성할 수 있습니다.

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

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

      // Specifies the flavor dimensions you want to use. The order in which you
      // list each dimension determines its 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 above--the first dimension has 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. To learn more about assigning version codes to
          // support app updates and uploading to Google Play, read Multiple APK Support
          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"
          ...
        }
      }
    }
    ...
    

변형 필터링

모듈의 build.gradle 파일에서 variantFilter 블록을 사용하지 않으려는 빌드 변형을 필터링할 수 있습니다. 다음 샘플 코드는 'minApi21' 및 'demo' 제품 버전을 조합하는 어떠한 변형도 빌드하지 말라고 Gradle에 지시합니다.

    android {
     ...
     buildTypes {...}

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

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

앱 테스트

로컬 및 통합 단위 테스트 실행에 관해 자세히 알아보려면 앱 테스트를 참조하세요.

Lint 옵션 구성

모듈 레벨 build.gradle 파일의 lintOptions 블록을 사용하여 특정 lint 옵션을 구성할 수 있습니다. Android 프로젝트에 lint를 사용하는 방법에 관해 자세히 알아보려면 Lint로 코드 개선을 참조하세요.

    android {
      ...
      lintOptions {
        // Turns off checks for the issue IDs you specify.
        disable 'TypographyFractions','TypographyQuotes'
        // Turns on checks for the issue IDs you specify. These checks are in
        // addition to the default lint checks.
        enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
        // To enable checks for only a subset of issue IDs and ignore all others,
        // list the issue IDs with the 'check' property instead. This property overrides
        // any issue IDs you enable or disable using the properties above.
        check 'NewApi', 'InlinedApi'
        // If set to true, turns off analysis progress reporting by lint.
        quiet true
        // if set to true (default), stops the build if errors are found.
        abortOnError false
        // if true, only report errors.
        ignoreWarnings true
      }
    }
    ...
    

계측 manifest 설정 구성

Gradle에서 테스트 APK를 빌드하면 AndroidManifest.xml 파일이 자동으로 생성되고 <instrumentation> 노드로 이 파일이 구성됩니다. 이 노드의 일부 설정을 변경하려면 또 다른 manifest 파일을 테스트 소스 세트에 만들거나 모듈 레벨 build.gradle 파일을 구성할 수 있습니다(다음 코드 샘플 참조).

    android {
      ...
      // Each product flavor you configure can override properties in the
      // defaultConfig block. To learn more, go to Configure Product Flavors.
      defaultConfig {
        ...
        // Specifies the application ID for the test APK.
        testApplicationId "com.test.foo"
        // Specifies the fully-qualified class name of the test instrumentation runner.
        testInstrumentationRunner "android.test.InstrumentationTestRunner"
        // If set to 'true', enables the instrumentation class to start and stop profiling.
        // If set to false (default), profiling occurs the entire time the instrumentation
        // class is running.
        testHandleProfiling true
        // If set to 'true', indicates that the Android system should run the instrumentation
        // class as a functional test. The default value is 'false'
        testFunctionalTest true
      }
    }
    ...
    

테스트 빌드 유형 변경

기본적으로 모든 테스트는 특정 디버그 빌드 유형에 관해 실행됩니다. 모듈 레벨 build.gradle 파일의 testBuildType 속성을 사용하여 이를 다른 빌드 유형으로 변경할 수 있습니다. 예를 들어, 'staging' 빌드 유형에 관해 테스트를 실행하려면 다음 스니펫에 표시된 대로 파일을 편집합니다.

    android {
        ...
        testBuildType "staging"
    }
    

Gradle 테스트 옵션 구성

Gradle에서 모든 테스트를 실행하는 방법을 변경하는 옵션을 지정하려면 testOptions 블록을 모듈 레벨 build.gradle에 구성합니다.

    android {
      ...
      // Encapsulates options for running tests.
      testOptions {
        // Changes the directory where Gradle saves test reports. By default, Gradle saves test reports
        // in the path_to_your_project/module_name/build/outputs/reports/ directory.
        // '$rootDir' sets the path relative to the root directory of the current project.
        reportDir "$rootDir/test-reports"
        // Changes the directory where Gradle saves test results. By default, Gradle saves test results
        // in the path_to_your_project/module_name/build/outputs/test-results/ directory.
        // '$rootDir' sets the path relative to the root directory of the current project.
        resultsDir "$rootDir/test-results"
      }
    }
    

로컬 유닛 테스트에만 옵션을 지정하려면 testOptions.unitTests 블록을 구성합니다.

    android {
      ...
      testOptions {
        ...
        // Encapsulates options for local unit tests.
        unitTests {
          // By default, local unit tests throw an exception any time the code you are testing tries to access
          // Android platform APIs (unless you mock Android dependencies yourself or with a testing
          // framework like Mockito). However, you can enable the following property so that the test
          // returns either null or zero when accessing platform APIs, rather than throwing an exception.
          returnDefaultValues true

          // Encapsulates options for controlling how Gradle executes local unit tests. For a list
          // of all the options you can specify, read Gradle's reference documentation.
          all {
            // Sets JVM argument(s) for the test JVM(s).
            jvmArgs '-XX:MaxPermSize=256m'

            // You can also check the task name to apply options to only the tests you specify.
            if (it.name == 'testDebugUnitTest') {
              systemProperty 'debug', 'true'
            }
          }
        }
      }
    }
    

빌드 최적화

이 섹션에서는 전체 빌드 및 증분 빌드의 속도를 높여주는 몇몇 구성을 제공합니다. 자세한 내용은 빌드 속도 최적화를 참조하세요.

코드 축소

Android 스튜디오는 ProGuard를 사용하여 코드를 축소합니다. 새 프로젝트의 경우 Android 스튜디오는 Android SDK의 tools/proguard/folder에 있는 기본 설정 파일(proguard-android.txt)을 사용합니다. 더 많은 코드 축소를 원하면 동일 위치에 있는 proguard-android-optimize.txt 파일을 사용해 보세요.

    android {
      buildTypes {
        release {
          minifyEnabled true
          proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                                               'proguard-rules.pro'
        }
      }
      ...
    }
    ...
    

각 빌드 변형 전용의 ProGuard 규칙을 추가하려면 각 버전의 추가 proguardFiles 속성을 구성합니다. 예를 들어 다음 샘플에서는 flavor2-rules.pro를 'flavor2'에 추가합니다. 이제 릴리스 버전의 'flavor2'는 세 개의 ProGuard 규칙을 모두 사용하며 'release' 블록의 규칙들도 함께 적용됩니다.

    android {
      ...
      buildTypes {
        release {
          minifyEnabled true
          proguardFiles getDefaultProguardFile('proguard-android.txt'),
                 'proguard-rules.pro'
        }
      }
      productFlavors {
        flavor1 {
          ...
        }
        flavor2 {
          proguardFile 'flavor2-rules.pro'
        }
      }
    }
    ...
    

앱 게시

앱을 Google Play에 게시하는 방법에 관해 자세히 알아보려면 앱 게시를 참조하세요.

앱 서명

Android 스튜디오는 릴리스 빌드용 서명을 구성하는 쉬운 방법을 UI에서 제공하지만, 사용자는 signingConfigs 블록을 모듈의 build.gradle 파일에서 수동으로 구성할 수 있습니다.

    android {
      ...
      defaultConfig { ... }

      // Encapsulates signing configurations.
      signingConfigs {
        // Creates a signing configuration called "release".
        release {
          // Specifies the path to your keystore file.
          storeFile file("my-release-key.jks")
          // Specifies the password for your keystore.
          storePassword "password"
          // Specifies the identifying name for your key.
          keyAlias "my-alias"
          // Specifies the password for your key.
          keyPassword "password"
        }
      }
      buildTypes {
        release {
          // Adds the "release" signing configuration to the release build type.
          signingConfig signingConfigs.release
          ...
        }
      }
    }
    ...
    

프로젝트에서 개인 서명 정보 제거

기본적으로 서명 구성은 모듈의 build.gradle 파일에 일반 텍스트로 기록됩니다. 개발자가 팀이나 오픈 소스 프로젝트에서 작업하는 경우, 다음과 같은 처리를 통해 빌드 파일로부터 민감한 정보를 제거할 수 있습니다.

  1. 프로젝트의 루트 디렉터리에 keystore.properties라는 파일을 생성하고 다음 정보를 포함합니다.
        storePassword=myStorePassword
        keyPassword=myKeyPassword
        keyAlias=myKeyAlias
        storeFile=myStoreFileLocation
        
  2. build.gradle 파일에서 다음과 같이 keystore.properties 파일을 로드합니다(android 블록 앞에 있어야 함).
        // Creates a variable called keystorePropertiesFile, and initializes it to the
        // keystore.properties file.
        def keystorePropertiesFile = rootProject.file("keystore.properties")
    
        // Initializes a new Properties() object called keystoreProperties.
        def keystoreProperties = new Properties()
    
        // Loads the keystore.properties file into the keystoreProperties object.
        keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
    
        android {
          ...
        }
        ...
        
  3. keystoreProperties 객체에 저장된 서명 정보를 입력합니다.
        android {
          signingConfigs {
            config {
              keyAlias keystoreProperties['keyAlias']
              keyPassword keystoreProperties['keyPassword']
              storeFile file(keystoreProperties['storeFile'])
              storePassword keystoreProperties['storePassword']
            }
          }
          ...
        }
        ...
        
  4. 알림바에서 Sync Now를 클릭합니다.

앱 서명에 관해 자세히 알아보려면 앱 서명을 참조하세요.

앱 개발 단순화

다음 팁은 Android 앱 개발을 더 쉽게 만들어줍니다.

맞춤형 필드 및 리소스 값을 앱 코드와 공유

Gradle은 빌드 시간에 BuildConfig 클래스를 생성하므로 앱 코드가 현재 빌드에 관한 정보를 검사할 수 있습니다. buildConfigField() 메서드를 사용하여 Gradle 빌드 구성 파일의 BuildConfig 클래스에 맞춤형 필드를 추가하고 앱의 런타임 코드에서 해당 값에 액세스할 수도 있습니다. 마찬가지로, resValue()로 앱 리소스 값을 추가할 수 있습니다.

    android {
      ...
      buildTypes {
        release {
          // These values are defined only for the release build, which
          // is typically used for full builds and continuous builds.
          buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
          resValue("string", "build_time", "${minutesSinceEpoch}")
          ...
        }
        debug {
          // Use static values for incremental builds to ensure that
          // resource files and BuildConfig aren't rebuilt with each run.
          // If these rebuild dynamically, they can interfere with
          // Apply Changes as well as Gradle UP-TO-DATE checks.
          buildConfigField("String", "BUILD_TIME", "\"0\"")
          resValue("string", "build_time", "0")
        }
      }
    }
    ...
    

앱 코드에서 다음과 같이 속성에 액세스할 수 있습니다.

Kotlin

    ...
    Log.i(TAG, BuildConfig.BUILD_TIME)
    Log.i(TAG, getString(R.string.build_time))
    

자바

    ...
    Log.i(TAG, BuildConfig.BUILD_TIME);
    Log.i(TAG, getString(R.string.build_time));
    

속성을 manifest와 공유

경우에 따라 manifest와 코드에서 모두 동일한 속성을 선언해야 합니다(예: FileProvider의 권한을 선언하는 경우). 변경 사항을 적용하기 위해 동일한 속성을 여러 위치에서 업데이트하는 대신, 단일 속성을 모듈의 build.gradle 파일에서 정의하고 이 속성을 manifest와 코드에서 모두 사용할 수 있습니다(다음 샘플 참조). 자세히 알아보려면 manifest에 빌드 변수 삽입을 참조하세요.

    android {
      // For settings specific to a product flavor, configure these properties
      // for each flavor in the productFlavors block.
      defaultConfig {
        // Creates a property for the FileProvider authority.
        def filesAuthorityValue = applicationId + ".files"
        // Creates a placeholder property to use in the manifest.
        manifestPlaceholders =
          [filesAuthority: filesAuthorityValue]
          // Adds a new field for the authority to the BuildConfig class.
          buildConfigField("String",
                           "FILES_AUTHORITY",
                           "\"${filesAuthorityValue}\"")
      }
      ...
    }
    ...
    

manifest에서 다음과 같이 자리표시자에 액세스합니다.

    <manifest>
      ...
      <application>
        ...
        <provider
          android:name="android.support.v4.content.FileProvider"
          android:authorities="${filesAuthority}"
          android:exported="false"
          android:grantUriPermissions="true">
          ...
        </provider>
      </application>
    </manifest>
    

앱 코드의 FILES_AUTHORITY 필드에 액세스하는 것은 다음과 같습니다.

Kotlin

    ...
    val contentUri: Uri = FileProvider.getUriForFile(context, BuildConfig.FILES_AUTHORITY, myFile)
    

자바

    ...
    Uri contentUri = FileProvider.getUriForFile(getContext(),
      BuildConfig.FILES_AUTHORITY,
      myFile);