web-dev-qa-db-fra.com

Comment réduire le code - limite de la méthode 65k dans dex

J'ai un assez grand Android app qui repose sur de nombreux projets de bibliothèque. Le compilateur Android a une limitation de 65536 méthodes par fichier .dex et je surpasse ce comp nombre.

Il existe essentiellement deux chemins que vous pouvez choisir (du moins à ma connaissance) lorsque vous atteignez la limite de la méthode.

1) Réduisez votre code

2) Construire plusieurs fichiers dex ( voir cet article )

J'ai examiné les deux et j'ai essayé de découvrir ce qui faisait que le nombre de mes méthodes était si élevé. L’API de Google Drive est l’élément le plus important avec plus de 12 000 dépendants de la goyave. Le nombre total de bibliothèques pour Drive API v2 atteint plus de 23 000!

Ma question est la suivante: que pensez-vous que je devrais faire? Devrais-je supprimer l'intégration de Google Drive en tant que fonctionnalité de mon application? Est-il possible de réduire l'API (oui, j'utilise proguard)? Devrais-je choisir la route des dex multiples (qui a plutôt l'air pénible, en particulier avec des API tierces)?

90
Jared Rummler

Il semble que Google ait finalement mis en place une solution de contournement/correctif pour dépasser la limite de la méthode des fichiers dex de 65 Ko.

À propos de la limite de référence de 65 K

Les fichiers d'application Android (APK) contiennent des fichiers bytecode exécutables sous la forme de fichiers Dalvik Executable (DEX), qui contiennent le code compilé utilisé pour exécuter votre application. La spécification Dalvik Executable limite le nombre total de méthodes pouvant être référencées dans un seul fichier DEX à 65 536, y compris les méthodes d'infrastructure, les méthodes d'infrastructure, les méthodes de bibliothèque et les méthodes de votre propre code. Android). nécessite que vous configuriez votre processus de génération d’application pour générer plus d’un fichier DEX, appelé configuration multidex.

Prise en charge de Multidex avant Android 5.0

Les versions de la plate-forme antérieures à Android 5.0 utilisent le runtime de Dalvik pour exécuter le code d'application. Par défaut, Dalvik limite les applications à un seul fichier bytecode classes.dex par APK. Afin de contourner cette limitation , vous pouvez utiliser le multidex support library , qui fait partie du fichier DEX principal de votre application, puis gère l’accès aux fichiers DEX supplémentaires et au code qu’ils contiennent.

Prise en charge de Multidex pour Android 5.0 et versions ultérieures

Android 5.0 et versions ultérieures utilisent un environnement d'exécution appelé ART qui prend en charge de manière native le chargement de plusieurs fichiers dex à partir de fichiers APK de l'application. ART effectue une pré-compilation au moment de l'installation de l'application, qui analyse les fichiers de classes (.. N) .dex et les compile en un seul fichier .oat à exécuter par le périphérique Android. Pour plus d'informations sur les l'exécution Android 5.0, voir Présentation de ART .

Voir: Création d'applications avec des méthodes supérieures à 65K


Bibliothèque de support Multidex

Cette bibliothèque prend en charge la création d'applications avec plusieurs fichiers Dalvik Executable (DEX). Les applications faisant référence à plus de 65 536 méthodes sont nécessaires pour utiliser les configurations multidex. Pour plus d'informations sur l'utilisation de multidex, reportez-vous à la section Création d'applications avec plus de 65 000 méthodes .

Cette bibliothèque se trouve dans le répertoire/extras/Android/support/multidex/après le téléchargement de Android Bibliothèques d'assistance. La bibliothèque ne contient pas de ressources d'interface utilisateur. Pour l'inclure dans votre projet d'application, suivez les instructions pour Ajout de bibliothèques sans ressources.

L'identifiant de dépendance de script de génération Gradle pour cette bibliothèque est le suivant:

com.Android.support:multidex:1.0.+ Cette notation de dépendance spécifie la version 1.0.0 ou ultérieure.


Vous devez néanmoins éviter de dépasser la limite de la méthode 65K en utilisant activement proguard et en examinant vos dépendances.

67
Jared Rummler

vous pouvez utiliser la bibliothèque de support multidex pour cela, Pour activer multidex

1) incluez-le dans les dépendances:

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

2) Activez-le dans votre application:

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

) si vous avez une classe application pour votre application, remplacez la méthode attachBaseContext comme ceci:

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

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

4) si vous ne le faites pas avez une classe application pour votre application, inscrivez-vous Android.support.multidex.MultiDexApplication = en tant que votre application dans votre fichier manifeste. comme ça:

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

et ça devrait marcher!

52
Prakhar

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

"À partir de la version 6.5 des services Google Play, vous pourrez choisir parmi plusieurs API individuelles et vous pourrez voir"

...

"Cela inclura de manière transitoire les bibliothèques" de base ", qui sont utilisées dans toutes les API."

C'est une bonne nouvelle, pour un jeu simple, par exemple, vous n'avez probablement besoin que de base, games et peut-être drive.

"La liste complète des noms d'API est présentée ci-dessous. Plus de détails sont disponibles sur le Android Site du développeur .:

  • com.google.Android.gms: base de jeux-services: 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: emplacement des services de lecture: 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: jeux-services-jeux: 6.5.87
  • com.google.Android.gms: portefeuille-services-jeux: 6.5.87
  • com.google.Android.gms: identité de services de jeu: 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

Dans les versions des services Google Play antérieures à la version 6.5, vous deviez compiler l'intégralité du package d'API dans votre application. Dans certains cas, cela rendait plus difficile le maintien du nombre de méthodes dans votre application (y compris les API de structure, les méthodes de bibliothèque et votre propre code) sous la limite de 65 536.

À partir de la version 6.5, vous pouvez compiler sélectivement les API de service Google Play dans votre application. Par exemple, pour inclure uniquement les API Google Fit et Android Wear, remplacez la ligne suivante dans votre fichier build.gradle:

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

avec ces lignes:

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

pour plus de référence, vous pouvez cliquer here

9
akshay

Vous pouvez utiliser Jar Jar Links pour réduire d’énormes bibliothèques externes telles que les services Google Play (méthodes 16K!)

Dans votre cas, vous devrez simplement extraire tout le fichier jar des services Google Play, à l'exception des sous-packages commoninternal et drive.

7
pixel

Utilisez proguard pour alléger votre apk, car les méthodes inutilisées ne figureront pas dans votre version finale. Vérifiez bien que votre fichier de configuration proguard a bien utilisé proguard avec goyave (toutes mes excuses, si vous en avez déjà un, cela n’était pas connu au moment de la rédaction):

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

En outre, si vous utilisez ActionbarSherlock, le passage à la bibliothèque de support v7 appcompat réduira également considérablement le nombre de méthodes (sur la base de votre expérience personnelle). Les instructions sont situées:

7
petey

Pour les utilisateurs d'Eclipse qui n'utilisent pas Gradle, il existe des outils qui décomposent le fichier jar des services Google Play et le reconstruisent avec uniquement les parties souhaitées.

J'utilise strip_play_services.sh de dextorer .

Il peut être difficile de savoir exactement quels services inclure, car il existe des dépendances internes, mais vous pouvez commencer petit et ajouter de la configuration s'il s'avère que des éléments manquants sont nécessaires.

4
Brian White

Je pense qu’à long terme, casser votre application en plusieurs dex serait la meilleure solution.

3
prmottajr

Sinon, utiliser multidex, ce qui rend le processus de construction très lent. Vous pouvez faire ce qui suit. Comme yahska mentionné, utilisez une bibliothèque de services Google Play spécifique. Dans la plupart des cas, seul cela est nécessaire.

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

Voici tous les packages disponibles compilation sélective d’API dans votre exécutable

Si cela ne suffit pas, vous pouvez utiliser le script Gradle. Mettez ce code dans le fichier '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
}

}

Puis appliquez ce script dans votre build.gradle, comme ceci

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

Le support multi-dex est va être la solution officielle pour ce problème. Voir ma réponse ici pour les détails.

2
Alex Lipov

Si vous utilisez les services Google Play, sachez qu'il ajoute des méthodes de 20 000 $ ou plus. Comme déjà mentionné, Android Studio offre l'option d'inclusion modulaire de services spécifiques, mais les utilisateurs coincés avec Eclipse doivent prendre en charge la modularisation :(

Heureusement, il existe un script Shell qui rend le travail assez facile. Extrayez simplement dans le répertoire jar de Google Play Services, éditez le fichier .conf fourni selon vos besoins et exécutez le script Shell.

Un exemple de son utilisation est ici.

Comme il l'a dit, je remplace compile 'com.google.Android.gms:play-services:9.0.0' juste avec les bibliothèques dont j'avais besoin et cela a fonctionné.

1
Tamir Gilany

Si vous utilisez les services Google Play, sachez qu'il ajoute des méthodes de 20 000 $ ou plus. Comme déjà mentionné, Android Studio offre l'option d'inclusion modulaire de services spécifiques, mais les utilisateurs coincés avec Eclipse doivent prendre en charge la modularisation :(

Heureusement il y a un script Shell qui rend le travail assez facile. Extrayez simplement dans le répertoire jar de Google Play Services, éditez le fichier .conf fourni selon vos besoins et exécutez le script Shell.

Un exemple d'utilisation est ici .

1
Tom