이동식 저장소

[Android] Build variant 심화 본문

Primary/Android

[Android] Build variant 심화

해스끼 2022. 7. 21. 17:36

Product flavor 결합하기

Flavor를 여러 그룹으로 나눌 수 있다. 예를 들어 1) API 레벨에 따라 2) 체험판 여부에 따라 포함되는 코드를 다르게 하고 싶다면, flavor dimension을 여러 개 만들고 각 dimension에서 flavor를 하나씩 고르면 된다. 고등학교 확통 시간에 배웠던 윗도리 3개와 바지 4개를 입는 경우의 수와 비슷하다.

위의 예시를 gradle로 구현해 보자.

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

  // flavor 그룹. 모든 flavor는 적어도 하나의 flavor dimension에 속해야 한다.
  // flavor를 고를 때 여기에 나열된 순서대로 고른다.
  flavorDimensions "api", "mode"

  productFlavors {
    demo {
      // mode 그룹에 속함
      dimension "mode"
      ...
    }

    full {
      dimension "mode"
      ...
    }

    // api flavors
    minApi24 {
      dimension "api"
      minSdkVersion 24
      // 기기가 최신 API 버전에 대응하는 앱을 찾을 수 있도록
      // versionCode를 최신 API부터 내림차순으로 설정한다.
      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"
      ...
    }
  }
}
...

Flavor를 분류하는 dimension 2개를 만들고, 각 dimension에 속하는 flavor를 선언하였다.

 

Build variant는 ``<product-flavor><build-type>``으로 만들어진다고 했다. 그런데 flavor dimension이 여러 개 존재하므로 ``<product-flavor>`` 부분은 다시 ``<api><mode>``로 나뉜다. 따라서 최종 build variant는 다음의 형식으로 만들어진다.

 

``<api><mode><build-type>``

 

이렇게까지 나누는 이유는 flavor 또는 build type에만 속하는 source set을 정의할 수 있기 때문이다. 앱의 핵심 부분은 ``main``에서 개발하고, API 레벨에 따라 구현이 다른 부분은 각 flavor의 source set에서 구현할 수 있다.

 

어, ``main``이라고? 그렇다. ``src``에 있는 그 ``main`` 맞다. 사실 여기 있는 폴더 각각이 모두 build variant이다. ``main``은 모든 build variant이 공유하는 코드를, ``test``는 local test 코드를 담고 있는 build variant인 것이다.

특정 조합만 무시하기

Flavor의 특정 조합이 불필요하다면, ``variantFilter`` 블럭 안에 무시하고 싶은 조합의 조건을 정의할 수 있다.

android {
  ...
  buildTypes {...}

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

  variantFilter { variant ->
      def names = variant.flavors*.name
      // Build type 이름 검사법: variant.buildType.name == "<buildType>"
      if (names.contains("minApi21") && names.contains("demo")) {
          // 무시
          setIgnore(true)
      }
  }
}
...

 

Source set 정의하기

위에서 말했다시피 특정 flavor, build type, build variant별로 고유한 source set을 만들 수 있다. 공통 기능은 ``main``에서 개발하고, 선택적으로 제공되는 기능은 source set에서 개발할 수 있다. 디버깅 코드를 ``debug``의 source set에 작성한다던가.

 

Source set을 어디에 정의해야 하는지 궁금하다면 Gradle의 ``sourceSets`` 작업을 실행하자. Android Studio 오른쪽 위의 Gradle 탭을 누르고, 코끼리 버튼을 눌러 ``gradle sourceSets``를 입력하면 된다.

오른쪽부터 차례대로 누르자

친절하게 알려준다.

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

주의: 하나의 폴더는 하나의 source set에만 속할 수 있다. 예를 들어 특정 코드를 ``test``와 ``androidTest`` 모두에서 사용할 수는 없다. Android Studio 내부 구현 자체가 그렇게 구현돼 있다고 한다.

Source set 탐색 순서

``demoDebug`` build variant를 빌드할 때, 다음의 순서대로 코드가 탐색된다.

  • ``demoDebug`` (build variant)
  • ``debug`` (build type)
  • ``demo`` (product flavor)
  • ``main`` (main)

더 구체적인 source set이 우선한다고 보면 된다. 예를 들어 이름이 같은 파일이 ``demoDebug``와 ``debug``에 모두 존재한다면, ``demoDebug``의 클래스가 사용된다. 

 

주의: 여러 build type에 이름이 같은 파일을 정의할 수는 있지만, build type과 main 모두에 정의해서는 안 된다.

 

Flavor dimension이 여러 개 존재한다면, 모든 flavor의 조합마다 source set을 정의해야 한다. 즉 모든 ``<product-flavor>``에 대응하는 source set이 존재해야 한다.

의존성 정의

라이브러리 등 의존성을 정의할 때 특정 build variant만 의존성을 사용하도록 할 수 있다. 예를 들어 의존성을``debugImplementation``으로 정의하면, 해당 라이브러리는 ``debug``에서만 사용할 수 있다.

dependencies {
    // "mylibrary" 모듈을 "free" flavor의 의존성으로 정의한다.
    freeImplementation project(":mylibrary")

    // 라이브러리를 "test"에서만 사용할 수 있게 정의한다.
    testImplementation 'junit:junit:4.12'

    // 라이브러리를 "androidTest"에서만 사용할 수 있게 정의한다.
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
당연하게 여겼던 코드의 의미를 하나씩 깨닫고 있다. 이게 그런 의미였구나.

참고

 

빌드 변형 구성  |  Android 개발자  |  Android Developers

빌드 변형을 구성하여 단일 프로젝트에서 여러 버전의 앱을 만드는 방법을 알아보세요.

developer.android.com

Comments