Как сжать код-65K предел метода в dex


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

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

1) сожмите ваш код

2) построить несколько файлов dex (смотрите это сообщение в блоге)

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

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

12 87

12 ответов:

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

о пределе ссылки 65K

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

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

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

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

Android 5.0 и выше использует среду выполнения под названием ART, которая изначально поддерживает загрузку нескольких файлов dex из приложение APK файлы. ИСКУССТВО выполняет предварительную компиляцию во время установки приложения, которое сканирует учебные занятия.(.Северный.)DeX файлы и компилирует их в один .файл овса для исполнение устройства Android. Для получения дополнительной информации на Андроид 5.0 runtime, см. знакомство с искусством.

посмотреть: создание приложений с более чем 65K методов


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

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

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

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

com.андроид.поддержка: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) если у вас приложение класса для вашего приложения, затем переопределить 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.поддержка.мультидекс.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

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

начиная с версии 6.5, вы можете вместо этого выборочно компилировать API сервиса Google Play в свое приложение. Например, чтобы включить только API Google Fit и Android Wear, замените следующее линия в вашей сборке.файл 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, переключение на библиотеку поддержки V7 appcompat также уменьшит количество ваших методов на много (на основе личного опыта). Инструкции находятся :

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

в вашем случае вы просто разорвете все из Google Play Services jar, кроме commoninternal и drive суб-пакетов.

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

Я использую strip_play_services.sh по дексторе.

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

Я думаю, что в долгосрочной перспективе ломать ваше приложение в нескольких 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
}

}

затем примените этот скрипт в своей сборке.Gradle, а такой

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

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

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

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

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

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

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

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