
Android 빌드가 빨간 줄을 뱉어요 (Gradle의 늪에서 탈출하기)
안드로이드는 Xcode보다 낫다고요? Gradle 지옥에 빠져보면 그 말이 쏙 들어갈 겁니다. minSdkVersion 충돌, Multidex 에러, Namespace 변경(Gradle 8.0), JDK 버전 문제, 그리고 의존성 트리 분석까지 완벽하게 해결해 봅니다.

안드로이드는 Xcode보다 낫다고요? Gradle 지옥에 빠져보면 그 말이 쏙 들어갈 겁니다. minSdkVersion 충돌, Multidex 에러, Namespace 변경(Gradle 8.0), JDK 버전 문제, 그리고 의존성 트리 분석까지 완벽하게 해결해 봅니다.
로그인 화면을 만들었는데 키보드가 올라오니 노란 줄무늬 에러가 뜹니다. resizeToAvoidBottomInset부터 스크롤 뷰, 그리고 채팅 앱을 위한 reverse 팁까지, 키보드 대응의 모든 것을 정리해봤습니다.

안드로이드는 오는데 iOS는 조용합니다. 혹은 앱이 켜져 있을 때만 옵니다. Background/Terminated 상태 처리, APNs 인증서, 그리고 Notification Channel 설정까지 완벽하게 해결합니다.

서로 다른 인터페이스를 연결해주는 변환기. 레거시 시스템과 신규 시스템을 이어주는 가장 강력한 디자인 패턴.

분명히 클래스를 적었는데 화면은 그대로다? 개발자 도구엔 클래스가 있는데 스타일이 없다? Tailwind 실종 사건 수사 일지.

Flutter 프로젝트를 처음 클론받거나 라이브러리를 추가했을 때, 안드로이드 빌드가 멈추는 건 일상입니다.
터미널에는 빨간 글씨로 FAILURE: Build failed with an exception.이라고 뜨는데,
로그가 너무 길어서 뭘 봐야 할지도 모르겠습니다.
StackOverflow를 뒤져서 gradle-wrapper.properties 버전을 요리조리 바꿔보지만 해결되지 않습니다.
안드로이드 빌드 에러의 4대 천왕과, 그들을 잡는 고급 무기(명령어)를 소개합니다.
라이브러리를 추가하고 빌드를 돌렸는데 이런 에러가 뜹니다.
Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [...]
"네가 추가한 라이브러리는 안드로이드 19(KitKat) 이상이어야 돌아가는데, 너는 16(Jelly Bean)부터 지원한다고 설정해놨어. 말이 안 되잖아."
android/app/build.gradle 파일을 열어서 버전을 올려줍니다.
요즘(2025년)은 최소 21(Lollipop)이나 23(Marshmallow)은 되어야 합니다. 대부분의 라이브러리가 21 이상을 요구하기 때문입니다.
defaultConfig {
// minSdkVersion 16 <-- 삭제
minSdkVersion 23 // <-- 상향 조정 (Flutter 3.x 기본값 권장)
targetSdkVersion 34
}
또는 local.properties 파일에 변수를 선언하고 가져다 쓰는 방식이 더 깔끔합니다.
// android/local.properties
flutter.minSdkVersion=23
앱이 커지고 Firebase, Google Maps 같은 덩치 큰 라이브러리를 쓰다 보면 갑자기 이런 에러가 뜹니다.
Cannot fit requested classes in a single dex file (# methods: 65536 > 65536)
"안드로이드 실행 파일(dex) 하나에 담을 수 있는 함수(method) 개수가 65,536개를 넘었어. 터질 것 같아." (65536은 2의 16승, 즉 2바이트로 표현 가능한 최대 주소값입니다.)
파일을 여러 개로 쪼개서 담도록(Multi-dex) 설정을 켜주면 됩니다.
android/app/build.gradle:
defaultConfig {
// ...
multiDexEnabled true // 👈 이거 한 줄이면 끝
}
dependencies {
// 만약 에러가 계속되면 이것도 추가 (minSdkVersion 20 이하일 때 필수)
implementation "androidx.multidex:multidex:2.0.1"
}
최신 Flutter 프로젝트에서 Gradle 8.0 (AGP 8.0) 이상을 쓸 때 발생하는 에러입니다.
AndroidManifest.xml에 있는 package="com.example.app" 속성과 build.gradle의 설정이 충돌합니다.
android/app/build.gradle의 android { ... } 블록 안에 namespace를 명시해야 합니다.
android {
namespace "com.example.myapp" // 👈 패키지명을 여기로 이사시킴
compileSdk 34
// ...
}
이제 매니페스트 파일에서는 package 속성을 지워도 됩니다. AGP 8.0부터는 namespace가 R 클래스 생성의 기준이 됩니다.
Unsupported class file major version 61
또는
Execution failed for task ':app:compileReleaseJavaWithJavac'.
> invalid source release: 17
"Java 17로 짜여진 코드를 Java 8 컴파일러로 돌리려고 해?" 또는 그 반대 상황입니다. 최신 Flutter와 안드로이드 스튜디오는 Java 17을 표준으로 씁니다.
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
Mac 터미널에서 javac -version을 쳤을 때 17 버전이 나오는지 확인하세요.
안드로이드 스튜디오 설정(Settings > Build, Execution, Deployment > Build Tools > Gradle)에서도 Gradle JDK가 17로 잡혀있는지 확인해야 합니다.
./gradlew dependencies) 제대로 파보기가끔 "A 라이브러리가 B 라이브러리 1.0을 쓰고, C 라이브러리는 B 라이브러리 2.0을 써서 충돌"하는 경우가 있습니다. 이럴 때는 Gradle이 어떤 의존성을 가져오는지 눈으로 확인해야 합니다.
cd android
./gradlew app:dependencies
이 명령어를 치면 트리가 쫙 나옵니다.
debugRuntimeClasspath - Runtime classpath of compilation 'debug' (target (androidJvm)).
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0 -> 1.9.0 (*)
+--- com.google.firebase:firebase-analytics:21.3.0
| +--- com.google.android.gms:play-services-measurement:21.3.0
| | +--- com.google.android.gms:play-services-basement:18.1.0 -> 18.2.0
여기서 -> 1.9.0 (*) 같은 표시를 보면서 버전 충돌이나 불필요한 라이브러리가 포함되는지 감시할 수 있습니다.
"어제는 됐는데 오늘은 안 돼요"의 90%는 캐시 문제입니다. Gradle 데몬이 꼬였거나, 다운로드받은 jar 파일이 깨졌을 때 씁니다.
cd android
# 1. 프로젝트 내 빌드 폴더 삭제 (가벼운 청소)
./gradlew clean
# 2. 유저 홈의 Gradle 캐시 삭제 (강력한 청소 - 라이브러리 다시 다 받음)
rm -rf ~/.gradle/caches
# 3. Gradle 데몬 죽이기
./gradlew --stop
이 3콤보를 날리고 다시 빌드하면, 웬만한 "유령 에러"는 사라집니다.
multiDexEnabled true를 켜라.build.gradle에 namespace를 넣어라../gradlew app:dependencies로 범인을 찾아라.clean, rm -rf caches).Gradle은 느리고 복잡하고 짜증 나지만, 안드로이드 생태계를 지탱하는 거대하고 강력한 코끼리입니다. 코끼리를 다루는 법(명령어)을 익혀두면, 개발이 훨씬 쾌적해집니다.
Cloning a Flutter project or adding a library often breaks Android builds.
The terminal screams FAILURE: Build failed with an exception., and the logs are 500 lines long.
You copy-paste gradle-wrapper.properties versions from StackOverflow, confusingly replacing 7.5 with 8.0, but nothing works.
Let's catch the 4 Bosses of Android Build Errors and equip the Advanced Weapons (Commands) to defeat them.
Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [...]
"The library you added demands Android 19+ (KitKat), but your app claims to support Android 16 (Jelly Bean). That's illegal."
Open android/app/build.gradle and bump the version.
In 2025, 21 (Lollipop) or 23 (Marshmallow) is the de facto standard.
defaultConfig {
// minSdkVersion 16 <-- Delete
minSdkVersion 23 // <-- Upgrade
targetSdkVersion 34
}
If you stick to 16, you can't use 90% of modern libraries (Firebase, Google Maps, etc.).
Cannot fit requested classes in a single dex file (# methods: 65536 > 65536)
"Your executable file (dex) is full. It can only hold 65,536 methods (2^16 addresses)." This happens when you add heavy dependencies like Firebase or AWS Amplify.
Enable Multi-dex (splitting code into multiple files).
android/app/build.gradle:
defaultConfig {
multiDexEnabled true // 👈 The magic line
}
dependencies {
// If you support ancient Android versions (SDK < 21), add this too:
implementation "androidx.multidex:multidex:2.0.1"
}
With Gradle 8.0 (AGP 8.0), Google changed the rules.
Defining package in AndroidManifest.xml is deprecated/removed for generating R classes.
Move the package definition to android/app/build.gradle utilizing the namespace property:
android {
namespace "com.example.myapp" // 👈 Define it here
compileSdk 34
// ...
}
If you declare it in both places improperly, Gradle throws a "Namespace not specified" or conflict error.
Unsupported class file major version 61
You are mixing Java versions.
You are forcing a Java 17 library to run on Java 8 JVM, or vice versa.
Align everything to Java 17 (The current standard for Flutter/Android).
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
Android Studio > Settings > Build Tools > Gradle > Gradle JDK.java -version../gradlew dependencies)Sometimes, Library A uses Library B (v1.0) and Library C uses Library B (v2.0). Conflict! Gradle tries to resolve it, but sometimes fails. You need to See the Matrix.
cd android
./gradlew app:dependencies
This prints the entire dependency tree.
debugRuntimeClasspath - Runtime classpath of compilation 'debug'.
+--- com.squareup.retrofit2:retrofit:2.9.0
| +--- com.squareup.okhttp3:okhttp:3.14.9 -> 4.9.0 (*)
You can trace exactly which library is pulling in the old version of OkHttp or Kotlin and causing issues.
If it worked yesterday but fails today, it's a corrupted cache. Don't just restart your computer. Nuke the cache.
cd android
# 1. Clean project artifacts
./gradlew clean
# 2. Delete global Gradle cache (forces re-download of all jars)
rm -rf ~/.gradle/caches
# 3. Stop the Gradle Daemon
./gradlew --stop
Running ./gradlew --stop is crucial because the Gradle Daemon (a background process) holds onto file locks and memory. Killing it forces a fresh start.
kapt vs ksp?A: kapt is in maintenance mode. Migrate to KSP (Kotlin Symbol Processing) for faster builds. Most libraries (Room, Moshi) support KSP now.
A: This happens when two dependencies include the same class. Run ./gradlew app:dependencies and exclude the transitive dependency:
implementation ('com.example.lib:1.0.0') {
exclude group: 'com.duplicate.group', module: 'duplicate-module'
}
A:
gradle.properties: org.gradle.configuration-cache=true.org.gradle.jvmargs=-Xmx4g.An open-source build automation tool that is designed to be flexible enough to build almost any type of software. Android Studio uses a specialized plugin called the Android Gradle Plugin (AGP) to integrate Gradle into the IDE. Unlike Ant or Maven, Gradle uses a Groovy or Kotlin-based DSL (Domain Specific Language).
Android apps are compiled into .dex files, which are zipped into a single .apk file. The Dalvik Virtual Machine (DVM) or Android Runtime (ART) executes these files. The "65k method limit" refers to the limitation of addressing methods within a single dex file.
A support library and build configuration that allows an app to be split into multiple .dex files. The primary dex file (classes.dex) contains the code necessary to boot the app, while secondary dex files (classes2.dex, etc.) are loaded dynamically.
ProGuard is a tool for code shrinking, optimization, and obfuscation. R8 is Google's replacement for ProGuard, offering faster builds and better optimization. It removes unused code and resources, making the APK smaller and harder to reverse-engineer.
The process where Gradle merges all AndroidManifest.xml files from your main app module and all included libraries into a single manifest file for the final APK. Conflicts often arise here (e.g., conflicting minSdkVersion or permissions).
A long-lived background process that executes builds much faster than starting a new JVM for every build. It caches data about project structure, files, tasks, and more. Sometimes it consumes too much memory (RAM) and needs to be killed or configured with higher heap size (-Xmx).
Dalvik: The original runtime used by Android (up to KitKat 4.4). It used JIT (Just-In-Time) compilation. ART (Android Runtime): The modern runtime (Lollipop 5.0+). It uses AOT (Ahead-Of-Time) compilation, installing compiled native machine code during app installation, resulting in faster app launch and execution.