메서드가 64K개를 초과하는 앱에 관해 멀티덱스 사용 설정

앱에 API 20 이하 minSdk가 있고 앱과 앱에서 참조하는 라이브러리가 65,536개 메서드를 초과하면 앱이 Android 빌드 아키텍처의 제한에 도달했음을 나타내는 다음과 같은 빌드 오류가 발생합니다.

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

이전 버전의 빌드 시스템은 다른 오류를 보고하지만, 이는 동일한 문제를 나타냅니다.

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

이러한 오류는 모두 공통 숫자인 65536을 표시합니다. 이 숫자는 단일 DEX(Dalvik Executable) 바이트 코드 파일 내에서 코드가 호출할 수 있는 참조의 총 개수를 나타냅니다. 이 페이지에서는 멀티덱스라는 앱 구성을 사용 설정하여 이 제한을 극복하는 방법을 설명합니다. 멀티덱스를 사용하면 앱에서 다중 DEX 파일을 빌드하고 읽을 수 있습니다.

64K 참조 제한 관련 정보

Android 앱(APK) 파일에는 Dalvik Executable(DEX) 파일 형식의 실행 가능한 바이트 코드 파일이 포함되며, DEX 파일에는 앱을 실행하기 위해 사용되는 컴파일된 코드가 포함됩니다. Dalvik Executable 사양은 단일 DEX 파일 내에서 참조될 수 있는 메서드의 총 개수를 65,536개로 제한하며 여기에는 Android 프레임워크 메서드, 라이브러리 메서드, 자체 코드에 있는 메서드가 포함됩니다.

컴퓨터 공학에서 킬로 또는 K라는 용어는 1,024(또는 2^10)를 나타냅니다. 65,536은 64 X 1,024와 동일하므로 이 제한을 '64K 참조 제한'이라고 부릅니다.

Android 5.0 미만에서 멀티덱스 지원

Android 5.0(API 수준 21) 이전의 플랫폼 버전에서는 앱 코드 실행을 위해 Dalvik 런타임을 사용합니다. 기본적으로 Dalvik에서는 APK당 하나의 classes.dex 바이트 코드 파일로 앱을 제한합니다. 이 제한을 우회하려면 멀티덱스 라이브러리를 모듈 수준 build.gradle 또는 build.gradle.kts 파일에 추가하세요.

Groovy

dependencies {
    def multidex_version = "2.0.1"
    implementation "androidx.multidex:multidex:$multidex_version"
}

Kotlin

dependencies {
    val multidex_version = "2.0.1"
    implementation("androidx.multidex:multidex:$multidex_version")
}

이 라이브러리는 앱의 기본 DEX 파일의 일부가 되며 추가 DEX 파일 및 이러한 파일에 포함된 코드의 액세스를 관리합니다. 이 라이브러리의 현재 버전을 보려면 멀티덱스 버전을 참고하세요.

자세한 내용은 멀티덱스용 앱 구성 방법에 관한 섹션을 참고하세요.

Android 5.0 이상에서 멀티덱스 지원

Android 5.0(API 수준 21) 이상에서는 ART라는 런타임을 사용합니다. 기본적으로 이 런타임은 APK 파일에서 여러 개의 DEX 파일을 로드하는 것을 지원합니다. ART는 앱 설치 시 사전 컴파일을 실행하여 classesN.dex 파일을 스캔하고 Android 기기에서 실행할 수 있도록 단일 OAT 파일로 컴파일합니다. 따라서 minSdkVersion이 21 이상이라면 멀티덱스가 기본적으로 사용 설정되며 멀티덱스 라이브러리가 필요하지 않습니다.

Android 5.0 런타임에 관한 자세한 내용은 Android 런타임(ART) 및 Dalvik을 참고하세요.

참고: Android 스튜디오를 사용하여 앱을 실행하면 배포하는 대상 기기에 맞게 빌드가 최적화됩니다. 대상 기기에서 Android 5.0 이상을 실행할 경우 멀티덱스 사용 설정도 여기에 포함됩니다. Android 스튜디오를 사용하여 앱을 배포하는 경우에만 이와 같은 최적화 작업이 이루어지므로 64K 제한을 피하기 위해서는 멀티덱스용으로 출시 빌드를 구성해야 할 수도 있습니다.

64K 제한 피하기

64K개 이상의 메서드 참조를 사용하도록 앱을 구성하기 전에 앱 코드가 호출하는 참조의 총 개수를 줄이는 단계를 먼저 진행합니다. 여기에는 앱 코드나 포함된 라이브러리에 의해 정의된 메서드가 포함됩니다.

다음 전략은 DEX 참조 제한을 넘지 않도록 도와줍니다.

앱의 직접 종속 항목과 전이 종속 항목 검토
앱에 포함되는 큰 라이브러리 종속 항목의 값이 앱에 추가되는 코드 양을 상회하는지 고려하세요. 흔히 사용되지만 문제가 되는 패턴은 몇 가지 유틸리티 메서드가 유용하기 때문에 매우 큰 라이브러리를 포함하는 것입니다. 대개의 경우 앱 코드 종속 항목을 줄이면 DEX 참조 제한을 피하는 데 도움이 됩니다.
R8로 사용되지 않는 코드 삭제
코드 축소를 사용 설정하여 출시 빌드에서 R8을 실행합니다. 축소를 사용 설정하면 사용되지 않는 코드를 APK와 함께 제공하지 않을 수 있습니다. 코드 축소가 올바르게 구성되면 사용되지 않는 코드와 리소스를 종속 항목에서 삭제할 수도 있습니다.

이러한 기법을 사용하면 APK의 전체 크기를 줄일 수 있고 앱에 멀티덱스가 필요하지 않게 됩니다.

멀티덱스용 앱 구성

참고: minSdkVersion이 21 이상으로 설정되면 멀티덱스가 기본적으로 사용 설정되며 멀티덱스 라이브러리가 필요하지 않습니다.

minSdkVersion이 20 이하로 설정되어 있으면 멀티덱스 라이브러리를 사용하고 앱 프로젝트를 다음과 같이 수정해야 합니다.

  1. 다음과 같이 모듈 수준 build.gradle 파일을 수정하여 멀티덱스를 사용 설정하고 멀티덱스 라이브러리를 종속 항목으로 추가합니다.

    Groovy

    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 33
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
        implementation "androidx.multidex:multidex:2.0.1"
    }
    

    Kotlin

    android {
        defaultConfig {
            ...
            minSdk = 15 
            targetSdk = 33
            multiDexEnabled = true
        }
        ...
    }
    
    dependencies {
        implementation("androidx.multidex:multidex:2.0.1")
    }
    
  2. Application 클래스의 재정의 여부에 따라 다음 중 하나를 실행합니다.
    • Application 클래스를 재정의하지 않으면 매니페스트 파일을 수정하여 다음과 같이 <application> 태그에서 android:name을 설정합니다.

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.myapp">
          <application
                  android:name="androidx.multidex.MultiDexApplication" >
              ...
          </application>
      </manifest>
      
    • Application 클래스를 재정의하는 경우 다음과 같이 변경하여 MultiDexApplication을 확장합니다.

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

      public class MyApplication extends MultiDexApplication { ... }
      
    • Application 클래스를 재정의하지만 기본 클래스는 변경할 수 없는 경우 대신 attachBaseContext() 메서드를 재정의하고 MultiDex.install(this)를 호출하여 멀티덱스를 사용 설정합니다.

      Kotlin

      class MyApplication : SomeOtherApplication() {
      
          override fun attachBaseContext(base: Context) {
              super.attachBaseContext(base)
              MultiDex.install(this)
          }
      }
      

      Java

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

      주의: MultiDex.install()이 완료되기 전에 리플렉션 또는 JNI를 통해 MultiDex.install()이나 다른 코드를 실행하지 마세요. 멀티덱스 추적은 이러한 호출을 따르지 않아 ClassNotFoundException을 일으키거나 DEX 파일 간 클래스 파티션 불량으로 인한 오류를 확인합니다.

이제 앱을 빌드할 때 Android 빌드 도구는 기본 DEX 파일(classes.dex)과 보조 DEX 파일(classes2.dex, classes3.dex 등)을 필요에 따라 구성합니다. 그 후 빌드 시스템이 모든 DEX 파일을 APK로 패키징합니다.

런타임에 기본 classes.dex 파일에서만 검색하는 대신 멀티덱스 API는 특수 클래스 로더를 사용하여 메서드에서 사용할 수 있는 모든 DEX 파일을 검색합니다.

멀티덱스 라이브러리 제한사항

멀티덱스 라이브러리에는 몇 가지 알려진 제한사항이 있습니다. 라이브러리를 앱 빌드 구성에 통합할 때 다음 사항을 고려하세요.

  • 시작 중에 기기 데이터 파티션에 DEX 파일을 설치하는 작업은 복잡하며 보조 DEX 파일이 큰 경우 애플리케이션 응답 없음(ANR) 오류가 발생할 수 있습니다. 이 문제를 방지하려면 코드 축소를 사용 설정하여 DEX 파일의 크기를 최소화하고 코드에서 사용되지 않는 부분을 삭제합니다.
  • Android 5.0(API 수준 21) 미만 버전에서 실행 중인 경우 멀티덱스를 사용하는 것만으로는 linearalloc 제한(문제 37008143)을 해결할 수 없습니다. 이 제한은 Android 4.0(API 수준 14)에서 상향 조정되었지만 문제가 완전히 해결되지는 않았습니다.

    Android 4.0보다 낮은 버전에서는 DEX 색인 제한에 도달하기 전에 linearalloc 제한에 도달할 수 있습니다. 따라서 API 수준 14 미만을 타겟팅한다면 시작 시 또는 특정 클래스 그룹이 로드될 때 앱에 문제가 있을 수 있으므로 이러한 플랫폼 버전은 철저히 테스트해야 합니다.

    코드 축소는 이러한 문제를 줄이거나 완전히 없앨 수도 있습니다.

기본 DEX 파일에 필요한 클래스 선언

멀티덱스 앱의 각 DEX 파일을 빌드할 때 빌드 도구는 앱이 성공적으로 시작될 수 있도록 복잡한 의사결정 과정을 통해 기본 DEX 파일에 어떤 클래스가 필요한지 결정합니다. 시작 시 필요한 클래스가 기본 DEX 파일에 제공되지 않으면 앱이 java.lang.NoClassDefFoundError 오류와 함께 비정상 종료됩니다.

빌드 도구는 앱 코드에서 직접 액세스하는 코드의 코드 경로를 인식합니다. 그러나 이 문제는 사용하는 라이브러리의 종속 항목이 복잡한 경우와 같이 코드 경로가 덜 명확할 때 발생할 수 있습니다. 예를 들어 코드가 네이티브 코드에서 자바 메서드 검사 또는 호출을 사용한다면 이러한 클래스는 기본 DEX 파일에 필요한 것으로 인식되지 않을 수 있습니다.

java.lang.NoClassDefFoundError를 수신한다면 빌드 유형에서 multiDexKeepProguard 속성으로 선언하여 기본 DEX 파일에 필요한 추가 클래스를 수동으로 지정해야 합니다. 클래스가 multiDexKeepProguard 파일에서 일치하면 이 클래스가 기본 DEX 파일에 추가됩니다.

multiDexKeepProguard 속성

multiDexKeepProguard 파일은 ProGuard와 동일한 형식을 사용하며 전체 ProGuard 문법을 지원합니다. 앱에 유지되는 항목을 맞춤설정하는 방법에 관한 자세한 내용은 유지할 코드 맞춤설정을 참고하세요.

multiDexKeepProguard에서 지정한 파일에는 유효한 ProGuard 문법으로 된 -keep 옵션이 포함되어야 합니다. 예를 들어, -keep com.example.MyClass.class입니다. 다음과 같이 multidex-config.pro라는 파일을 만들 수 있습니다.

-keep class com.example.MyClass
-keep class com.example.MyClassToo

패키지에서 모든 클래스를 지정하고 싶다면 파일은 다음과 같은 형식을 취해야 합니다.

-keep class com.example.** { *; } // All classes in the com.example package

그리고 이 파일을 다음과 같은 빌드 유형에 관해 선언할 수 있습니다.

Groovy

android {
    buildTypes {
        release {
            multiDexKeepProguard file('multidex-config.pro')
            ...
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            multiDexKeepProguard = file("multidex-config.pro")
            ...
        }
    }
}

개발 빌드에서 멀티덱스 최적화

기본 DEX 파일에 어떤 클래스를 포함해야 하고 보조 DEX 파일에 어떤 클래스를 포함할 수 있는지에 관한 복잡한 결정을 빌드 시스템에서 하므로 멀티덱스 구성에는 상당히 긴 빌드 처리 시간이 필요합니다. 일반적으로 멀티덱스를 사용하는 증분 빌드는 시간이 더 오래 걸리고 개발 프로세스를 늦출 가능성이 있습니다.

더 긴 증분 빌드 시간을 완화하려면 사전 덱싱을 사용하여 빌드 간에 멀티덱스 출력을 재사용합니다. 사전 덱싱을 사용하려면 Android 5.0(API 수준 21) 이상에서만 사용할 수 있는 ART 형식이 필요합니다. Android 스튜디오를 사용하는 경우 IDE에서는 Android 5.0(API 수준 21) 이상을 실행하는 기기에 앱을 배포할 때 자동으로 사전 덱싱을 사용합니다. 그러나 명령줄에서 Gradle 빌드를 실행하는 경우 사전 덱싱을 사용 설정하려면 minSdkVersion을 21 이상으로 설정해야 합니다.

프로덕션 빌드의 설정을 유지하려면 제품 버전을 사용하여 두 가지 버전의 앱(개발 버전을 사용한 버전과 출시 버전을 사용한 버전)을 만들면 됩니다. 이때 다음과 같이 minSdkVersion 값을 다르게 합니다.

Groovy

android {
    defaultConfig {
        ...
        multiDexEnabled true
        // The default minimum API level you want to support.
        minSdkVersion 15
    }
    productFlavors {
        // Includes settings you want to keep only while developing your app.
        dev {
            // Enables pre-dexing for command-line builds. When using
            // Android Studio 2.3 or higher, the IDE enables pre-dexing
            // when deploying your app to a device running Android 5.0
            // (API level 21) or higher, regardless of minSdkVersion.
            minSdkVersion 21
        }
        prod {
            // If you've configured the defaultConfig block for the production version of
            // your app, you can leave this block empty and Gradle uses configurations in
            // the defaultConfig block instead. You still need to include this flavor.
            // Otherwise, all variants use the "dev" flavor configurations.
        }
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                                                 'proguard-rules.pro'
        }
    }
}
dependencies {
    implementation "androidx.multidex:multidex:2.0.1"
}

Kotlin

android {
    defaultConfig {
        ...
        multiDexEnabled = true
        // The default minimum API level you want to support.
        minSdk = 15
    }
    productFlavors {
        // Includes settings you want to keep only while developing your app.
        create("dev") {
            // Enables pre-dexing for command-line builds. When using
            // Android Studio 2.3 or higher, the IDE enables pre-dexing
            // when deploying your app to a device running Android 5.0
            // (API level 21) or higher, regardless of minSdkVersion.
            minSdk = 21
        }
        create("prod") {
            // If you've configured the defaultConfig block for the production version of
            // your app, you can leave this block empty and Gradle uses configurations in
            // the defaultConfig block instead. You still need to include this flavor.
            // Otherwise, all variants use the "dev" flavor configurations.
        }
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android.txt"),
                                                 "proguard-rules.pro")
        }
    }
}

dependencies {
    implementation("androidx.multidex:multidex:2.0.1")
}

Android 스튜디오 또는 명령줄에서 빌드 속도를 개선하는 데 도움이 되는 전략을 자세히 알아보려면 빌드 속도 최적화를 참고하세요. 빌드 변형 사용에 관한 자세한 내용은 빌드 변형 구성을 참고하세요.

도움말: 여러 멀티덱스 요구사항에 맞는 다양한 빌드 변형이 있는 경우 API 수준 20 이하의 파일만 <application> 태그 이름을 변경하도록 각 변형에 다른 매니페스트 파일을 제공할 수 있습니다. 각 변형에 다른 Application 서브클래스를 만들어 API 수준 20 이하의 서브클래스만 MultiDexApplication 클래스를 확장하거나 MultiDex.install(this)를 호출하도록 할 수도 있습니다.

멀티덱스 앱 테스트

멀티덱스 앱용 계측 테스트를 작성할 때 MonitoringInstrumentation 또는 AndroidJUnitRunner 계측을 사용한다면 추가 구성이 필요하지 않습니다. 다른 Instrumentation을 사용한다면 다음 코드를 사용하여 onCreate() 메서드를 재정의해야 합니다.

Kotlin

fun onCreate(arguments: Bundle) {
  MultiDex.install(targetContext)
  super.onCreate(arguments)
  ...
}

Java

public void onCreate(Bundle arguments) {
  MultiDex.install(getTargetContext());
  super.onCreate(arguments);
  ...
}