Как уменьшить код - ограничение метода 65k в индексе

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

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

1) Уменьшите свой код

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

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

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


person Jared Rummler    schedule 18.03.2013    source источник
comment
Мне нравится ваше приложение. Вы думали о том, чтобы сделать обязательную загрузку всех дополнительных библиотек в псевдо-форме apk? Я лично хотел бы видеть интеграцию с Диском   -  person JBirdVegas    schedule 18.03.2013
comment
Хм, вы можете объяснить, почему proguard не решает проблему?   -  person Kevin Bourrillion    schedule 18.03.2013
comment
Proguard помогает, но не сильно. Для тестирования я создал проект из краткого руководства. Я добавил предложенный конфиг в proguard и результат сэкономил мне порядка 2-3к методов. Это помогает, но проект по-прежнему превышал 22 000, что является большим куском.   -  person Jared Rummler    schedule 18.03.2013
comment
Facebook недавно задокументировал свой обходной путь для почти идентичной проблемы в своем приложении для Android. Может быть полезно: facebook.com/notes/facebook-engineering/   -  person Reuben Scratton    schedule 18.03.2013
comment
Начинаем двигаться по множественному маршруту dex. Я успешно создал дополнительный файл dex для работы с Google Диском. Я чувствую себя плохо для тех, кто нуждается в гуаве в качестве зависимости. :P Это все еще довольно большая проблема для меня.   -  person Jared Rummler    schedule 19.03.2013
comment
@JaredRummler Привет, я сталкиваюсь с такими же проблемами, как и ты. Однако я застрял во время перехода от затмения к муравью. Вы не против взглянуть? stackoverflow.com/questions/18629021/   -  person Cheok Yan Cheng    schedule 05.09.2013
comment
@JaredRummler, если вы используете actionbarsherlock, переключитесь на ACtionBarAppCompat.   -  person petey    schedule 20.12.2013
comment
Какую версию гуавы вы сейчас используете?   -  person petey    schedule 20.12.2013
comment
как считать методы?   -  person Bri6ko    schedule 03.06.2014
comment
@Bri6ko gist.github.com/JakeWharton/6002797 cat $dexfile | голова -c 92 | хвост -c 4 | hexdump -e '1/4 %d\n'   -  person Jared Rummler    schedule 06.06.2014
comment
Некоторые дополнительные примечания здесь: stackoverflow.com/questions/21490382 (включая ссылку на утилиту, которая перечислит ссылки на методы в АПК). Обратите внимание, что ограничение в 64 КБ не связано с проблемой Facebook, связанной с несколькими комментариями.   -  person fadden    schedule 09.06.2014
comment
Вот скрипт, который я написал для методов подсчета в папке jar gist.github.com/toms972/c83504df2da1176a248a   -  person Tom Susel    schedule 28.07.2014
comment
Только для новичков, родившихся в эпоху Mac OS X. Это ограничение в 64K dex абсолютно напоминает мне о конце эпохи Mac OS 8/9: ограничение CodeWarrior в 64K TOC: compgroups.net/comp.mac.codewarrior/toc-size-to-big-how-to-fix/ Не только глобальные переменные, но и виртуальные функции в классах и другие вещи также увеличивали размер оглавления, поэтому, если у вас просто был достаточно большой исполняемый файл, вы облажались. Спустя десять лет я снова столкнулся с ограничением в 64 КБ. ART заменяет Dalvik, надеюсь, у него не будет такого ограничения.   -  person Csaba Toth    schedule 29.01.2015
comment
@CsabaToth ART не изменил лимит.   -  person Jared Rummler    schedule 29.01.2015
comment
@JaredRummler Нееет! Кстати, не для придирки, но 65536 - это 64К, а не 65К. Хотя даже Google ссылается на 65K developer.android.com/tools/building/multidex.html< /а>   -  person Csaba Toth    schedule 29.01.2015
comment
Мы написали плагин Gradle, который подсчитывает количество методов после каждой сборки @Bri6ko.   -  person philipp    schedule 05.06.2015
comment
@CsabaToth 65K относится к 65 000   -  person cren90    schedule 29.07.2015
comment
Это 64к, а не 65к. 65535.   -  person user207421    schedule 21.07.2016


Ответы (12)


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

Об ограничении ссылок в 65 КБ

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

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

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

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

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

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


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

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

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

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

com.android.support:multidex:1.0.+ Это обозначение зависимости указывает версию выпуска 1.0.0 или выше.


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

person Jared Rummler    schedule 03.11.2014
comment
+1, почему люди не голосуют за правильные ответы, когда на них отвечает один и тот же человек? - person Pacerier; 12.11.2014
comment
минимальный уровень API становится 14! - person Vihaan Verma; 13.04.2015
comment
Мы написали небольшой плагин для Gradle, чтобы дать вам текущее количество методов для каждой сборки. Нам помогло управлять библиотеками - github.com/KeepSafe/dexcount-gradle-plugin - person philipp; 31.07.2015

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

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

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

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

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

3) если у вас есть класс application для вашего приложения, переопределите метод 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) если у вас нет класса application для вашего приложения, зарегистрируйте android.support.multidex.MultiDexApplication как ваше приложение в файле манифеста. нравится:

<application
    ...
    android:name="android.support.multidex.MultiDexApplication">
    ...
</application>

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

person Prakhar    schedule 12.04.2015

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 Developer:

  • com.google.android.gms:play-services-base:6.5.87
  • com.google.android.gms:play-services-ads:6.5.87
  • com.google.android.gms:play-services-appindexing:6.5.87
  • com.google.android.gms:play-сервисы-карты:6.5.87
  • com.google.android.gms:play-services-location:6.5.87
  • com.google.android.gms:play-services-fitness:6.5.87
  • com.google.android.gms:play-services-panorama:6.5.87
  • com.google.android.gms:play-services-drive:6.5.87
  • com.google.android.gms:play-services-games:6.5.87
  • com.google.android.gms:play-services-wallet:6.5.87
  • com.google.android.gms:play-services-identity:6.5.87
  • com.google.android.gms:play-services-cast:6.5.87
  • com.google.android.gms:play-services-plus:6.5.87
  • com.google.android.gms:play-services-appstate:6.5.87
  • com.google.android.gms:play-services-wearable:6.5.87
  • com.google.android.gms:play-services-all-wear:6.5.87
person Csaba Toth    schedule 19.01.2015
comment
Любая информация о том, как это сделать в проекте Eclipse? - person Brian White; 26.01.2015
comment
Я пока не могу перейти на эту версию. Но если ваш проект основан на Maven, то, надеюсь, вам просто нужно решить это в maven pom. - person Csaba Toth; 27.01.2015
comment
@ webo80 Ну, это помогает, только если у вас версия до 6.5.87. Меня интересует ответ Пити, что proguard убирает неиспользуемые функции. Интересно, это касается и сторонних библиотек, или только ваших собственных вещей. Я должен больше читать о proguard. - person Csaba Toth; 29.01.2015
comment
@BrianWhite Единственное решение на данный момент, похоже, состоит в том, чтобы удалить файл .jar с помощью какого-то внешнего инструмента. - person milosmns; 24.04.2015
comment
В итоге я использовал этот инструмент: gist.github.com/dextorer/a32cad7819b7f272239b - person Brian White; 24.04.2015
comment
@CsabaToth, ты спас меня! Я добавил только несколько из приведенного выше списка вместо полного «com.google.android.gms:play-services», и это имело значение! - person EZDsIt; 02.04.2016

В версиях сервисов 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'

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

person akshay    schedule 27.02.2015
comment
Как это сделать в затмении? - person hardik9850; 14.07.2015

Используйте 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 версии 7 также значительно уменьшит количество методов (исходя из личного опыта). Инструкция находится:

person petey    schedule 20.12.2013
comment
это выглядит многообещающе, но я получил Warning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessor при запуске ./gradlew :myapp:proguardDevDebug - person ericn; 05.01.2015
comment
Однако во время разработки proguard обычно не запускается (по крайней мере, не с Eclipse), поэтому вы не можете извлечь выгоду из сокращения до тех пор, пока не выполните сборку релиза. - person Brian White; 24.04.2015

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

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

person pixel    schedule 15.06.2014

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

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

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

person Brian White    schedule 24.04.2015

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

person prmottajr    schedule 09.12.2013
comment
Я ищу правильный способ сделать это с Gradle :-/ Любая подсказка? - person Ivan Morgillo; 01.07.2014

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

person Alex Lipov    schedule 04.10.2014

Если не использовать 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'
person Roman Nazarevych    schedule 23.04.2015

Если вы используете сервисы Google Play, вы, возможно, знаете, что он добавляет более 20 000 методов. Как уже упоминалось, в Android Studio есть возможность модульного включения определенных сервисов, но пользователи, застрявшие в Eclipse, должны взять модульность в свои руки :(

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

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

person Tom    schedule 28.05.2015

Если вы используете сервисы Google Play, вы, возможно, знаете, что он добавляет более 20 000 методов. Как уже упоминалось, в Android Studio есть возможность модульного включения определенных сервисов, но пользователи, застрявшие в Eclipse, должны взять модульность в свои руки :(

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

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

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

person Tamir Gilany    schedule 22.05.2016