Включить мультидекс для приложений с более чем 64 тысячами методов.

Если minSdk вашего приложения равен API 20 или ниже , а само приложение и библиотеки, на которые оно ссылается, превышают 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. Это число представляет собой общее количество ссылок, которые может вызвать код в одном исполняемом файле байт-кода Dalvik (DEX). На этой странице объясняется, как обойти это ограничение, включив конфигурацию приложения, известную как multidex , которая позволяет вашему приложению создавать и читать несколько файлов DEX.

О пределе ссылок в 64 КБ

Файлы приложений Android (APK) содержат исполняемые файлы байт-кода в виде исполняемых файлов Dalvik (DEX) , которые содержат скомпилированный код, используемый для запуска вашего приложения. Спецификация Dalvik Executable ограничивает общее количество методов, на которые можно ссылаться в одном DEX-файле, 65 536, включая методы фреймворка Android, методы библиотек и методы в вашем собственном коде.

В контексте компьютерных наук термин «кило», или К , обозначает 1024 (или 2^10). Поскольку 65 536 равно 64x1024, этот предел называется _пределом 64К_.

Поддержка Multidex до Android 5.0

Версии платформы до Android 5.0 (API уровня 21) используют среду выполнения Dalvik для выполнения кода приложения. По умолчанию Dalvik ограничивает приложения одним файлом байт-кода classes.dex на APK. Чтобы обойти это ограничение, добавьте библиотеку multidex в файл build.gradle или build.gradle.kts на уровне модуля:

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

Эта библиотека становится частью основного DEX-файла вашего приложения, а затем управляет доступом к дополнительным DEX-файлам и содержащемуся в них коду. Чтобы просмотреть текущие версии этой библиотеки, см. раздел «Версии multidex» .

Более подробную информацию смотрите в разделе о том, как настроить приложение для multidex .

Поддержка Multidex для Android 5.0 и выше

В Android 5.0 (уровень API 21) и выше используется среда выполнения ART, которая изначально поддерживает загрузку нескольких DEX-файлов из APK-файлов. ART выполняет предварительную компиляцию во время установки приложения, сканируя файлы classes N .dex и компилируя их в один OAT-файл для выполнения на устройстве Android. Таким образом, если minSdkVersion равен 21 или выше, multidex включен по умолчанию, и библиотека multidex вам не нужна.

Дополнительную информацию о среде выполнения Android 5.0 можно найти в статьях Android Runtime (ART) и Dalvik .

Примечание: При запуске приложения в Android Studio сборка оптимизируется для целевых устройств, на которых оно развёртывается. Это включает в себя включение multidex, если целевые устройства работают под управлением Android 5.0 и выше. Поскольку эта оптимизация применяется только при развёртывании приложения в Android Studio, вам всё равно может потребоваться настроить сборку релиза для multidex, чтобы избежать ограничения в 64 КБ.

Избегайте ограничения в 64 КБ

Прежде чем настраивать приложение для использования ссылок на методы размером 64 КБ или более, примите меры по сокращению общего количества ссылок, вызываемых кодом вашего приложения, включая методы, определенные кодом вашего приложения или включенными библиотеками.

Следующие стратегии помогут вам избежать достижения контрольного лимита DEX:

Проверьте прямые и транзитивные зависимости вашего приложения.
Подумайте, перевешивает ли ценность любой крупной зависимости от библиотеки, включаемой в приложение, объём добавляемого в него кода. Распространенная, но проблемная практика — включать очень большую библиотеку, потому что несколько вспомогательных методов оказались полезными. Сокращение зависимостей кода приложения часто помогает избежать ограничения на количество ссылок DEX.
Удалить неиспользуемый код с помощью R8
Включите сжатие кода для запуска R8 в ваших релизных сборках. Включите сжатие кода, чтобы гарантировать отсутствие неиспользуемого кода в APK-файлах. Если сжатие кода настроено правильно, оно также может удалить неиспользуемый код и ресурсы из ваших зависимостей.

Использование этих методов может помочь вам уменьшить общий размер вашего APK и избежать необходимости использования multidex в вашем приложении.

Настройте свое приложение для мультидекса

Примечание: если minSdkVersion установлен на 21 или выше, multidex включен по умолчанию и вам не нужна библиотека multidex.

Если minSdkVersion равно 20 или ниже, то необходимо использовать библиотеку multidex и внести следующие изменения в проект приложения:

  1. Измените файл build.gradle на уровне модуля, чтобы включить multidex, и добавьте библиотеку multidex в качестве зависимости, как показано здесь:

    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 33
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
        implementation "androidx.multidex:multidex:2.0.1"
    }
    android {
        defaultConfig {
            ...
            minSdk = 15 
            targetSdk = 33
            multiDexEnabled = true
        }
        ...
    }
    
    dependencies {
        implementation("androidx.multidex:multidex:2.0.1")
    }
  2. В зависимости от того, переопределяете ли вы класс Application , выполните одно из следующих действий:
    • Если вы не переопределяете класс Application , отредактируйте файл манифеста, чтобы установить android:name в теге <application> следующим образом:

      <?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 , как показано ниже:

      class MyApplication : MultiDexApplication() {...}
      public class MyApplication extends MultiDexApplication { ... }
    • Если вы переопределяете класс Application , но при этом невозможно изменить базовый класс, то вместо этого переопределите метод attachBaseContext() и вызовите MultiDex.install(this) , чтобы включить multidex:

      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() . Трассировка Multidex не будет отслеживать эти вызовы, что приведёт к исключению ClassNotFoundException или ошибкам проверки из-за неправильного разделения классов между DEX-файлами.

Теперь при сборке приложения инструменты сборки Android создают основной DEX-файл ( classes.dex ) и вспомогательные DEX-файлы ( classes2.dex , classes3.dex и т. д.) по мере необходимости. Затем система сборки упаковывает все DEX-файлы в ваш APK.

Во время выполнения, вместо поиска только в основном файле classes.dex , API multidex используют специальный загрузчик классов для поиска ваших методов во всех доступных файлах DEX.

Ограничения библиотеки multidex

Библиотека multidex имеет ряд известных ограничений. При включении библиотеки в конфигурацию сборки приложения учтите следующее:

  • Установка DEX-файлов во время запуска системы в раздел данных устройства сложна и может привести к ошибкам «Приложение не отвечает» (ANR), если вторичные DEX-файлы имеют большой размер. Чтобы избежать этой проблемы, включите сокращение кода , чтобы минимизировать размер DEX-файлов и удалить неиспользуемые фрагменты кода.
  • В версиях Android до 5.0 (уровень API 21) использование multidex недостаточно для обхода ограничения linearalloc ( проблема 37008143 ). Это ограничение было увеличено в Android 4.0 (уровень API 14), но это не решило проблему полностью.

    В версиях Android ниже 4.0 лимит линейного распределения памяти может быть достигнут раньше, чем лимит индекса DEX. Поэтому, если вы ориентируетесь на уровни API ниже 14, тщательно протестируйте их на этих версиях платформы, поскольку ваше приложение может столкнуться с проблемами при запуске или загрузке определённых групп классов.

    Сокращение кода может уменьшить или, возможно, устранить эти проблемы.

Объявите требуемые классы в основном файле DEX

При сборке каждого DEX-файла для приложения Multidex инструменты сборки выполняют сложный процесс принятия решений, чтобы определить, какие классы необходимы в основном DEX-файле для успешного запуска приложения. Если какой-либо класс, необходимый при запуске, отсутствует в основном DEX-файле, приложение аварийно завершает работу с ошибкой java.lang.NoClassDefFoundError .

Инструменты сборки распознают пути кода, к которому осуществляется прямой доступ из кода вашего приложения. Однако эта проблема может возникнуть, когда пути кода менее заметны, например, если используемая вами библиотека имеет сложные зависимости. Например, если код использует интроспекцию или вызов методов Java из машинного кода, то эти классы могут не распознаваться как требуемые в основном DEX-файле.

Если вы получили java.lang.NoClassDefFoundError , необходимо вручную указать дополнительные классы, необходимые в основном DEX-файле, объявив их с помощью свойства multiDexKeepProguard в типе сборки. Если класс найден в файле multiDexKeepProguard , он добавляется в основной DEX-файл.

свойство multiDexKeepProguard

Файл multiDexKeepProguard использует тот же формат, что и ProGuard, и поддерживает всю грамматику ProGuard. Подробнее о настройке сохраняемых данных в приложении см. в разделе «Настройка сохраняемого кода» .

Файл, указанный в multiDexKeepProguard , должен содержать параметры -keep в любом допустимом синтаксисе ProGuard. Например, -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')
            ...
        }
    }
}
android {
    buildTypes {
        getByName("release") {
            multiDexKeepProguard = file("multidex-config.pro")
            ...
        }
    }
}

Оптимизация multidex в сборках разработки

Конфигурация multidex требует значительного увеличения времени сборки, поскольку система сборки должна принимать сложные решения о том, какие классы должны быть включены в первичный DEX-файл, а какие — во вторичные DEX-файлы. Это означает, что инкрементальные сборки с использованием multidex обычно занимают больше времени и могут потенциально замедлить процесс разработки.

Чтобы сократить время инкрементальной сборки, используйте предварительный дексинг для повторного использования выходных данных multidex между сборками. Предварительный дексинг основан на формате ART, доступном только в Android 5.0 (API уровня 21) и выше. Если вы используете Android Studio, IDE автоматически использует предварительный дексинг при развертывании приложения на устройстве под управлением Android 5.0 (API уровня 21) и выше. Однако, если вы запускаете сборки 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 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"
}
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 Studio или командной строки, прочтите статью «Оптимизация скорости сборки» . Подробнее об использовании вариантов сборки см. в статье «Настройка вариантов сборки» .

Совет: Если у вас разные варианты сборки для разных потребностей Multidex, вы можете предоставить отдельный файл манифеста для каждого варианта, чтобы имя тега <application> изменялось только в файле для API уровня 20 и ниже. Вы также можете создать отдельный подкласс Application для каждого варианта, чтобы только подкласс для API уровня 20 и ниже расширял класс MultiDexApplication или вызывал MultiDex.install(this) .

Тестовые мультидекс-приложения

При написании тестов инструментирования для приложений Multidex дополнительная настройка не требуется, если вы используете инструментирование MonitoringInstrumentation или AndroidJUnitRunner . Если вы используете другой Instrumentation , необходимо переопределить его метод onCreate() следующим кодом:

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