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

О эталонном пределе 64 КБ

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

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

Поддержка 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 или выше, мультидексация включена по умолчанию, и библиотека мультидекса вам не нужна.

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

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

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

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

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

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

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

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

Примечание. Если для minSdkVersion установлено значение 21 или выше, мультидексация включена по умолчанию, и вам не нужна библиотека мультидекса.

Если для вашего 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) чтобы включить мультидекс:

      Котлин

      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 недостаточно для обхода ограничения линейного выделения ( проблема 37008143 ). Этот предел был увеличен в Android 4.0 (уровень API 14), но это не решило проблему полностью.

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

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

Объявите классы, необходимые в основном файле DEX.

При создании каждого файла DEX для мультидекс-приложения инструменты сборки выполняют сложный процесс принятия решений, чтобы определить, какие классы необходимы в основном файле 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 в сборках разработки

Мультидексная конфигурация требует значительного увеличения времени обработки сборки, поскольку система сборки должна принимать сложные решения о том, какие классы должны быть включены в основной файл DEX, а какие классы могут быть включены во вторичные файлы DEX. Это означает, что инкрементные сборки с использованием 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 или командной строки, прочтите статью «Оптимизация скорости сборки» . Дополнительные сведения об использовании вариантов сборки см. в разделе Настройка вариантов сборки .

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

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

При написании инструментальных тестов для мультидекс-приложений дополнительная настройка не требуется, если вы используете инструментарий 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);
  ...
}