Как уменьшить код - лимит метода 65k в dex

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

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

1) Сожмите свой код

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

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

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

12 ответов

Решение

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

О пределе 65K

Файлы приложения Android (APK) содержат исполняемые файлы байт-кода в виде файлов Dalvik Executable (DEX), которые содержат скомпилированный код, используемый для запуска вашего приложения. Спецификация исполняемого файла Dalvik ограничивает общее количество методов, на которые можно ссылаться в одном файле DEX, до 65536, включая методы платформы 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 выполняет предварительную компиляцию во время установки приложения, которая сканирует файлы классов (.. N).dex и компилирует их в один файл.oat для исполнения устройством Android. Для получения дополнительной информации о среде выполнения Android 5.0 см. Введение в ART.

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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, кроме commoninternal а также drive суб-пакеты.

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам