Cómo reducir el código: límite del método 65k en dex

Tengo una aplicación de Android bastante grande que depende de muchos proyectos de biblioteca. El comstackdor de Android tiene una limitación de 65536 métodos por archivo .dex y estoy superando ese número.

Básicamente, hay dos caminos que puedes elegir (al menos que yo sepa) cuando alcanzas el límite del método.

1) Reducir su código

2) Crea múltiples archivos dex ( mira esta publicación en el blog )

Miré a ambos y traté de averiguar qué estaba causando que mi recuento de métodos fuera tan alto. La API de Google Drive toma la mayor parte con la dependencia de Guava en más de 12,000. ¡Las libs totales para Drive API v2 alcanzan más de 23,000!

Mi pregunta es, ¿qué crees que debería hacer? ¿Debo eliminar la integración de Google Drive como una característica de mi aplicación? ¿Hay alguna forma de reducir la API (sí, uso Proguard)? ¿Debo ir a la ruta de dex múltiple (que parece bastante dolorosa, especialmente cuando se trata de API de terceros)?

Parece que Google finalmente ha implementado una solución / solución para superar el límite del método 65K de los archivos dex.

Acerca del Límite de referencia de 65K

Los archivos de la aplicación Android (APK) contienen archivos ejecutables bytecode en forma de archivos ejecutables Dalvik (DEX), que contienen el código comstackdo utilizado para ejecutar su aplicación. La especificación del ejecutable de Dalvik limita el número total de métodos a los que se puede hacer referencia en un único archivo DEX a 65.536, incluidos los métodos de Framework de Android, los métodos de biblioteca y los métodos en su propio código. Superar este límite requiere que configure el proceso de comstackción de su aplicación para generar más de un archivo DEX, conocido como configuración multidex.

Soporte Multidex antes de Android 5.0

Las versiones de la plataforma anteriores a Android 5.0 usan el tiempo de ejecución de Dalvik para ejecutar el código de la aplicación. De forma predeterminada, Dalvik limita las aplicaciones a un único archivo de código de bytes de classes.dex por APK. Para evitar esta limitación, puede usar la biblioteca de soporte multidex , que se convierte en parte del archivo DEX primario de su aplicación y luego administra el acceso a los archivos DEX adicionales y el código que contienen.

Soporte Multidex para Android 5.0 y superior

Android 5.0 y versiones posteriores utilizan un tiempo de ejecución llamado ART que admite de forma nativa la carga de múltiples archivos dex desde los archivos APK de la aplicación. ART realiza la precomstackción en el momento de la instalación de la aplicación, que busca archivos de clases (.. N) .dex y los comstack en un solo archivo .oat para su ejecución por el dispositivo Android. Para obtener más información sobre el tiempo de ejecución de Android 5.0, consulte Introducción a ART .

Ver: Creación de aplicaciones con más de 65K métodos


Biblioteca de soporte de Multidex

Esta biblioteca proporciona soporte para crear aplicaciones con múltiples archivos ejecutables Dalvik (DEX). Las aplicaciones que hacen referencia a más de 65536 métodos son necesarias para usar configuraciones multidexuales. Para obtener más información sobre el uso de multidex, consulte Creación de aplicaciones con más de 65K métodos .

Esta biblioteca se encuentra en el directorio / extras / android / support / multidex / después de descargar las bibliotecas de soporte de Android. La biblioteca no contiene recursos de interfaz de usuario. Para incluirlo en su proyecto de aplicación, siga las instrucciones para Agregar bibliotecas sin recursos.

El identificador de dependencia del script de comstackción Gradle para esta biblioteca es el siguiente:

com.android.support:multidex:1.0.+ Esta notación de dependencia especifica la versión de lanzamiento 1.0.0 o superior.


Aún así, debe evitar llegar al límite del método de 65 K al usar proguard y revisar sus dependencias.

puede usar la biblioteca de soporte multidex para eso, para habilitar multidex

1) incluirlo en dependencias:

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

2) Habilítalo en tu aplicación:

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

3) si tienes una clase de aplicación para tu aplicación, entonces reemplaza el método attachBaseContext así:

 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 no tiene una clase de aplicación para su aplicación, entonces registre android.support.multidex.MultiDexApplication como su aplicación en su archivo de manifiesto. Me gusta esto:

  ...  

y debería funcionar bien!

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

“Comenzando con la versión 6.5 de los servicios de Google Play, podrá elegir entre varias API individuales, y puede ver”

“Esto incluirá transitoriamente las bibliotecas ‘base’, que se usan en todas las API”.

Estas son buenas noticias, para un juego simple, por ejemplo, probablemente solo necesites la base , los games y quizás drive .

“La lista completa de nombres de API se encuentra a continuación. Se pueden encontrar más detalles en el sitio para desarrolladores de 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-outlook: 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

En las versiones de los servicios de Google Play anteriores a la 6.5, tenía que comstackr todo el paquete de API en su aplicación. En algunos casos, al hacerlo, era más difícil mantener el número de métodos en su aplicación (incluidas las API de framework, los métodos de biblioteca y su propio código) por debajo del límite de 65.536.

A partir de la versión 6.5, puede comstackr selectivamente las API de servicio de Google Play en su aplicación. Por ejemplo, para incluir solo las API de Google Fit y Android Wear, reemplace la siguiente línea en su archivo build.gradle:

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

con estas líneas:

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

para más referencia, puede hacer clic aquí

Use Proguard para aligerar su apk ya que los métodos que no se utilizan no estarán en su comstackción final. Verifique que tenga los siguientes datos en su archivo de configuración de proguard para usar proguard con guava (mis disculpas si ya tiene esto, no se sabía al momento de escribirlo):

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

Además, si está utilizando ActionbarSherlock, cambiar a la biblioteca de soporte de v7 appcompat también reducirá mucho su conteo de métodos (en base a su experiencia personal). Las instrucciones están ubicadas:

Puede utilizar Jar Jar Links para reducir grandes bibliotecas externas como Google Play Services (¡métodos de 16K!)

En su caso, simplemente extraerá todo desde el jar de Servicios de Google Play, excepto los common internal y de drive common .

Para los usuarios de Eclipse que no usan Gradle, existen herramientas que descomponen el contenedor de servicios de Google Play y lo reconstruyen solo con las piezas que desea.

Yo uso strip_play_services.sh por dextorer .

Puede ser difícil saber exactamente qué servicios incluir porque existen algunas dependencias internas, pero puede comenzar de a poco y agregar a la configuración si resulta que faltan elementos necesarios.

Creo que, a la larga, romper tu aplicación en múltiples dex sería la mejor manera.

El soporte Multi-dex será la solución oficial para este problema. Vea mi respuesta aquí para más detalles.

Si no se usa multidex, el proceso de comstackción es muy lento. Puedes hacer lo siguiente. Como yahska mencionó, usa una biblioteca de servicios de google play específica. Para la mayoría de los casos solo esto es necesario.

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

Aquí están todos los paquetes disponibles Comstackción selectiva de API en su ejecutable

Si esto no es suficiente, puede usar el script de Gradle. Pon este código en el archivo ‘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/outlook/**", "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 } 

}

A continuación, aplique esta secuencia de comandos en su build.gradle, así

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

Si usa Google Play Services, puede saber que agrega 20k + métodos. Como ya se mencionó, Android Studio tiene la opción de incluir servicios específicos en forma modular, pero los usuarios atascados con Eclipse tienen que tomar la modularización en sus propias manos 🙁

Afortunadamente hay un script de shell que hace el trabajo bastante fácil. Simplemente extraiga al directorio jar de los servicios de google play, edite el archivo .conf proporcionado según sea necesario y ejecute el script de shell.

Un ejemplo de su uso está aquí .

Si usa Google Play Services, puede saber que agrega 20k + métodos. Como ya se mencionó, Android Studio tiene la opción de incluir servicios específicos en forma modular, pero los usuarios atascados con Eclipse tienen que tomar la modularización en sus propias manos 🙁

Afortunadamente hay un script de shell que hace el trabajo bastante fácil. Simplemente extraiga al directorio jar de los servicios de google play, edite el archivo .conf proporcionado según sea necesario y ejecute el script de shell.

Un ejemplo de su uso está aquí.

Como dijo, reemplazo compile 'com.google.android.gms:play-services:9.0.0' solo con las bibliotecas que necesitaba y funcionó.