Як зменшити код - обмеження методу 65k у dex


91

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

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

1) Зменште свій код

2) Створіть кілька файлів dex ( див. Цю публікацію в блозі )

Я розглянув обидва варіанти і спробував з’ясувати, що змушує мою кількість методів піти так високо. API Google Drive приймає найбільшу порцію із залежністю від Гуави - понад 12 000. Загальна кількість ресурсів для Drive API v2 сягає понад 23000!

Моє питання, я здогадуюсь, що, на вашу думку, я повинен робити? Чи слід вилучати інтеграцію з Google Drive як функцію мого додатка? Чи є спосіб зменшити API (так, я використовую proguard)? Чи варто йти маршрутом декількох dex (що виглядає досить болісно, ​​особливо якщо мова йде про сторонні API)?


2
Я випадково люблю ваш додаток. Чи замислювались ви про необхідне завантаження всіх зайвих бібліотек у псевдо apkформі? Я особисто хотів би бачити інтеграцію Drive
JBirdVegas

8
Нещодавно Facebook задокументував їх вирішення, що здається майже ідентичною проблемою в їх додатку для Android. Може бути корисним: facebook.com/notes/facebook-engineering/…
Рубен Скраттон,

4
Починаючи прямувати по багаторазовому маршруту dex. Я успішно створив вторинний файл dex для роботи з Google Drive. Мені погано до того, кому потрібна гуава як залежність. : P Це все ще досить велика проблема для мене
Джаред Раммлер,

4
як порахувати методи?
Bri6ko

1
Деякі додаткові примітки тут: stackoverflow.com/questions/21490382 (включаючи посилання на утиліту, яка перелічить посилання на методи у файлі .apk). Зверніть увагу, що обмеження 64 КБ не пов’язане з проблемою Facebook, пов’язаною з кількома коментарями.
fadden

Відповіді:


69

Схоже, Google нарешті реалізував обхідний шлях / виправлення для перевищення межі методу 65K файлів dex.

Про ліміт 65K

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

Підтримка 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 тис


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

Ця бібліотека забезпечує підтримку створення додатків з декількома виконуваними файлами Dalvik (DEX). Додатки, що посилаються на більш ніж 65536 методів, потрібні для використання конфігурацій багатовимірного тексту. Для отримання додаткової інформації про використання мультидексу див. Створення додатків із методами понад 65 тис .

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

Ідентифікатор залежності сценарію побудови Gradle для цієї бібліотеки такий:

com.android.support:multidex:1.0.+ Цей запис залежностей визначає версію версії 1.0.0 або новішу.


Вам все одно слід уникати досягнення межі методу 65K, активно використовуючи proguard та переглядаючи свої залежності.


6
+1, чому люди не голосують за правильні відповіді, коли на них відповідає одна і та ж людина?
Pacerier

мінімальний рівень API стає 14!
Vihaan Verma,

5
Ми написали невеликий плагін Gradle, щоб дати вам поточний підрахунок методів при кожній побудові. Нам було корисно керувати бібліотеками - github.com/KeepSafe/dexcount-gradle-plugin
philipp

53

Ви можете використовувати бібліотеку підтримки 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>

і це повинно працювати нормально!


31

Play Services6.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: 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-services-maps: 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

Будь-яка інформація про те, як це зробити в рамках проекту Eclipse?
Brian White

Я ще не в змозі перейти на цю версію. Але якщо ваш проект заснований на Maven, то, сподіваємось, вам просто доведеться вирішити це у вашому maven pom.
Csaba Toth

@ webo80 Ну, це допомагає, лише якщо у вас версія до 6.5.87. Цікаво про відповідь Піті, що прогуар позбавляє невикористаних функцій. Цікаво, чи це також стосується 2-ї партії, або просто ваші власні речі. Мені слід прочитати більше про прогард.
Csaba Toth

@BrianWhite На сьогодні єдиним рішенням є видалення файлу .jar за допомогою якогось зовнішнього інструменту ..
milosmns

У підсумку я використав цей інструмент: gist.github.com/dextorer/a32cad7819b7f272239b
Брайан Уайт

9

У версіях сервісів 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'

для отримання додаткової довідки ви можете натиснути тут


Як це зробити в затемненні?
Hardik9850

7

Використовуйте Proguard, щоб полегшити ваш apk, оскільки невикористані методи не будуть у вашій остаточній збірці. Перевірте, що у вашому конфігураційному файлі proguard є наступне, щоб використовувати proguard з гуавою (вибачаюся, якщо у вас це вже є, на момент написання статті це не було відомо):

# 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, перехід на бібліотеку підтримки vcom appcompat також значно зменшить кількість методів (на основі особистого досвіду). Інструкції знаходяться:


це виглядає багатообіцяючим, але я отримав Warning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessorпід час бігу./gradlew :myapp:proguardDevDebug
ericn

1
Однак під час розробки Proguard зазвичай не запускається (нарешті, не з Eclipse), тому ви не зможете отримати вигоду від усадки, поки не зробите збірку релізу.
Брайан Уайт

7

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

У вашому випадку ви просто ріпи все від Google Play Services банки , за винятком common internalі driveсуб-пакети.


4

Для користувачів Eclipse, які не використовують Gradle, є інструменти, які розберуть банку Служб Google Play і відновлять її лише з потрібними деталями.

я використовую strip_play_services.sh від dextorer .

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


3

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


2
Я шукаю правильний спосіб зробити це з Gradle: - / Будь-який підказка?
Іван Моргілло


2

Якщо не використовувати мультидекс, що робить процес збірки дуже повільним. Ви можете зробити наступне. Як згадував 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'

1

Якщо ви користуєтеся Службами Google Play, ви можете знати, що він додає 20 тисяч методів. Як уже зазначалося, Android Studio має можливість модульного включення певних служб, але користувачі, застряглі в Eclipse, повинні взяти модуляризацію у свої руки :(

На щастя, існує сценарій оболонки, який робить роботу досить простою. Просто витягніть до каталогу jar служби google play, відредагуйте наданий файл .conf за необхідності та запустіть скрипт оболонки.

Приклад його використання наведено тут .


1

Якщо ви користуєтеся Службами Google Play, ви можете знати, що вона додає 20 тисяч методів. Як уже зазначалося, Android Studio має можливість модульного включення певних служб, але користувачі, застряглі в Eclipse, повинні взяти модуляризацію у свої руки :(

На щастя, існує сценарій оболонки, який робить роботу досить простою. Просто витягніть до каталогу jar служби google play, відредагуйте наданий файл .conf за необхідності та запустіть скрипт оболонки.

Приклад його використання наведено тут.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.