Как сжимать код - предел метода 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)?

Ответ 1

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

О контрольном пределе 65K

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

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

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

Поддержка нескольких приложений для Android 5.0 и выше

Android 5.0 и выше использует время выполнения, называемое ART, которое изначально поддерживает загрузку нескольких файлов dex из файлов APK приложения. ИЗОБРАЗИТЕЛЬНОЕ ИСКУССТВО выполняет предварительную компиляцию во время установки приложения, которая сканирует классов (..N).dex и скомпилирует их в один файл .oat для исполнение на устройстве Android. Для получения дополнительной информации об Android 5.0, см. Представление ART.

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


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

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

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

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

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


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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

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

Если этого недостаточно, вы можете использовать gradle script. Поместите этот код в файл '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
}

}

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

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

Ответ 11

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

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

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

Ответ 12

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

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

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

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