이동식 저장소

[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에만 속할 수 있다. 예를 들어 특정 코드를 testandroidTest 모두에서 사용할 수는 없다. Android Studio 내부 구현 자체가 그렇게 구현돼 있다고 한다.

Source set 탐색 순서

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

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

더 구체적인 source set이 우선한다고 보면 된다. 예를 들어 이름이 같은 파일이 demoDebugdebug에 모두 존재한다면, 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