목 차
1. 빌드 시스템의 개요.
2. Gradle이 무엇이고 왜 중요한가?
3. Android 애플리케이션의 Gradle 빌드 구조
4. AOSP 플랫폼 빌드 : Soong + Kati + Ninja
5. Gradle 최적화 및 고급 기능
6. 왜 Flutter 개발자도 Android 빌드 시스템을 알아야 할까??
7. Flutter 프로젝트 안의 Android 빌드 시스템 구조
8. Gradle과 AGP(안드로이드 Gradle Plugin)
8. Flutter 빌드 -> Gradle 빌드 -> AOSP 빌드의 연결 고리
10. Flutter 개발자가 자주 만나는 빌드 문제들.
11. 빌드 Variants와 Product Flavors
12. NDK 빌드와 Flutter
13. 빌드 속도 최적화
14. CI/CD 파이프라인
15. Google Play의 API 레벨 요구사항은 어떻게 대응해야 할까??
16. Build Variants와 Product Flavors는 어떻게 활용하는가??
17. Package Name은 어떻게 설정하고 변경할까?
18. Multidex 에러는 어떻게 해결해야할까??
19. Desugaring은 무엇이고 언제 필요할까
20. Android 권한은 어떻게 관리해야할까
21. Keystore 관리와 앱 서명은 어떻게 해야할까
22. Google Play App Signing과 SHA-1 문제는 어떻게 해겨랗까
23. AndroidManifest.xml 충돌은 어떻게 해결할까
24. Firebase 설정은?
소스 코드를 "실행 가능한 앱/APK/AAB" 로 변환하는 시스템
여러 도구(컴파일러, 로거처리기, 리소스 처리기 등)를 연쇄적으로 조정하는 역할 !
앱 단위 : Gradle + Android Gradle Plugin(AGP)
플랫폼(AOSP) 단위 : Soong, Kati, Ninja 기반
배경 및 역사 : GNU Make -> Soong + Ninja +Kati
Gradle은 소스 코드를 APK나 AAB 파일로 변환하는 빌드 자동화 도구입니다.
Flutter Android 앱도 결국 Android 앱이기 때문에 Gradle을 사용합니다.
android/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin/.../MainActivity.kt
│ │ │ └── res/
│ │ ├── debug/
│ │ └── profile/
│ └── build.gradle # 앱 레벨 Gradle 설정
├── gradle/
│ └── wrapper/
│ └── gradle-wrapper.properties # Gradle 버전 정의
├── build.gradle # 프로젝트 레벨 Gradle 설정
├── settings.gradle
└── local.properties
이 둘은 호환성이 있어야 합니다.
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.2.0' // AGP 버전
}
}
Gradles(Gradle Wrapper)가 뭐길래 꼭 써야 하나요?
사용 방법
# Mac/Linux
./gradlew clean
./gradlew build
# Windows
gradlew.bat clean
gradlew.bat build
# 잘못된 방법 (시스템 gradle 사용)
gradle clean # 이렇게 하지 마세요!
# Wrapper를 통해 Gradle 버전 업그레이드
# 중요: 이 명령어를 2번 실행해야 합니다!
./gradlew wrapper --gradle-version=8.2
./gradlew wrapper --gradle-version=8.2 # 두 번째 실행
# 또는 gradle-wrapper.properties 직접 수정
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
Gradle의 역할은 한 마디로,
"코드를 앱(프로덕트)으로 만들어주는 모든 작업을 관리하는 빌드 툴"이라고 할 수 있습니다.
Gradle이 Android를 빌드할 수 있게 도와주는 것이 바로 "Android Gradle Plugin (AGP) 입니다.
MyApp/
├── build.gradle # 프로젝트 수준
├── settings.gradle # 프로젝트 세팅
├── gradle.properties # Gradle 속성
├── gradle/ # Wrapper
│ └── wrapper/
│ └── gradle-wrapper.properties
└── app/
├── build.gradle # 모듈 수준
└── src/
위치 : MyApp/build.gradle
주요 역할
ex
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.1.2")
}
}
위치 : MyApp/settings.gradle 또는 settings.gradle.kts
주요 역할
예시 :
include ':app'
위치 : MyApp/gradle.properties
주요 역할
예시 :
org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true
android.enableJetifier=true
위치 : MyApp/gradle/wrapper/gradle-wrapper.properties
주요 역할
예시 :
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
namespace 'com.example.myapp'
compileSdk 34
defaultConfig {
applicationId "com.example.myapp"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'
), 'proguard-rules.pro'
}
}
flavorDimensions "version"
productFlavors {
free {
dimension "version"
applicationIdSuffix ".free"
}
paid {
dimension "version"
applicationIdSuffix ".paid"
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
}
AOSP(Android Open Source Project)는 Android 플랫폼 자체를 빌드하기 위한 전체 소스 코드와 도구들의 모음.
- 우리가 흔히 사용하는 Android 시스템(커널 제외) 전체를 제작 가능.
💡 이 빌드는 앱이 아닌 OS 자체를 빌드하는 것!
구성 요소 | 역할 |
---|---|
device/ , vendor/ , kernel/ | 디바이스별 설정 및 드라이버 |
frameworks/ , system/ | Android 플랫폼의 핵심 코드 |
packages/ | 기본 앱들 (Settings, Launcher 등) |
build/ | 빌드 시스템 관련 스크립트 |
Android.mk , Android.bp | 모듈 빌드 스크립트 |
이름 | 역할 | 설명 |
---|---|---|
Soong | 프론트엔드 | Android.bp 기반의 빌드 시스템 (Go로 작성) |
Kati | 중간 파서 | Android.mk 파일을 Ninja 포맷으로 변환 (Make 호환) |
Ninja | 백엔드 | 실제 빌드 수행 (컴파일, 링크 등 빠르게 병렬 처리) |
★ 기존 GNU Make 기반 빌드에서 성능과 유지보수성을 개선한 구조
$ source build/envsetup.sh
$ lunch aosp_x86_64-eng
$ m
각 단계의 의미는
단계 | 설명 |
---|---|
envsetup.sh | 빌드 환경 변수 등록 |
lunch | 빌드 대상 디바이스와 variant 선택 |
m | 빌드 실행 (make wrapper → soong_ui → ninja 실행) |
Android는 하나의 소스 트리로 여러 기기를 빌드할 수 있게 설계.
이를 위한 Product와 Variant 개념이 존재.
ex)
lunch aosp_arm64-eng
AOSP/
├── build/ # 빌드 시스템 관련 (core, soong, etc)
├── device/ # 디바이스 정의
├── vendor/ # 벤더 드라이버, HAL
├── kernel/ # 리눅스 커널 소스
├── system/ # core 시스템 컴포넌트
├── frameworks/ # Android Framework (Java)
├── packages/ # 기본 앱
├── prebuilts/ # 미리 빌드된 바이너리
├── external/ # 외부 오픈소스
├── Android.bp / mk # 각 디렉토리의 빌드 정의
빌드 후 생성되는 주요 이미지 파일.
| 이미지 | 설명 |
| -------------- | -------------------------------------------- |
| `system.img` | `/system` 파티션 이미지 (framework, system apps 등) |
| `vendor.img` | `/vendor` 파티션 (HAL, 드라이버 등) |
| `boot.img` | 커널 + initramfs |
| `recovery.img` | 복구 파티션 |
| `userdata.img` | 초기 사용자 데이터 파티션 |
항목 | Android.mk | Android.bp |
---|---|---|
문법 | Makefile | JSON-like DSL |
처리 도구 | Kati → Make | Soong |
유지보수 | 복잡, 오래됨 | 단순화, 현대화 |
사용 위치 | legacy 모듈 | 신규 Soong 기반 모듈 |
-> 점점 Android.mk는 사라지고 Android.bp로 전환 중.
명령어 | 역할 |
---|---|
m / mm | 전체 / 현재 디렉토리만 빌드 |
mmm path | 특정 경로 모듈만 빌드 |
m MODULE_NAME | 특정 모듈만 빌드 |
m installclean | 빌드 캐시 초기화 |
m clean | 전체 클린 빌드 |
항목 | 내용 |
---|---|
목적 | Android 플랫폼(시스템) 전체 빌드 |
결과물 | system.img, boot.img 등 |
핵심 툴 | Soong + Kati + Ninja |
빌드 정의 | Android.bp (신규), Android.mk (레거시) |
빌드 흐름 | soong_ui → .ninja 생성 → ninja 실행 |
기기 정의 | device/ , vendor/ 디렉토리에서 구성 |
빌드 대상 | lunch 명령으로 Product + Variant 설정 |
도구 | 문제점 |
---|---|
Make (Android.mk) | 빌드 의존성 복잡도 증가, 성능 저하, 유지보수 어려움 |
Gradle | 앱 수준 빌드에 최적화되어 있으며, 플랫폼 전체를 다루기엔 부적합 |
-> 빌드 속도, 병렬성, 명확한 의존성 관리가 반드시 필요해짐.
구성요소 | 역할 | 언어 | 특징 |
---|---|---|---|
Soong | 빌드 시스템의 프런트엔드 | Go | Android.bp 파서 및 의존성 관리 |
Kati | 레거시 Android.mk 호환 | C++ | Makefile → Ninja로 변환 |
Ninja | 빌드 백엔드 실행기 | C++ | 빠르고 단순한 빌드 수행 |
Soong은 Android에서 기존 Make 시스템을 대체하기 위해 도입된 현대적인 빌드 시스템.
Android.bp 파일 파싱
모듈 간 의존성 분석
빌드 규칙을 생성
Ninja 빌드 파일 (build.ninja) 생성
즉, Soong은 "어떻게 빌드할 것인가"를 정의해서 Ninja가 실행할 수 있도록 만들어주는 도구 !
cc_library {
name: "libhello",
srcs: ["hello.cpp"],
shared_libs: ["liblog"],
}
위 코드처럼 선언하면 Soong은
이것들을 분석해서 build.ninja 파일에 변환.
Kati는 Makefile(=Android.mk)을 Ninja 형식으로 변환하는 GNU Make 호환 빌드 파서
Google은 점진적으로 Android.mk → Android.bp 전환 중이지만 완전한 전환은 아직 진행형
Ninja는 Soong과 Kati가 만든 .ninja 파일을 실제로 실행하는 초고속 빌드 툴
| 항목 | 설명 |
| ------ | --------------------------- |
| 의존성 관리 | `.ninja` 파일 기반으로 처리됨 |
| 속도 | Make 대비 매우 빠름 (수천 파일 병렬 처리) |
| 추상화 | 없음. 순수 실행만 담당 |
| 실행 위치 | `out/` 디렉토리 하위에서 실행됨 |
Android.bp Android.mk
│ │
[Soong] [Kati]
│ │
└─────┬──────┬─────┘
▼
build.ninja
│
[Ninja]
▼
바이너리 / 이미지 생성
$ source build/envsetup.sh
$ lunch aosp_x86_64-eng
$ m
→ m 은 결국 soong_ui를 실행하고
→ soong_ui는 Soong → Kati → Ninja를 호출
→ Ninja가 모든 작업을 병렬로 실행해 빌드 완료
out/
├── build.ninja ← Ninja 실행용 빌드 스크립트
├── target/
│ └── product/
│ └── aosp_x86/
│ ├── system.img
│ ├── boot.img
│ └── ... (파티션 이미지들)
├── soong/
│ └── .intermediates/ ← 각 모듈별 중간 결과물
| 도구 | 위치 | 입력 | 출력 | 핵심 기능 |
| ----- | --------------------- | ----------- | ----------- | ----------- |
| Soong | `build/soong` | Android.bp | build.ninja | 신규 모듈 정의 파싱 |
| Kati | `prebuilts/.../kati` | Android.mk | ninja 파일 | 레거시 모듈 변환 |
| Ninja | `prebuilts/.../ninja` | build.ninja | 실제 실행 | 병렬 빌드 |
Google Play는 매년 타겟 API 레벨을 올리도록 요구합니다.
2025년 8월 31일부터는 모든 신규 앱과 업데이트가 Android 15(API 레벨 35) 이상을 타겟해야합니다.
android {
compileSdkVersion 35 // 컴파일 시 사용할 SDK
defaultConfig {
applicationId "com.example.myapp"
minSdkVersion 21 // 최소 지원 Android 버전
targetSdkVersion 35 // 타겟 Android 버전 (2025년 8월 31일 필수)
versionCode 1
versionName "1.0.0"
}
}
minSdkVersion (최소 SDK 버전)
targetSdkVersion (타겟 SDK 버전)
android {
// 하드코딩 대신 Flutter 권장 버전 사용
compileSdkVersion flutter.compileSdkVersion
defaultConfig {
applicationId "com.example.myapp"
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode 1
versionName "1.0.0"
}
}
android {
// 특정 버전이 필요하면 직접 지정
compileSdkVersion 35
defaultConfig {
// 더 낮은 최소 버전이 필요한 경우
minSdkVersion 19 // Flutter 기본값보다 낮게
// Google Play 요구사항에 맞춰 직접 설정
targetSdkVersion 35
}
}
# Flutter SDK의 기본 설정값 확인
cat $FLUTTER_ROOT/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy | grep -E "(compileSdk|minSdk|targetSdk)"
많은 개발자들이 개발 서버와 운영 서버를 사용하는데,
매번 코드를 수정하며 빌드하는 것은 코드 안전성 측면에서 매우 위험.
android {
flavorDimensions "environment"
productFlavors {
dev {
dimension "environment"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
// 개발 서버 URL 등을 buildConfigField로 정의
buildConfigField "String", "API_URL", '"https://dev-api.example.com"'
}
staging {
dimension "environment"
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
buildConfigField "String", "API_URL", '"https://staging-api.example.com"'
}
prod {
dimension "environment"
buildConfigField "String", "API_URL", '"https://api.example.com"'
}
}
}
android/app/src/
├── main/
│ ├── AndroidManifest.xml # 기본 Manifest
│ ├── res/
│ │ ├── mipmap-hdpi/
│ │ │ └── ic_launcher.png
│ │ └── values/
│ │ └── strings.xml
│ └── kotlin/
├── dev/
│ ├── AndroidManifest.xml # dev용 Manifest (선택적)
│ └── res/
│ ├── mipmap-hdpi/
│ │ └── ic_launcher.png # dev용 아이콘
│ └── values/
│ └── strings.xml # dev용 앱 이름
├── staging/
│ └── res/
└── prod/
└── res/
<resources>
<string name="app_name">My App</string>
</resources>
<resources>
<string name="app_name">My App (Dev)</string>
</resources>
<resources>
<string name="app_name">My App (Staging)</string>
</resources>
각 flavor 폴더의 res/mipmap-* 디렉토리에 다른 아이콘을 넣으면 자동으로 적용됩니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- dev 환경에서만 필요한 디버깅 권한 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application>
<!-- 개발 환경에서만 HTTP 허용 -->
<meta-data
android:name="android.network-security-config"
android:resource="@xml/network_security_config_dev" />
</application>
</manifest>
충돌입 라생 시, tools:replace를 사용해 덮어쓰기 가능.
Flutter에서 실행.
# 개발 환경으로 실행 (dev 아이콘과 이름 사용)
flutter run --flavor dev
# 운영 환경으로 빌드 (prod 설정 사용)
flutter build apk --flavor prod
Dart 코드에서 접근하려면 별도 설정이 필요합니다.
보통 flutter_flavor 패키지를 사용합니다.
// android/app/build.gradle
defaultConfig {
applicationId "com.example.myapp" // 이것이 Package Name
}
defaultConfig {
applicationId "com.mycompany.awesomeapp" // 원하는 이름으로 변경
}
android/app/src/main/kotlin/com/example/myapp/
→ android/app/src/main/kotlin/com/mycompany/awesomeapp/
package com.mycompany.awesomeapp // 패키지명 변경
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
: Multidex 에러는 흔히 이런 식의 에러 메시지로 나타남.
com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex
혹은
Cannot fit requested classes in a single dex file
이 오류 메시지의 의미는??
Android 앱은 classes.dex라는 파일에 자바 바이트코드를 담아서 빌드합니다.
Dalvik/ART의 single DEX limit -> 하나의 DEX 파일에 최대 65536 메서드 참조 가능.
앱이 너무 커져서, 이 참조 가능 한도를 초과하면 에러가 발생하게 됩니다.
Flutter 프로젝트로 네이티브 라이브러리, 여러 Plugin, Firebase 등을 많이 쓰면
금방 이 한도를 넘어서게 됩니다.
minSdkVersion ≥ 21 이라면 기본적으로 multidex 지원됨 (ART가 native로 멀티덱스 로딩 가능)
defaultConfig {
minSdkVersion 21
}
-만약 20 이하라면, Legacy multidex 설치 필요
(1) Gradle 의존성 추가
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}
(2) multiDexEnabled 설정
android {
defaultConfig {
...
multiDexEnabled true
}
}
(3) Application 클래스 수정.
Applicaton 클래스를 Flutter에선 보통 생성해줘야 합니다.
ex)
package com.example.myapp;
import io.flutter.app.FlutterApplication;
import androidx.multidex.MultiDex;
import android.content.Context;
public class MainApplication extends FlutterApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
그리고서 AndroidManifext.xml에 아래처럼 Application 이름을 등록해줍니다.
<application
android:name=".MainApplication"
...>
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
그리고서 build,gradle:
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
android {
defaultConfig {
minSdkVersion 21
multiDexEnabled true
}
}
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}
minSdkVersion ≥ 21 이면 필요 없음
20 이하라면 반드시 필요
import io.flutter.app.FlutterApplication;
import androidx.multidex.MultiDex;
import android.content.Context;
public class MainApplication extends FlutterApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
상황 | 조치 |
---|---|
minSdkVersion ≥ 21 | multiDexEnabled true 설정만으로 OK |
minSdkVersion ≤ 20 | - multidex 라이브러리 추가 - MainApplication 생성 |
앱 사이즈 줄이고 싶음 | R8(Proguard) 활성화 |