wake-up-neo.com

So verkleinern Sie den Code - 65k-Methodenlimit in dex

Ich habe eine ziemlich große Android App, die sich auf viele Bibliotheksprojekte stützt. Der Android= Compiler hat eine Beschränkung von 65536 Methoden pro .dex Datei und ich übertreffe das Nummer.

Es gibt grundsätzlich zwei Wege, die Sie wählen können (zumindest die mir bekannten), wenn Sie das Methodenlimit erreichen.

1) Verkleinern Sie Ihren Code

2) Erstellen Sie mehrere Dex-Dateien ( siehe diesen Blog-Beitrag )

Ich untersuchte beide und versuchte herauszufinden, warum meine Methodenzahl so hoch war. Die Google Drive-API nimmt den größten Anteil ein, wobei die Guava-Abhängigkeit bei über 12.000 liegt. Die Gesamtbibliothek für Drive API v2 erreicht über 23.000!

Meine Frage ist wohl, was denkst du soll ich tun? Sollte ich die Google Drive-Integration als Funktion meiner App entfernen? Gibt es eine Möglichkeit, die API zu verkleinern (ja, ich verwende Proguard)? Sollte ich den Mehrfach-Dex-Weg gehen (der ziemlich schmerzhaft aussieht, insbesondere im Umgang mit APIs von Drittanbietern)?

90
Jared Rummler

Offenbar hat Google endlich einen Workaround/Fix implementiert, um die 65-KB-Methode für Dex-Dateien zu überschreiten.

Über das 65K-Referenzlimit

Android-Anwendungsdateien (APK-Dateien) enthalten ausführbare Bytecode-Dateien in Form von Dalvik Executable-Dateien (DEX-Dateien), die den kompilierten Code enthalten, mit dem Ihre App ausgeführt wird. Die Dalvik Executable-Spezifikation beschränkt die Gesamtzahl der Methoden, auf die in einer einzelnen DEX-Datei verwiesen werden kann, auf 65.536, einschließlich Android Framework-Methoden, Bibliotheksmethoden und Methoden in Ihrem eigenen Code. Überschreiten dieser Grenze erfordert, dass Sie Ihren App-Erstellungsprozess so konfigurieren, dass mehr als eine DEX-Datei generiert wird, die als Multidex-Konfiguration bezeichnet wird.

Multidex-Unterstützung vor Android 5.0

Plattformversionen vor Android 5.0 verwenden die Dalvik-Laufzeit zur Ausführung von App-Code. Standardmäßig beschränkt Dalvik Apps auf eine einzige class.dex-Bytecode-Datei pro APK. Um diese Einschränkung zu umgehen Verwenden Sie die Multidex-Unterstützungsbibliothek , die Teil der primären DEX-Datei Ihrer App wird und den Zugriff auf die zusätzlichen DEX-Dateien und den darin enthaltenen Code verwaltet.

Multidex-Unterstützung für Android 5.0 und höher

Android 5.0 und höher verwendet eine Laufzeit namens ART, die das Laden mehrerer Dex-Dateien aus Anwendungs-APK-Dateien von Haus aus unterstützt. ART führt eine Vorkompilierung zum Zeitpunkt der Anwendungsinstallation durch, bei der nach Classes (.. N) .dex-Dateien gesucht und diese zur Ausführung durch das Gerät Android=) in eine einzige .oat-Datei kompiliert werden. Weitere Informationen zu die Android 5.0 Laufzeit, siehe Introducing ART .

Siehe: Erstellen von Apps mit über 65.000 Methoden


Multidex Support Library

Diese Bibliothek bietet Unterstützung für das Erstellen von Apps mit mehreren Dalvik Executable (DEX) -Dateien. Für die Verwendung von Multidex-Konfigurationen sind Apps erforderlich, die auf mehr als 65536-Methoden verweisen. Weitere Informationen zur Verwendung von multidex finden Sie unter Erstellen von Apps mit über 65 KB-Methoden .

Diese Bibliothek befindet sich im Verzeichnis/extras/Android/support/multidex /, nachdem Sie die Android Support-Bibliotheken) heruntergeladen haben. Die Bibliothek enthält keine Ressourcen für die Benutzeroberfläche. Folgen Sie den Anweisungen für Hinzufügen von Bibliotheken ohne Ressourcen.

Der Gradle-Buildskript-Abhängigkeitsbezeichner für diese Bibliothek lautet wie folgt:

com.Android.support:multidex:1.0.+ Diese Abhängigkeitsnotation gibt die Release-Version 1.0.0 oder höher an.


Sie sollten dennoch vermeiden, das 65-KB-Methodenlimit zu überschreiten, indem Sie proguard aktiv verwenden und Ihre Abhängigkeiten überprüfen.

67
Jared Rummler

sie können dafür die Multidex-Unterstützungsbibliothek verwenden. Um multidex zu aktivieren

1) füge es in Abhängigkeiten ein:

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

2) Aktiviere es in deiner App:

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

) Wenn Sie eine application -Klasse für Ihre App haben, überschreiben Sie die attachBaseContext -Methode wie folgt:

package ....;
...
import Android.support.multidex.MultiDex;

public class MyApplication extends Application {
  ....
   @Override
   protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    MultiDex.install(this);
   }
}

4) Wenn Sie nicht eine Anwendung Klasse für Ihre Anwendung haben, registrieren Sie sich Android.support.multidex.MultiDexApplication = als Ihre Anwendung in Ihrer Manifest-Datei. so was:

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

und es sollte gut funktionieren!

52
Prakhar

Play Services 6.5+ hilft: http://Android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

"Ab Version 6.5 der Google Play-Dienste können Sie aus einer Reihe von einzelnen APIs auswählen und sehen"

...

"Dies schließt transitiv die" Basis "-Bibliotheken ein, die für alle APIs verwendet werden."

Das sind gute Nachrichten, für ein einfaches Spiel brauchen Sie zum Beispiel wahrscheinlich nur base, games und vielleicht drive.

"Die vollständige Liste der API-Namen finden Sie unten. Weitere Details finden Sie auf der Android Entwicklerseite:

  • 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: Speicherort der Wiedergabedienste: 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
31
Csaba Toth

In Versionen von Google Play-Diensten vor 6.5 mussten Sie das gesamte API-Paket in Ihre App kompilieren. In einigen Fällen wurde es dadurch schwieriger, die Anzahl der Methoden in Ihrer App (einschließlich Framework-APIs, Bibliotheksmethoden und Ihres eigenen Codes) unter dem Grenzwert von 65.536 zu halten.

Ab Version 6.5 können Sie stattdessen selektiv Google Play-Dienst-APIs in Ihre App kompilieren. Um beispielsweise nur die APIs Google Fit und Android Wear) einzuschließen, ersetzen Sie die folgende Zeile in Ihrer build.gradle-Datei:

compile 'com.google.Android.gms:play-services:6.5.87'

mit diesen Zeilen:

compile 'com.google.Android.gms:play-services-fitness:6.5.87'
compile 'com.google.Android.gms:play-services-wearable:6.5.87'

für weitere Informationen klicken Sie auf hier

9
akshay

Sie können Jar Jar Links verwenden, um große externe Bibliotheken wie Google Play Services zu verkleinern (16K-Methoden!)

In Ihrem Fall rippen Sie einfach alles aus dem Google Play Services-Jar mit Ausnahme der Unterpakete commoninternal und drive.

7
pixel

Verwende proguard, um deine apk zu vereinfachen, da nicht verwendete Methoden nicht in deinem endgültigen Build sind. Überprüfe noch einmal, ob du in deiner Proguard-Konfigurationsdatei Folgendes angegeben hast, um Proguard mit Guave zu verwenden (ich entschuldige mich, wenn du das bereits hast, war es zum Zeitpunkt des Schreibens noch nicht bekannt):

# 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.**

Wenn Sie ActionbarSherlock verwenden, wird durch den Wechsel zur Supportbibliothek von v7 appcompat auch die Anzahl Ihrer Methoden um ein Vielfaches reduziert (basierend auf persönlicher Erfahrung). Anweisungen befinden sich:

7
petey

Für Eclipse-Benutzer, die Gradle nicht verwenden, gibt es Tools, die das Google Play Services-Jar auflösen und es nur mit den gewünschten Teilen neu erstellen.

Ich benutze strip_play_services.sh von dextorer .

Es kann schwierig sein, genau zu wissen, welche Services einbezogen werden sollen, da es einige interne Abhängigkeiten gibt. Sie können jedoch klein anfangen und die Konfiguration erweitern, wenn sich herausstellt, dass die erforderlichen Dinge fehlen.

4
Brian White

Ich denke, dass es auf lange Sicht der beste Weg ist, Ihre App in mehrere Dex zu zerlegen.

3
prmottajr

Wenn Sie multidex nicht verwenden, ist der Build-Prozess sehr langsam. Sie können Folgendes tun. Wie yahska erwähnt, benutze eine bestimmte Google Play Service Bibliothek. In den meisten Fällen wird nur dies benötigt.

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

Hier finden Sie alle verfügbaren Pakete Selektives Kompilieren von APIs in Ihre ausführbare Datei

Wenn dies nicht ausreicht, können Sie gradle script verwenden. Gib diesen Code in die Datei 'strip_play_services.gradle' ein

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
}

}

Wenden Sie dieses Skript dann wie folgt in Ihrem build.gradle an

apply plugin: 'com.Android.application'
apply from: 'strip_play_services.gradle'
2
Lemberg

Multi-Dex-Unterstützung ist werde sein die offizielle Lösung für dieses Problem. Siehe meine Antwort hier für die Details.

2
Alex Lipov

Wenn Sie Google Play Services verwenden, wissen Sie möglicherweise, dass dadurch mehr als 20.000 Methoden hinzugefügt werden. Wie bereits erwähnt, hat Android Studio die Möglichkeit, bestimmte Dienste modular einzubinden, aber Benutzer, die mit Eclipse nicht weiterkommen, müssen die Modularisierung selbst in die Hand nehmen :(

Glücklicherweise gibt es ein Shell-Skript, das die Arbeit ziemlich einfach macht. Extrahieren Sie einfach in das JAR-Verzeichnis von Google Play Services, bearbeiten Sie die mitgelieferte CONF-Datei nach Bedarf und führen Sie das Shell-Skript aus.

Ein Beispiel für die Verwendung ist hier.

Wie er sagte, ersetze ich compile 'com.google.Android.gms:play-services:9.0.0' nur mit den Bibliotheken, die ich brauchte und es hat funktioniert.

1
Tamir Gilany

Wenn Sie Google Play Services verwenden, wissen Sie möglicherweise, dass dadurch mehr als 20.000 Methoden hinzugefügt werden. Wie bereits erwähnt, hat Android Studio die Möglichkeit, bestimmte Dienste modular einzubinden, aber Benutzer, die mit Eclipse nicht weiterkommen, müssen die Modularisierung selbst in die Hand nehmen :(

Zum Glück es gibt ein Shell-Skript , das die Arbeit ziemlich einfach macht. Extrahieren Sie einfach in das JAR-Verzeichnis von Google Play Services, bearbeiten Sie die mitgelieferte CONF-Datei nach Bedarf und führen Sie das Shell-Skript aus.

Ein Beispiel für seine Verwendung ist hier .

1
Tom