Android Gradle 물고 뜯고 씹고 맛보기

ee·2025년 1월 3일

Android

목록 보기
7/10

gradle이 뭐냐고 물어본다면 나는 음.. 잘 모르겠다. 그럼 gradle이 중요한가? 그 것도 잘 모르겠다. 아마 나같은 beginner들은 대부분 잘 모를 것 이다.

새로운 프로젝트를 생성하면 app 폴더에 build.gradle.kts 파일이 있을 뿐만 아니라 gradle 폴더가 따로 존재한다. 따라서 Gradle은 안드로이드 프로젝트에서 중요한 역할인 것은 확실하다. 그렇다면 Gradle은 정확히 어떤 역할을 할까?

짧게 말하자면 Gradle은 task Runner이다. 즉 어떤 결과를 얻기 위해 올바른 task를 올바른 순서로 build 되게 해준다. 예를 들어 앱을 안드로이드 디바이스에서 실행할 수 있게 해준다. Gradle은 앱을 배포하거나 설치하고 실행하는데 책임이 있지만 그렇다고 해서 Gradle 자체가 이러한 기능을 가져오는 것이 아니라 안드로이드 디바이스와 어떻게 소통하는지 또는 APK를 어떻게 설치하는 지를 아는 것이다.

groovy vs kotlin

Gradle은 groovy와 kotlin 두 종류가 있는데 groovy는 옛날 버전이고 조금 다른 문법을 가지고 있다. Gradle kotlin은 kotlin을 프로그래밍 언어로 사용하기에 타입 세이프티와 같은 코틀린 언어의 장점을 가져가면서 빌드 로직을 짤 수 있어 특정한 익스텐션을 구현하기 쉽다는 장점을 가지고 있다. 두 가지 모두 같은 일을 하지만 언어만 다를 뿐이다.

gradlew

gradlew는 실행가능한 파일이다. gradle이 실행하는 각 task들을 실행할 수 있다. 터미널에서 ./gradlew tasks를 입력하면 gradle이 실행할 수 있는 모든 task를 리스트로 보여준다. 우리가 빌드 버튼을 클릭하면 gradle은 실행해야할 모든 task들을 서로 영향을 주지않고 병렬적으로 실행해줄 것이다.

build.gradle.kts(project)

plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
    alias(libs.plugins.kotlin.compose) apply false
}

이 파일은 top level build file로 configuration을 추가해서 전체 프로젝트에 적용할 수 있다. 예를 들어 jetpack compose 플러그인을 추가한다.
이 플러그인들은 사실 코틀린 클래스이고 build logic을 담고 있다.

위 코드를 보면 apply false가 되어있는데 이는 모든 모듈에 불필요하게 플러그인이 적용되지 않게 하기 위해 일단 프로젝트 단위에서 플러그인을 선언하고 하위 모듈에서 이를 재사용하여 코드의 중복성을 줄이면서 효율적으로 플러그인을 적용할 수 있게 한다.

build.gradle.kts(module)

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
}

모듈단위에서 플러그인을 적용한다.

android {
    namespace = "com.example.untitled_capstone"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.untitled_capstone"
        minSdk = 23
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
}

android 블락: 안드로이드 애플리케이션을 설정하는데 사용된다.
namespace: 모듈의 패키지 이름으로 앱의 고유 식별자이다.
compileSdk: 애플리케이션을 빌드할 때 사용할 Android SDK 버전을 지정하며 최신 API를 사용할 수 있도록 빌드 도구가 SDK 34를 기반으로 컴파일한다.
applicationId: 애플리케이션의 고유 식별자로, Play Store에 게시될 때 사용된다. 따라서 앱을 배포한 이후에는 수정하면 다른 앱으로 인식하기 때문에 변경하면 안된다.
minSdk: 애플리케이션이 지원해야 하는 최소 sdk를 지정한다.
targetSdk: 테스트 되는 가장 높은 sdk 레벨을 지정한다.
versionCode: 앱의 내부 버전 번호로 업데이트 시 증가해야 한다.
versionName: 사용자에게 표시되는 앱의 버전이다.
testInstrumentationRunner: 테스트 실행 시 사용할 Runner 클래스이다.

buildTypes {
    release {
        isMinifyEnabled = false
        proguardFiles(
            getDefaultProguardFile("proguard-android-optimize.txt"),
            "proguard-rules.pro"
        )
    }
}

buildTypes: 각 빌드 타입의 설정을 정한다. 디폴트로 release build type이 있고 원한다면 debug나 create를 통하여 새로운 build variant를 생성할 수 있다.
release: 배포용 빌드 타입이다. 앱을 배포할 때 이 설정을 따른다.
isMinifyEnabled: 코드 난독화 여부를 정한다. true로 설정시 소스 파일이 알아 보기 어렵게 난독화된다.
proguardFiles: ProGuard 설정 파일을 지정하여 코드 난독화 및 최적화를 설정.
getDefaultProguardFile: 기본 ProGuard 설정 파일.
proguard-rules.pro: 사용자 정의 ProGuard 규칙 파일.

compileOptions {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

sourceCompatibility: 소스 코드의 Java 버전.
targetCompatibility: 빌드된 코드가 호환될 Java 버전.

kotlinOptions {
    jvmTarget = "11"
}

jvmTarget: Kotlin 컴파일러가 생성하는 JVM 바이트코드의 타겟 버전.

buildFeatures {
    compose = true
}

buildFeatures: 컴포즈라던지 viewbinding등 build feature을 지정하는데 사용.

dependencies {

    implementation(libs.androidx.core.ktx)
    ...
    debugImplementation(libs.androidx.ui.test.manifest)
}

프로젝트에 필요한 라이브러리들을 지정한다. implementation안에 이름은 카탈로그 형식으로 libs.versions.toml에 각 라이브러리의 버전과 이름이 선언되어 있다.

libs.versions.toml

Gradle의 Version Catalog 기능을 사용하는 데 필요한 파일이다. 의존성 버전을 한 곳에서 관리하여 유지보수가 쉬워진다.

[versions]
agp = "8.7.3"
kotlin = "2.0.0"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0"
composeBom = "2024.04.01"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

versions: 라이브러리의 버전을 선언.
libraries: 라이브러리의 그룹명을 선언하고 이름과 버전을 선언.
plugins: gradle 플러그인의 id와 버전을 선언.

Gradle modules and libraries

라이브러리는 어디서 가져오는 것인가? 라이브러리는 그저 모듈이다. 모듈은 프로젝트 내의 독립적인 컨테이너이며 Gradle에서 library로도 사용될 수 있다. 안드로이드 스튜디오에서 새로운 모듈을 생성할 수 있다.

Android sdk를 사용해야 하는 경우 android library를 사용하고 단순히 kotlin으로 빌드 로직을 짜는 경우라면 kotlin library를 사용한다.


새 모듈(라이브러리)를 생성하면 build.gradle.kts 파일을 가지고 있어 각 모듈마다 다른 종속성 및 설정을 적용할 수 있는 것을 알 수 있다. 이 모듈을 애플리케이션 모듈에서 사용하려면 build.gradle.kts 파일에 dependencies 블락에서 implementation(project(":app:mylibrary"))를 선언하면 된다.

settings.gradle.kts

pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "Untitled_Capstone"
include(":app")
include(":app:mylibrary")

settings.gradle.kts 파일은 Gradle 빌드 시스템에서 프로젝트와 모듈을 구성하고 관리하기 위한 설정을 정의한다.
pluginManagement: 어떤 레포지토리에서 플러그인을 검색할 지 설정한다.
google: android 관련 플러그인을 검색한다.
mavenCentral: 오픈소스 Java/kotlin 플러그인과 라이브러리를 검색한다.
content: 특정 그룹 이름 패턴에 따라 검색 범위를 제한한다.
dependencyResolutionManagement: 애플리케이션의 의존성 관리 방식과 저장소 설정을 정의한다.
repositoriesMode: FAIL_ON_PROJECT_REPOS로 설정하여 프로젝트 수준의 repositories 블록을 금지하고 모든 의존성은 전역 repositories에서만 검색하도록 강제한다.
include: 프로젝트에 포함될 모듈을 정의한다.

Build Config

buildTypes {
	debug {
		buildConfigField("String", "BASE_URL", "\"https://test.com\"")
		isMinifyEnabled = false
	}
	create("staging"){
		buildConfigField("String", "BASE_URL", "\"https://test.com\"")
		isMinifyEnabled = false
	}
	release {
		buildConfigField("String", "BASE_URL", "\"https://live.com\"")
		isMinifyEnabled = false
		proguardFiles(
			getDefaultProguardFile("proguard-android-optimize.txt"),
                    "proguard-rules.pro"
		)
	}
}
        
buildFeatures {
	buildConfig = true
}

각 build type에 buildConfigField를 추가하여 각 타입에 특정 url을 지정할 수 있다. 소스 코드에서 BuildConfig.BASE_URL로 지정하여 이 url을 가져올 수 있다. 그리고 이 값이 실제로 사용되어 build 된다면 Gradle은 build type에 따라 다른 url 값을 읽는다. 예를 들어 debug 타입으로 앱을 build 하면 BASE_URL은 https://test.com이 될 것이다.

Product Flavors

flavorDimensions += listOf("paid_status", "style")
productFlavors {
	create("free"){
		applicationIdSuffix = ".free"
		dimension = "paid_status"
	}
	create("paid"){
		applicationIdSuffix = ".paid"
		dimension = "paid_status"
	}
	create("green"){
		dimension = "style"
	}
	create("red"){
		dimension = "style"
	}
}

product flavors는 같은 앱에 다른 버전을 생성한다. 예를 들어 부분 결제가 있는 앱은 결제를 한 사용자와 결제하지 않은 사용자에 따라 앱을 다른 버전으로 보여줘야 할 것이다. 이 를 product flavors를 사용하여 free 버전과 paid 버전을 생성하고 suffix를 추가해 Gradle은 어떤 version을 사용하여 빌드할지 결정한다. dimension은 version의 종류를 의미하고 이는 freeGreen version, freeRed version 등으로 혼합되어 version이 생성된다.

정리

Gradle은 task Runner이다.
설정된 알맞은 build type, library, dependencies, plugins, sdk, version 등으로 build 되게 한다.

profile
정진이

0개의 댓글