Build Variant
ㅇ Build Variant란?
단일 프로젝트에서 다양한 버전의 앱을 만드는 것을 의미한다.
예를 들어, 게임을 한다고 할 때, 무료 버전으로 플레이 하다가 인앱 구매를 통해 유료로 변경하면 컨텐츠가 늘어나는데, 이런 경우 사용하는 것이 build variant이다.
여러개의 앱을 개발해서 무료와 유료로 배포를 하는 것이 아니라, 필요에 따라 버전만 다르게 빌드하면 된다.
개발자가 build variant를 직접 구성하는게 아니며, build variant를 형성하는 빌드 유형과 제품 버전을 구성하면 Gradle이 특정 규칙 세트를 사용해 다양한 빌드 설정(설정, 코드, 리소스)을 조합한다.
Build Variant = Build Type + Product Flavor
=> Android Gradle 플러그인은 Build Type과 Product Flavor를 결합해 Build Variant를 생성한다.
Android Studio에서는 Build Variants 탭에서 Variant 선택 가능
: View > Tool Windows > Build Variants
ㅇ Build Type
- 앱의 기본적인 빌드 설정을 정의하는 것
- build.gradle.kts 파일의 android 블록 내에 Build Type 만들고 구성한다.
ex)
android {
buildTypes {
getByName("debug") {
isDebuggable = true // 디버깅 가능
}
getByName("release") {
isMinifyEnabled = true // 코드 난독화(ProGuard/R8) 활성화
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
- Android 프로젝트에서 기본적으로 제공하는 두 가지 Build Type
1) Debug : 디버깅 목적. *debugging symbol과 log가 포함 -> 개발 과정에서 사용되는 정보 포함
2) Release : 배포 목적. *최적화와 난독화(ProGuard)가 적용 -> 성능과 보안을 강화
* debugging symbol이란?
- 개발자가 디버깅 과정에서 앱의 내부 동작을 추적할 수 있도록 돕는 정보
- 주로 컴파일 과정에서 생성, 실행 파일이나 라이브러리에 포함됨
ex) Stack Trace 정보 - 오류가 발생한 파일과 라인 번호, Logcat에서 변수 추적 - 변수 값과 디버깅 정보 등
* 최적화와 난독화(ProGuard)
1. 최적화
- 컴파일 과정에서 앱의 성능 개선을 목적으로 코드를 변환, 제거하는 작업
- 불필요한 변수, 메서드 호출, 루프 등을 제거해 실행 속도를 높이고 메모리 사용량을 줄임
ex)
val x = 5
val y = 10
return x+y=> return 15
2. 난독화(ProGuard)
- 앱의 코드가 역컴파일 될 경우, 보안 목적으로 코드를 읽기 어렵게 변환하는 과정
- 클래스, 메서드, 변수 이름을 읽기 어려운 형태로 변환
ex)
class User{
fun getName(): String{
return "John"
}
}=> class a{
fun b(): String{
return "John"
}
}
ㅇ Product Flavor
- 빌드 변수를 추가로 설정해 variant를 정의하는 것
- build.gradle.kts 파일의 android 블록 내에 Product Flavors를 만들고 구성한다.
ex)
android {
flavorDimensions.add("version") // Flavor 그룹 이름
productFlavors {
create("free") { // 무료 버전 앱
dimension = "version"
applicationIdSuffix = ".free"
versionNameSuffix = "-free"
}
create("paid") { // 유료 버전 앱
dimension = "version"
applicationIdSuffix = ".paid"
versionNameSuffix = "-paid"
}
}
}
ㅇ 동적 의존성 추가 방식 - "${A_FLAVOR}Implementation"
코드를 보던 중, flavor를 검색해보니 아래와 같은 표현이 등장하였다.
build.gradle.kts 파일에서
dependencies {
...
"${A_FLAVOR}Implementation"(libs.ui.a)
"${B_FLAVOR}Implementation"(libs.ui.b)
}
이런 방식으로 사용 중이었다.
이 표현은 Build Type이나 Product Flavor에 따라 특정 의존성을 선택적으로 추가하는 방식으로,
${A_FLAVOR}와 ${B_FLAVOR}는 Build Variant에 따라 동적으로 다른 의존성을 적용하기 위해 사용된다.
A_FLAVOR와 B_FLAVOR는 빌드 시점에 사용될 플레이버 이름이다.
${A_FLAVOR}는 A_FLAVOR 변수의 이름을 가져오는 것이고, 그 플레이버 이름이 a_flavor라고 한다면
a_flavorImplementation(libs.ui.a) 라는 dependency를 추가한다는 의미가 된다.
그런데 A_FLAVOR를 선택한 경우에는 "${A_FLAVOR}Implementation"(libs.ui.a) 의존성이 추가되고,
B_FLAVOR를 선택한 경우에는 "${B_FLAVOR}Implementation"(libs.ui.b) 의존성이 추가되는 방식이다.
이런 방식으로 Build Variant를 동적으로 처리하면,
1. 여러 flavor에 따라 달라지는 의존성을 한 번에 처리 가능하고,
2. 특정 환경 변수나 스크립트를 통해 동적으로 Build Variant를 변경 가능하다.
다만, 유지보수성과 가독성을 위해 명시적으로 작성하는 것이 선호되고,
flavor가 정확히 정의되지 않거나 오타가 있으면 run-time error가 발생할 수 있어 조심해야 한다.