Come ridurre il codice: limite del metodo di 65k in dex


91

Ho un'app Android piuttosto grande che si basa su molti progetti di libreria. Il compilatore Android ha una limitazione di 65536 metodi per file .dex e sto superando quel numero.

Ci sono fondamentalmente due percorsi che puoi scegliere (almeno che io sappia) quando raggiungi il limite del metodo.

1) Riduci il tuo codice

2) Crea più file dex ( vedi questo post del blog )

Ho esaminato entrambi e ho cercato di scoprire cosa causava il conteggio del mio metodo così alto. L'API di Google Drive prende il pezzo più grande con la dipendenza Guava a oltre 12.000. Le librerie totali per Drive API v2 raggiungono oltre 23.000!

La mia domanda, immagino, è: cosa pensi che dovrei fare? Devo rimuovere l'integrazione di Google Drive come funzionalità della mia app? C'è un modo per ridurre l'API (sì, io uso proguard)? Devo seguire il percorso di dex multiplo (che sembra piuttosto doloroso, soprattutto quando si tratta di API di terze parti)?


2
Mi capita di amare la tua app. Hai pensato di effettuare un download obbligatorio di tutte le librerie extra in una apkforma pseudo ? Personalmente vorrei vedere l' integrazione di Drive
JBirdVegas

8
Facebook ha recentemente documentato la loro soluzione alternativa per quello che sembra un problema quasi identico nella loro app Android. Può essere utile: facebook.com/notes/facebook-engineering/…
Reuben Scratton

4
Iniziando a dirigersi verso il percorso a più dex. Ho creato con successo un file dex secondario per lavorare con Google Drive. Mi dispiace per chiunque abbia bisogno di guava come dipendenza. : P Per me è ancora un grosso problema
Jared Rummler

4
come conti i metodi?
Bri6ko

1
Alcune note aggiuntive qui: stackoverflow.com/questions/21490382 (incluso un collegamento a un'utilità che elencherà i riferimenti al metodo in un APK). Nota che il limite di 64K non è correlato al problema di Facebook collegato ad alcuni commenti.
fadden

Risposte:


69

Sembra che Google abbia finalmente implementato una soluzione alternativa / correzione per superare il limite del metodo 65K dei file dex.

Informazioni sul limite di riferimento di 65K

I file dell'applicazione Android (APK) contengono file bytecode eseguibili sotto forma di file Dalvik Executable (DEX), che contengono il codice compilato utilizzato per eseguire l'app. La specifica Dalvik Executable limita il numero totale di metodi a cui è possibile fare riferimento all'interno di un singolo file DEX a 65.536, inclusi metodi del framework Android, metodi di libreria e metodi nel tuo codice. Per superare questo limite è necessario configurare il processo di compilazione dell'app per generare più di un file DEX, noto come configurazione multidex.

Supporto multidex prima di Android 5.0

Le versioni della piattaforma precedenti ad Android 5.0 utilizzano il runtime Dalvik per eseguire il codice dell'app. Per impostazione predefinita, Dalvik limita le app a un singolo file bytecode classes.dex per APK. Per aggirare questa limitazione, puoi utilizzare la libreria di supporto multidex , che diventa parte del file DEX principale della tua app e quindi gestisce l'accesso ai file DEX aggiuntivi e al codice che contengono.

Supporto multidex per Android 5.0 e versioni successive

Android 5.0 e versioni successive utilizzano un runtime chiamato ART che supporta nativamente il caricamento di più file dex dai file APK dell'applicazione. ART esegue la pre-compilazione al momento dell'installazione dell'applicazione che scansiona i file delle classi (.. N) .dex e li compila in un singolo file .oat per l'esecuzione dal dispositivo Android. Per ulteriori informazioni sul runtime di Android 5.0, vedere Presentazione di ART .

Vedere: Creazione di app con metodi oltre 65.000


Libreria di supporto multidex

Questa libreria fornisce supporto per la creazione di app con più file Dalvik Executable (DEX). Le app che fanno riferimento a più di 65536 metodi sono necessarie per utilizzare le configurazioni multidex. Per ulteriori informazioni sull'utilizzo del multidex, vedere Creazione di app con metodi oltre 65.000 .

Questa libreria si trova nella directory / extras / android / support / multidex / dopo aver scaricato le librerie di supporto Android. La libreria non contiene risorse dell'interfaccia utente. Per includerlo nel progetto dell'applicazione, seguire le istruzioni per Aggiungere librerie senza risorse.

L'identificatore di dipendenza dello script di build Gradle per questa libreria è il seguente:

com.android.support:multidex:1.0.+ Questa notazione di dipendenza specifica la versione 1.0.0 o successiva del rilascio.


Dovresti comunque evitare di raggiungere il limite del metodo 65K utilizzando attivamente proguard e rivedendo le tue dipendenze.


6
+1, Perché le persone non danno un voto positivo alle risposte corrette quando ricevono risposta dalla stessa persona?
Pacerier

il livello API minimo diventa 14!
Vihaan Verma

5
Abbiamo scritto un piccolo plugin Gradle per darti il ​​conteggio del tuo metodo corrente su ogni build. Ci è stato utile per gestire le biblioteche - github.com/KeepSafe/dexcount-gradle-plugin
philipp

53

è possibile utilizzare la libreria di supporto multidex per questo, Per abilitare multidex

1) includilo nelle dipendenze:

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

2) Abilitalo nella tua app:

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

3) se hai una classe dell'applicazione per la tua app, sovrascrivi il metodo attachBaseContext in questo modo:

package ....;
...
import android.support.multidex.MultiDex;

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

4) se non disponi di una classe di applicazione per la tua applicazione, registra android.support.multidex.MultiDexApplication come applicazione nel tuo file manifest. come questo:

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

e dovrebbe funzionare bene!


31

Play Services6.5+ aiuta: http://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

"A partire dalla versione 6.5 di Google Play Services, potrai scegliere tra una serie di singole API e puoi vedere"

...

"questo includerà in modo transitorio le librerie 'di base', che vengono utilizzate in tutte le API."

Questa è una buona notizia, per un gioco semplice, ad esempio, probabilmente hai solo bisogno di base, gamese forse drive.

"L'elenco completo dei nomi delle API è riportato di seguito. Ulteriori dettagli sono disponibili sul sito degli sviluppatori 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

Qualche informazione su come farlo all'interno di un progetto Eclipse?
Brian White

Non sono ancora in grado di eseguire l'aggiornamento a quella versione. Ma se il tuo progetto è basato su Maven, spero che tu debba risolverlo in te maven pom.
Csaba Toth

@ webo80 Bene, questo aiuta solo se sei fino alla versione 6.5.87. Mi chiedo quale sia la risposta di Petey, che il programma elimina le funzioni inutilizzate. Mi chiedo se ciò coinvolga anche le librerie di 2a festa o solo le tue cose. Dovrei leggere di più su proguard.
Csaba Toth

@BrianWhite L'unica soluzione per ora sembra rimuovere il file .jar con qualche strumento esterno ..
milosmns

Ho finito per usare questo strumento: gist.github.com/dextorer/a32cad7819b7f272239b
Brian White

9

Nelle versioni di Google Play Services precedenti alla 6.5, dovevi compilare l'intero pacchetto di API nella tua app. In alcuni casi, ciò ha reso più difficile mantenere il numero di metodi nella tua app (comprese le API del framework, i metodi di libreria e il tuo codice) sotto il limite di 65.536.

Dalla versione 6.5, puoi invece compilare selettivamente le API del servizio Google Play nella tua app. Ad esempio, per includere solo le API di Google Fit e Android Wear, sostituisci la seguente riga nel tuo file build.gradle:

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

con queste righe:

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

per ulteriori riferimenti, puoi fare clic qui


Come farlo in eclissi?
Hardik9850

7

Usa proguard per alleggerire il tuo apk poiché i metodi inutilizzati non saranno nella tua build finale. Controlla di aver seguito nel tuo file di configurazione proguard per utilizzare proguard con guava (mi scuso se lo hai già, non era noto al momento della scrittura):

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

Inoltre, se stai usando ActionbarSherlock, il passaggio alla libreria di supporto appcompat v7 ridurrà anche il numero di metodi di molto (in base all'esperienza personale). Le istruzioni si trovano:


questo sembra promettente ma ho avuto Warning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessordurante la corsa./gradlew :myapp:proguardDevDebug
Ericn

1
Durante lo sviluppo, tuttavia, proguard non viene generalmente eseguito (alla fine non con Eclipse), quindi non puoi beneficiare della riduzione fino a quando non esegui una build di rilascio.
Brian White

7

Puoi usare Jar Jar Links per ridurre enormi librerie esterne come Google Play Services (metodi 16K!)

Nel tuo caso, estrarrai tutto dal barattolo di Google Play Services eccetto common internale drivesotto-pacchetti.


4

Per gli utenti Eclipse che non utilizzano Gradle, ci sono strumenti che abbatteranno il vaso di Google Play Services e lo ricostruiranno solo con le parti che desideri.

Uso strip_play_services.sh di dextorer .

Può essere difficile sapere esattamente quali servizi includere perché ci sono alcune dipendenze interne, ma puoi iniziare in piccolo e aggiungere alla configurazione se si scopre che mancano le cose necessarie.


3

Penso che a lungo termine rompere la tua app in più dex sarebbe il modo migliore.


2
Sto cercando un modo corretto per farlo con Gradle: - / Qualche suggerimento?
Ivan Morgillo


2

Se non usare il multidex che rende il processo di compilazione molto lento. Puoi fare quanto segue. Come menzionato da yahska, utilizza una libreria di servizi Google Play specifica. Nella maggior parte dei casi è necessario solo questo.

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

Ecco tutti i pacchetti disponibili che compilano selettivamente le API nel tuo eseguibile

Se ciò non bastasse, puoi usare gradle script. Metti questo codice nel file "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
}

}

Quindi applica questo script nel tuo build.gradle, in questo modo

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

1

Se utilizzi Google Play Services, potresti sapere che aggiunge oltre 20k metodi. Come già accennato, Android Studio ha l'opzione per l'inclusione modulare di servizi specifici, ma gli utenti bloccati con Eclipse devono prendere la modularizzazione nelle proprie mani :(

Fortunatamente esiste uno script di shell che rende il lavoro abbastanza semplice. Basta estrarre nella directory jar di Google Play Services, modificare il file .conf fornito secondo necessità ed eseguire lo script della shell.

Un esempio del suo utilizzo è qui .


1

Se utilizzi Google Play Services, potresti sapere che aggiunge oltre 20.000 metodi. Come già accennato, Android Studio ha l'opzione per l'inclusione modulare di servizi specifici, ma gli utenti bloccati con Eclipse devono prendere in mano la modularizzazione :(

Fortunatamente esiste uno script di shell che rende il lavoro abbastanza semplice. Basta estrarre nella directory jar di Google Play Services, modificare il file .conf fornito secondo necessità ed eseguire lo script della shell.

Un esempio del suo utilizzo è qui.

Proprio come ha detto, sostituisco compile 'com.google.android.gms:play-services:9.0.0'solo con le librerie di cui avevo bisogno e ha funzionato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.