[Android] Cannot fit requested classes in a single dex file 해결 방법 및 원인 분석

JoJo Develog·2020년 3월 18일
7

Android Issues

목록 보기
1/4
post-thumbnail

개인 토이 프로젝트를 진행하고 있습니다만, 어제 흔히 말하는
“어제는 돌아갔는데 왜 오늘은 안 되는 거지?🤔” 하는 에러가 발생됐습니다.

이전에도 앱 개발을 하면서 해결한 적이 있었던 에러지만 꽤 시간이 지난터라 해결방법을 까먹고 말았습니다. 😑자랑이다
해외사이트와 구글 공식 안드로이드 개발자 사이트를 참조하여 이 문제를 해결했고 그 내용을 공유하고자 포스팅합니다.

1. 에러 내용😭

Error:Cannot fit requested classes in a single dex file

방금까지만 해도 빌드가 잘되던 프로젝트가 라이브러리 몇 개를 더 추가하고 리빌드를 하니 위와 같이 에러가 발생했습니다.

2. 해결 방법🙆‍♂️

해결 방법은 프로젝트의 minSdkVersion(최소 지원 안드로이드 SDK 버전)이 21 이상, 20 이하에 따라 다릅니다. (확인은 build.gradle(app)에서 하시면 됩니다.)

하지만 아직까진 대부분의 안드로이드 디바이스들이 낮은 버전으로 많이 사용 중이므로 웬만한 프로젝트들은 최소 지원 버전이 Android 5.0(API 21) 이하 일 겁니다.

2-1. minSdkVersion 20 이하 "AndroidX를 사용하지 않는" 경우
build.gradle(app) 파일에서 multiDexEnable을 true로 설정해서 추가하고 multidex 서포트 라이브러리를 dependencies에 추가해야 합니다.

android {
    compileSdkVersion 28
    buildToolsVersion '28.0.3'
    
    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        multiDexEnabled true <- 추가
    }
    ...
}

dependencies {
  ...
  // inSdkVersion 20 이하 "AndroidX를 사용하지 않는" 경우
  implementation 'com.android.support:multidex:1.0.3' <- 추가
  ...
}

2-2. minSdkVersion 20 이하 "AndroidX를 사용하는" 경우
설정은 AndroidX를 사용하지 않는 경우와 동일하지만 dependencies에 추가해야 할 내용이 다릅니다.
AndroidX 서포트 라이브러리에 포함이 되어 있기 때문에 아래와 같이 추가합니다.

dependencies {
  ...
  // inSdkVersion 20 이하 "AndroidX를 사용하는" 경우
  implementation 'androidx.multidex:multidex:2.0.1' <-추가
  ...
}

2-3. 어플리케이션 코드 설정
Multidex를 사용하려면 AndroidManifest의 Application 쪽에 MultiDexApplication의 클래스로 지정해주어야 합니다.
여기서 Application 클래스의 재정의 여부, 상속 여부에 따라 나뉘게 됩니다.

1) Application 클래스를 따로 만들지 않았을 때 (클래스 구현 X)
AndroidManifest에 다음처럼 android:name을 추가해주면 됩니다.

<application
android:name="android.support.multidex.MultiDexApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
...>
...
</application>

2) Custom Application 클래스를 만들 때 (클래스 구현 O)
다음처럼 Application 클래스를 따로 만들 수 있습니다.

자바 예제

public class MyApplication extends MultiDexApplication {
...
}

코틀린 예제

class MyApplication : MultiDexApplication() {
...
}

이 경우엔 AndroidManifest에 다음처럼 android:name=".Multidex가 상속된 클래스명" 추가해주면 됩니다.

<application
    android:name=".MyApplication"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme"
...>
...
</application>

3) Application은 재정의 하지만 MultiDexApplication를 상속하지 않는 경우
다음처럼 MultiDex.install(this) 를 호출하여 MultiDex를 사용하도록 만들 수 있습니다.

자바 예제

public class MyApplication extends SomeOtherApplication {
  @Override
  protected void attachBaseContext(Context base) {
     super.attachBaseContext(base);
     MultiDex.install(this);
  }
}

코틀린 예제

class LoginActivity : AppCompatActivity()  {
  override fun attachBaseContext(base: Context?) {
      super.attachBaseContext(base)
      MultiDex.install(this)
  }
}

🔔 주의: MultiDex.install()이 완료되기 전에 Reflection 또는 JNI를 통해 MultiDex.install()이나 다른 코드를 실행하면 안 됩니다. Multidex tracing은 이러한 호출을 따르지 않아 ClassNotFoundException이 발생하거나 DEX 파일 간의 잘못된 클래스 파티션으로 인한 오류가 발생합니다.



  • minSdkVersion 21 이상
    minSdkVersion이 21 이상으로 설정되어 있을 경우 multidex가 기본적으로 사용 설정이 되므로 dependencies에 multidex 지원 라이브러리 추가 및 어플리케이션 코드 설정이 필요하지 않습니다.
    그러므로 build.gradle(app) 파일에서 multiDexEnable을 true로 설정하시면 됩니다.
android {
    compileSdkVersion 28
    buildToolsVersion '28.0.3'
    
    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        multiDexEnabled true <- 추가
    }
    ...
}

아래의 이미지처럼 Project Structure에서도 추가 및 버전 확인이 가능합니다.
검색 시 Multidex 라이브러리가 2개가 표시되는데 AndroidX도 같이 표시되니 프로젝트에 맞춰서 추가하시거나 버전 확인하시면 됩니다.

3. 에러가 발생하는 이유🤨

안드로이드의 APK 파일에는 dex(Dalvik Executable) 파일 형식의 실행 가능한 바이트코드 파일이 포함됩니다.
이 단일 dex 파일 내에서 참조할 수 있는 메서드의 총개수는 65,536으로 제한하며, 프레임워크 메서드 / 라이브러리 메서드 / 프로젝트의 앱에서 정의한 메서드가 모두 포함됩니다.
참고로 총 메서드 개수는 64 * 1024의 값과 동일하며 이 제한을 64K reference limit(64K 참조 제한) 이라고도 합니다.
제 프로젝트에 에러가 발생한 이유는 라이브러리를 추가하면서 dex 파일 내 제한된 메서드 개수를 초과했기 때문입니다.
이를 해결하기 위해 MultiDex로 설정하여 dex 파일이 Multi로 파티션이 나누어지게끔 컴파일되도록 해야 합니다.

안드로이드 K (Kitkat(Watch), Android 4.4(W) API 20 ) "이하"의 안드로이드 버전에서는 앱의 코드 실행을 위해 Dalvik 런타임을 사용합니다.
APK당 하나의 classes.dex 바이트코드 파일로 앱을 제한합니다.
이러한 참조 제한을 해결하려면 Multidex 서포트 라이브러리를 사용해야 합니다.

안드로이드 L (Lollipop, Android 5.0 API 21) "이상"에서는 Dalvik이 아닌 ART(Android RunTime)이라는 런타임을 사용 합니다.
이 런타임은 APK 파일로부터 여러 개의 dex 파일을 로드하는 것을 지원합니다.
ART는 앱 설치 시에 사전 컴파일 실행하여 classesN.dex 파일들을 스캔 뒤에, 안드로이드 디바이스가 실행할 수 있도록 oat(Of-Ahead-Time -> Ahead-Of-Time) 파일로 컴파일합니다.
그래서 minSdkVersion 21 이상이라면 multidex 서포트 라이브러리가 필요 없습니다. build.gradle에 간단하게 multiDexEnabled true 만 추가하면 됩니다.

안드로이드 런타임 관련해서는 추후에 포스팅하도록 하고 지금은 아래 링크에서 내용 참조 부탁드립니다.
나무위키: https://namu.wiki/w/안드로이드%20런타임

4. Mutidex를 사용하면 문제가 없을까요?🙋‍♂️

4-1. Native 코드가 Main dex에 포함이 안되는 경우가 발생합니다.
Native 코드를 사용하는 라이브러리에서 Native(JNI) 코드를 사용하게되면 컴파일러 또는 빌드 툴의 의사결정 과정에서 라이브러리가 구동되기위해 필요한 클래스들이 Main dex 파일에 포함되지 않을 수 있습니다.
그럴 경우에는 multiDexKeepFile, multiDexKeepProguard 를 사용해서 Main dex에 포함시키도록 해야합니다. 그래서 해당 문제에 관련해서 Proguard 등으로 최적화를 할 때 주의해야 할 사항도 있으니 기본 dex 파일에 필요한 클래스 선언 참조 바랍니다.

4-2. Multidex는 빌드 속도가 느리다.
각각 나누어진 dex 파일들을 빌드할 때 빌드 툴(buildToolsVersion)은 Main dex 파일에 어떤 클래스들을 포함할지 고르는 아주 복잡한 의사결정을 수행하게 됩니다.
이러한 복잡한 과정을 하지 않으면 앱 실행에 필요한 클래스들이 Main dex에 포함되어있지않아 충돌(Crash)이 나기 때문입니다.
그래서 규모가 커지는 만큼 빌드 하는데 상당한 시간을 소모하게 됩니다.

4-3. 빌드 최적화
Multidex는 컴파일러 또는 빌드 툴이 복잡한 의사결정을 수행하면서 빌드하는 시간이 길어질 수밖에 없습니다. 빌드 시간을 줄이기 위해서는 빌드 사이에 Multidex를 재사용하는 pre-dexing을 사용할 수도 있습니다.
하지만 해당 방법은 ART를 지원하는 Android 5.0 API 21 이상 버전에서만 가능합니다.
만약에 Android Studio 2.3 이상인 경우는 IDE 자체에서 자동으로 pre-dexing을 사용하기 때문에 별도로 설정할 필요는 없습니다.
Android studio와 gradle plugin은 최신 버전으로 업데이트하면 패치 사항에 빌드 속도를 최적화하는 기능들이 거의 대부분 추가적으로 들어있기 때문에 특수한 경우를 제외하고는 항상 최신 버전으로 유지하는 것이 좋습니다.

4-4. Multidex 서포트 라이브러리의 제한 사항

  • 디바이스에 dex 파일들을 설치할 때, 두 번째 dex 파일 사이즈가 클 경우에 ANR(Android Not Responding)이 발생할 수 있습니다.
    해당 문제를 막기 위해서는 ProGuard에서 코드 축소(Code Shrinking)를 해야 합니다.
  • Android 5.0 롤리팝 미만에서 실행할 경우 linearalloc limit (issue 78035) 이슈를 완전히 막을 수 없습니다.

참고 링크
https://developer.android.com/studio/build/multidex#kotlin <- 영어로 보시는걸 추천해드립니다.

https://blog.mindorks.com/understanding-multidex-in-android

profile
12년도부터 대학에서 안드로이드 모바일을 전공으로 시작하여 "진짜 개발자"를 꿈꾸며 개발공부를 시작했습니다. SW 개발이 재밌어서 여러 방면으로 스터디하며 현재는 새로운 환경 및 새로운 트렌드에도 유연하게 적응을 잘하는 개발자로 성장해 나가는 중입니다. 글 내용에 대한 피드백은 언제나 환영입니다!

4개의 댓글

comment-user-thumbnail
2020년 9월 16일

정말 감사합니다!! flutter를 이리저리 만져보고 있는데 저 해당 문제가 발생했었는데
2-1번 방법으로 덕분에 해결했습니다!

혹시 글을 작성할 때 링크를 첨부해도 될까요?

1개의 답글
comment-user-thumbnail
2022년 8월 22일

안녕하세요, 좋은 글 보고 오류 해결 했습니다.
Flutter 프로젝트를 처음 시작 할 때 나오는 오류들을 정리해보려 하는데 링크 첨부해도 괜찮을까요?

1개의 답글