Как сжимать код – предел метода 65k в dex

У меня довольно большое приложение для Android, которое основано на многих библиотечных проектах. Компилятор Android имеет ограничение 65536 методов на файл .dex, и я превосхожу его.

Есть два пути, которые вы можете выбрать (по крайней мере, я знаю), когда вы нажмете ограничение метода.

1) Сократите свой код

2) Создайте несколько файлов dex ( см. Это сообщение в блоге )

Я заглянул в оба и попытался выяснить, что заставляет мой метод рассчитывать так высоко. API Google Диска занимает самый большой кусок с зависимостью от Guava более 12 000. Всего libs для Drive API v2 достигает более 23 000!

Мой вопрос, я думаю, что, как вы думаете, мне следует делать? Должен ли я удалить интеграцию с Google Диском в качестве функции моего приложения? Есть ли способ уменьшить API вниз (да, я использую proguard)? Должен ли я перейти на несколько маршрутов dex (что выглядит довольно болезненно, особенно в отношении сторонних API)?

Похоже, что Google наконец-то применил обходное решение / исправление для превзойти предел меток 65K для файлов dex.

О контрольном пределе 65 тыс.

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

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

Версии платформы до Android 5.0 используют рабочую среду Dalvik для выполнения кода приложения. По умолчанию Dalvik ограничивает приложения одним файлом байт-кода classes.dex для APK. Чтобы обойти это ограничение, вы можете использовать библиотеку поддержки multidex , которая становится частью основного файла DEX вашего приложения, а затем управляет доступом к дополнительным файлам DEX и содержащемуся в них коду.

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

Android 5.0 и выше использует среду выполнения, называемую ART, которая поддерживает загрузку нескольких файлов dex из файлов APK приложения. ART выполняет предварительную компиляцию во время установки приложения, которая сканирует файлы classов (..N) .dex и компилирует их в один файл .oat для исполнения устройством Android. Дополнительные сведения о времени выполнения Android 5.0 см. В разделе Введение в ART .

См. Создание приложений с более чем 65 тыс. Методов


Библиотека поддержки Multidex

Эта библиотека обеспечивает поддержку для создания приложений с несколькими файлами Dalvik Executable (DEX). Для использования multidex-конфигураций необходимы приложения, которые ссылаются на более 65536 методов. Дополнительные сведения об использовании multidex см. В разделе « Создание приложений с более чем 65K методами» .

Эта библиотека находится в каталоге / extras / android / support / multidex / после загрузки библиотек поддержки Android. Библиотека не содержит ресурсов пользовательского интерфейса. Чтобы включить его в свой проект приложения, следуйте инструкциям по добавлению библиотек без ресурсов.

Идентификатор зависимостей скрипта сборки Gradle для этой библиотеки выглядит следующим образом:

com.android.support:multidex:1.0.+ Эта нотация зависимостей определяет версию версии 1.0.0 или новее.


Вы все равно должны избегать ограничения метода 65K, активно используя proguard и анализируя ваши зависимости.

вы можете использовать библиотеку поддержки multidex для этого, Чтобы включить multidex

1) включить его в зависимости:

 dependencies { ... compile 'com.android.support:multidex:1.0.0' } 

2) Включите его в своем приложении:

 defaultConfig { ... minSdkVersion 14 targetSdkVersion 21 .... multiDexEnabled true } 

3) если у вас есть class приложения для вашего приложения, то переопределите метод attachBaseContext следующим образом:

 package ....; ... import android.support.multidex.MultiDex; public class MyApplication extends Application { .... @Override protected void attachBaseContext(Context context) { super.attachBaseContext(context); MultiDex.install(this); } } 

4) если у вас нет classа приложения для вашего приложения, зарегистрируйте файл android.support.multidex.MultiDexApplication в качестве приложения в файле манифеста. как это:

  ...  

и он должен работать нормально!

Play Services 6.5+ помогают: http://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

«Начиная с версии 6.5, сервисов Google Play, вы сможете выбрать из нескольких отдельных API, и вы можете увидеть”

«это будет транзитивно включать« базовые »библиотеки, которые используются во всех API-интерфейсах».

Это хорошая новость, для простой игры, например, вам, вероятно, нужна только base , games и, возможно, drive .

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

  • com.google.android.gms: стыковые услуги базы: 6.5.87
  • com.google.android.gms: стыковые услуги-объявления: 6.5.87
  • com.google.android.gms: игры-сервисы appindexing: 6.5.87
  • com.google.android.gms: плей-сервис-карта: 6.5.87
  • com.google.android.gms: игры-сервисы Откуда: 6.5.87
  • com.google.android.gms: стыковые услуги фитнес: 6.5.87
  • com.google.android.gms: стыковые услуги-панорама: 6.5.87
  • com.google.android.gms: стыковые услуги привода: 6.5.87
  • com.google.android.gms: стыковой сервис-игры: 6.5.87
  • com.google.android.gms: стыковые услуги кошелек: 6.5.87
  • com.google.android.gms: стыковые услуги-идентичность: 6.5.87
  • com.google.android.gms: стыковые услуги литья: 6.5.87
  • com.google.android.gms: стыковые услуги плюс: 6.5.87
  • com.google.android.gms: игры-сервисы appstate: 6.5.87
  • com.google.android.gms: стыковые услуги-носимый: 6.5.87
  • com.google.android.gms: игры-сервисы весь износ: 6.5.87

В версиях сервисов Google Play до 6.5 вам пришлось собрать весь пакет API в ваше приложение. В некоторых случаях это затрудняло сохранение количества методов в вашем приложении (включая интерфейсные API, библиотечные методы и собственный код) в соответствии с лимитом 65 536.

Начиная с версии 6.5, вы можете выборочно компилировать API-интерфейсы Google Play в свое приложение. Например, чтобы включить только API Google Fit и Android Wear, замените следующую строку в файле build.gradle:

 compile 'com.google.android.gms:play-services:6.5.87' 

с этими строками:

 compile 'com.google.android.gms:play-services-fitness:6.5.87' compile 'com.google.android.gms:play-services-wearable:6.5.87' 

для получения дополнительной информации вы можете нажать здесь

Используйте proguard, чтобы облегчить ваш apk, поскольку методы, которые не используются, не будут в вашей окончательной сборке. Дважды проверьте, есть ли в вашем файле конфигурации proguard использование proguard с guava (мои извинения, если у вас уже есть это, он не был известен на момент написания):

 # Guava exclusions (http://code.google.com/p/guava-libraries/wiki/UsingProGuardWithGuava) -dontwarn sun.misc.Unsafe -dontwarn com.google.common.collect.MinMaxPriorityQueue -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } # Guava depends on the annotation and inject packages for its annotations, keep them both -keep public class javax.annotation.** -keep public class javax.inject.** 

Кроме того, если вы используете ActionbarSherlock, переключение на библиотеку поддержки appcompat v7 также значительно сократит количество ваших методов (на основе личного опыта). Инструкции:

Вы можете использовать Jar Jar Links для сжатия огромных внешних библиотек, таких как Google Play Services (16K-методы!)

В вашем случае вы просто будете копировать все из баннера Google Play Services, за исключением common internal и drive -подпакетов.

Для пользователей Eclipse, не использующих Gradle, есть инструменты, которые разбивают банку Google Play Services и восстанавливают ее только теми частями, которые вы хотите.

Я использую strip_play_services.sh by dextorer .

Трудно точно узнать, какие службы включить, потому что есть некоторые внутренние зависимости, но вы можете начать с малого и добавить в конфигурацию, если окажется, что необходимые вещи отсутствуют.

Я думаю, что в долгосрочной перспективе нарушение вашего приложения в нескольких dex было бы лучшим способом.

Официальным решением этой проблемы будет поддержка Multi-Dex. См. Мой ответ здесь для деталей.

Если не использовать multidex, который делает процесс сборки очень медленным. Вы можете сделать следующее. Как упоминалось в yahska, используйте специальную библиотеку сервисов Google Play. В большинстве случаев это необходимо.

 compile 'com.google.android.gms:play-services-base:6.5.+' 

Вот все доступные пакеты. Выборочно компилируете API в свой исполняемый файл.

Если этого недостаточно, вы можете использовать скрипт gradleиента. Поместите этот код в файл ‘strip_play_services.gradle’

 def toCamelCase(String string) { String result = "" string.findAll("[^\\W]+") { String word -> result += word.capitalize() } return result } afterEvaluate { project -> Configuration runtimeConfiguration = project.configurations.getByName('compile') println runtimeConfiguration ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult // Forces resolve of configuration ModuleVersionIdentifier module = resolution.getAllComponents().find { it.moduleVersion.name.equals("play-services") }.moduleVersion def playServicesLibName = toCamelCase("${module.group} ${module.name} ${module.version}") String prepareTaskName = "prepare${playServicesLibName}Library" File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir def tmpDir = new File(project.buildDir, 'intermediates/tmp') tmpDir.mkdirs() def libFile = new File(tmpDir, "${playServicesLibName}.marker") def strippedClassFileName = "${playServicesLibName}.jar" def classesStrippedJar = new File(tmpDir, strippedClassFileName) def packageToExclude = ["com/google/ads/**", "com/google/android/gms/actions/**", "com/google/android/gms/ads/**", // "com/google/android/gms/analytics/**", "com/google/android/gms/appindexing/**", "com/google/android/gms/appstate/**", "com/google/android/gms/auth/**", "com/google/android/gms/cast/**", "com/google/android/gms/drive/**", "com/google/android/gms/fitness/**", "com/google/android/gms/games/**", "com/google/android/gms/gcm/**", "com/google/android/gms/identity/**", "com/google/android/gms/location/**", "com/google/android/gms/maps/**", "com/google/android/gms/panorama/**", "com/google/android/gms/plus/**", "com/google/android/gms/security/**", "com/google/android/gms/tagmanager/**", "com/google/android/gms/wallet/**", "com/google/android/gms/wearable/**"] Task stripPlayServices = project.tasks.create(name: 'stripPlayServices', group: "Strip") { inputs.files new File(playServiceRootFolder, "classes.jar") outputs.dir playServiceRootFolder description 'Strip useless packages from Google Play Services library to avoid reaching dex limit' doLast { def packageExcludesAsString = packageToExclude.join(",") if (libFile.exists() && libFile.text == packageExcludesAsString && classesStrippedJar.exists()) { println "Play services already stripped" copy { from(file(classesStrippedJar)) into(file(playServiceRootFolder)) rename { fileName -> fileName = "classes.jar" } } } else { copy { from(file(new File(playServiceRootFolder, "classes.jar"))) into(file(playServiceRootFolder)) rename { fileName -> fileName = "classes_orig.jar" } } tasks.create(name: "stripPlayServices" + module.version, type: Jar) { destinationDir = playServiceRootFolder archiveName = "classes.jar" from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) { exclude packageToExclude } }.execute() delete file(new File(playServiceRootFolder, "classes_orig.jar")) copy { from(file(new File(playServiceRootFolder, "classes.jar"))) into(file(tmpDir)) rename { fileName -> fileName = strippedClassFileName } } libFile.text = packageExcludesAsString } } } project.tasks.findAll { it.name.startsWith('prepare') && it.name.endsWith('Dependencies') }.each { Task task -> task.dependsOn stripPlayServices } project.tasks.findAll { it.name.contains(prepareTaskName) }.each { Task task -> stripPlayServices.mustRunAfter task } 

}

Затем примените этот скрипт в свой build.gradle, как это

 apply plugin: 'com.android.application' apply from: 'strip_play_services.gradle' 

При использовании сервисов Google Play вы можете знать, что он добавляет 20k + методов. Как уже упоминалось, Android Studio имеет возможность модульного включения конкретных сервисов, но пользователи, застрявшие с Eclipse, должны в свою очередь принять модуляцию 🙁

К счастью, есть сценарий оболочки, который упрощает работу. Просто извлеките в каталог jar google play services, отредактируйте предоставленный .conf файл по мере необходимости и выполните сценарий оболочки.

Пример его использования здесь .

При использовании сервисов Google Play вы можете знать, что он добавляет 20k + методов. Как уже упоминалось, Android Studio имеет возможность модульного включения конкретных сервисов, но пользователи, застрявшие с Eclipse, должны в свою очередь принять модуляцию 🙁

К счастью, есть сценарий оболочки, который упрощает работу. Просто извлеките в каталог jar google play services, отредактируйте предоставленный .conf файл по мере необходимости и выполните сценарий оболочки.

Пример его использования здесь.

Как и он сказал, я заменяю compile 'com.google.android.gms:play-services:9.0.0' только с теми библиотеками, которые мне нужны, и это сработало.

  • Заменить селекторные изображения программно
  • Как вы можете получить адрес электронной почты пользователя Android?
  • Рекомендуемый способ / порядок чтения данных из веб-службы, анализ этих данных и их вставка в SQLite db
  • Не удалось выполнить синхронизацию проекта Gradle после того, как Google объявила о новой системе управления версиями sdk
  • Как я могу настроить фокус (и отображать клавиатуру) на моем EditText программно
  • Запуск пользовательского приложения Android из браузера Android / Chrome
  • Синхронизация данных между Android-приложением и веб-сервером
  • Очистить кэш в приложении Android программно
  • Служба Android для проверки возможности подключения к Интернету?
  • Уведомление: активность уже открыта
  • Android: как включить и выключить экран программно?
  • Давайте будем гением компьютера.