64K가 넘는 메서드의 앱에 관해 멀티덱스 사용 설정

앱 및 앱이 참조하는 라이브러리에서 메서드가 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라는 용어는 1024(또는 2^10)를 나타냅니다. 65,536은 64 X 1024와 동일하므로 이 제한을 '64K 참조 제한'이라고 부릅니다.

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

Android 5.0(API 수준 21) 이전의 플랫폼 버전에서는 앱 코드 실행을 위해 Dalvik 런타임을 사용합니다. 기본적으로 Dalvik에서는 APK당 하나의 classes.dex 바이트 코드 파일로 앱을 제한합니다. 이 제한을 해결하기 위해 프로젝트에 멀티덱스 지원 라이브러리를 추가할 수 있습니다.

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

이 라이브러리의 현재 버전을 보려면 버전 페이지에서 멀티덱스 관련 정보를 확인하세요.

AndroidX를 사용하지 않는 경우 다음의 지원 라이브러리 종속 항목을 대신 추가합니다.

dependencies {
  implementation 'com.android.support:multidex:1.0.3'
}

이 라이브러리는 앱의 기본 DEX 파일의 일부가 되며 추가 DEX 파일 및 추가 파일에 포함된 코드의 액세스를 관리합니다. 자세한 내용은 멀티덱스용 앱 구성 방법에 관한 아래의 섹션을 참조하세요.

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

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

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

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

64K 제한 피하기

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

  • 앱의 직접 또는 전이 종속 항목 검토 - 앱에 포함되는 큰 라이브러리 종속 항목은 앱에 추가되는 코드 양을 상회하는 방식으로 사용되어야 합니다. 유틸리티 메서드 몇 개가 유용했으므로, 일반적인 안티패턴은 매우 큰 라이브러리를 포함하는 것입니다. 대개의 경우 앱 코드 종속 항목을 줄이면 DEX 참조 제한을 피하는 데 도움이 됩니다.
  • R8으로 사용되지 않는 코드 삭제 - 출시 빌드를 위해 코드 축소를 사용 설정하여 R8을 실행합니다. 축소를 사용 설정하면 사용되지 않는 코드는 APK에 전달되지 않습니다.

이 기법을 사용하면 앱에서 멀티덱스를 사용 설정할 필요가 없을 뿐만 아니라 APK의 전체 크기를 줄일 수 있습니다.

멀티덱스용 앱 구성

minSdkVersion이 21 이상으로 설정된 경우 멀티덱스가 기본적으로 사용 설정되므로 멀티덱스 지원 라이브러리가 필요하지 않습니다.

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

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

    android {
        defaultConfig {
            ...
            minSdkVersion 15
            targetSdkVersion 28
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
      implementation 'com.android.support:multidex:1.0.3'
    }
    
  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="android.support.multidex.MultiDexApplication" >
              ...
          </application>
      </manifest>
      
    • Application 클래스를 재정의한다면 다음과 같이 변경하여 MultiDexApplication을 확장합니다(가능한 경우).

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      자바

      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)
          }
      }
      

      자바

      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로 패키징합니다.

런타임 시, 멀티덱스 API는 특수 클래스 로더를 사용하여 메서드에 사용 가능한 모든 DEX 파일을 검색합니다(기본 classes.dex 파일에서만 검색하는 것이 아님).

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

멀티덱스 지원 라이브러리에는 몇 가지 알려진 제한 사항이 있으며 이 라이브러리를 앱 빌드 구성에 통합할 때 이러한 제한 사항을 파악하고 테스트해야 합니다.

  • 시작 중에 기기 데이터 파티션에 DEX 파일을 설치하는 작업은 복잡하며 보조 DEX 파일이 큰 경우 애플리케이션 응답 없음(ANR) 오류가 발생할 수 있습니다. 이 문제를 방지하려면 코드 축소를 사용 설정하여 DEX 파일의 크기를 최소화하고 코드에서 사용되지 않는 부분을 삭제합니다.
  • Android 5.0(API 수준 21) 이전 버전에서 실행 중인 경우 멀티덱스를 사용하는 것만으로는 linearalloc 제한(문제 78035)을 해결하는 데 충분하지 않습니다. 이 제한은 Android 4.0(API 레벨 14)에서 증가했지만, 완전히 해결되지 않았습니다. Android 4.0보다 낮은 버전에서는 DEX 색인 제한에 도달하기 전에 linearalloc 제한에 도달할 수 있습니다. 따라서, API 수준 14 미만을 타겟팅한다면 시작 시 또는 특정 클래스 그룹이 로드될 때 앱에 문제가 있을 수 있으므로 이러한 플랫폼 버전은 철저히 테스트해야 합니다.

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

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

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

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

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

multiDexKeepFile 속성

multiDexKeepFile에서 지정한 파일에는 com/example/MyClass.class 형식으로 줄당 클래스 하나가 포함되어야 합니다. 예를 들어, 다음과 같은 multidex-config.txt라는 파일을 만들 수 있습니다.

com/example/MyClass.class
com/example/MyOtherClass.class

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

android {
    buildTypes {
        release {
            multiDexKeepFile file('multidex-config.txt')
            ...
        }
    }
}

Gradle은 build.gradle 파일에 상대적인 경로를 읽기 때문에 위 예는 multidex-config.txtbuild.gradle 파일과 같은 디렉터리에 있을 때 작동합니다.

multiDexKeepProguard 속성

multiDexKeepProguard 파일은 Proguard와 동일한 형식을 사용하고 전체 Proguard 문법을 지원합니다. Proguard 형식 및 문법에 관한 자세한 내용은 Proguard 설명서의 Keep 옵션 섹션을 참조하세요.

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

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

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

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

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

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

도움말: Gradle용 Android 플러그인 3.0.0 이상에는 클래스당 덱싱과 같은 빌드 속도를 최적화하는 기능이 개선되어 수정한 클래스만 다시 덱싱됩니다. 일반적으로 최고의 개발 환경을 유지하려면 최신 버전의 Android 스튜디오 및 Android 플러그인으로 항상 업그레이드해야 합니다.

그러나, 명령줄에서 Gradle 빌드를 실행하는 경우 사전 덱싱을 사용 설정하려면 minSdkVersion을 21 이상으로 설정해야 합니다. 프로덕션 빌드의 설정을 유지하기 위한 유용한 전략은 제품 버전을 사용하여 두 가지 버전의 앱을 만드는 것입니다. minSdkVersion의 값을 달리하여 다음과 같이 하나는 개발 버전, 다른 하나는 출시 버전으로 만듭니다.

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 what you set for
            // 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 'com.android.support:multidex:1.0.3'
}

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)
  ...
}

자바

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