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


Tengo una aplicación Android bastante grande que se basa en muchos proyectos de bibliotecas. El compilador 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 llegas al límite del método.

1) Reduzca su código

2) Construir varios archivos dex (ver esta entrada de blog)

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

Mi pregunta supongo que 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 manera de reducir la API (sí, uso proguard)? ¿Debo ir a la ruta dex múltiple (que parece bastante doloroso, especialmente al tratar con API de terceros)?

Author: Jared Rummler, 2013-03-18

12 answers

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

Sobre el Límite de Referencia de 65K

Los archivos de la aplicación Android (APK) contienen archivos de bytecode ejecutables en forma de Dalvik Ejecutable (DEX) archivos, que contienen el código compilado utilizado para ejecutar la aplicación. El La especificación del ejecutable Dalvik limita el número total de métodos que se puede hacer referencia dentro de un solo archivo DEX a 65,536, incluir Métodos del marco de trabajo de Android, métodos de biblioteca y métodos propios codificar. Superar este límite requiere que configure su aplicación proceso de compilación para generar más de un archivo DEX, conocido como multidex configuración.

Compatibilidad con Multidex anterior a Android 5.0

Las versiones de la plataforma anteriores a Android 5.0 utilizan el tiempo de ejecución de Dalvik para ejecutar código de aplicación. De forma predeterminada, Dalvik limita las aplicaciones a una sola clase.archivo dex bytecode por APK. En para conseguir alrededor de esto limitación, puede usar la biblioteca de soporte multidex , que se convierte en parte del archivo DEX principal de su aplicación y luego administra el acceso a los archivos DEX adicionales y el código que contienen.

Compatibilidad con Multidex para Android 5.0 y versiones posteriores

Android 5.0 y superior utiliza un tiempo de ejecución llamado ART que de forma nativa soporta la carga de varios archivos dex desde archivos APK de aplicaciones. ARTE realiza la pre-compilación en la instalación de la aplicación tiempo que busca clase(..Y).dex archivos y los compila en un solo .archivo de avena para ejecución por el dispositivo Android. Para más información sobre Android 5.0 tiempo de ejecución, ver Introducción de ART.

Ver: Creación de Aplicaciones con Métodos de Más de 65K


Biblioteca de soporte Multidex

Esta biblioteca proporciona soporte para la construcción aplicaciones con múltiples archivos ejecutables Dalvik (DEX). Aplicaciones que hacen referencia más de 65536 métodos son necesarios para utilizar configuraciones multidex. Para obtener más información sobre el uso de multidex, consulte Creación de aplicaciones con Over 65K Métodos .

Esta biblioteca se encuentra en / extras / android / support / multidex/ directorio después de descargar las bibliotecas de soporte de Android. El 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.

La compilación de Gradle el identificador de dependencia del script para esta biblioteca es como sigue:

Com.androide.soporte: multidex: 1.0.+ Esta notación de dependencias especifica la versión 1.0.0 o superior.


Aún debe evitar alcanzar el límite del método 65K usando activamente proguard y revisando sus dependencias.

 65
Author: Jared Rummler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-01 06:53:18

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 tiene una clase application para su aplicación, entonces Anule el método attachBaseContext de la siguiente manera:

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 application para su solicitud, entonces regístrese android.apoyo.multidex.MultiDexApplication como su aplicación en su archivo de manifiesto. así:

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

Y debería funcionar bien!

 51
Author: Prakhar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-12 18:02:06

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

" A partir de la versión 6.5, de los servicios de Google Play, usted será capaz de elija entre una serie de API individuales, y se puede ver"

...

"esto incluirá transitivamente las bibliotecas 'base', que se utilizan en todas las API."

Esta es una buena noticia, para un juego simple, por ejemplo, probablemente solo necesita el base, games y tal vez drive.

"La lista completa de nombres de API se encuentra a continuación. Más detalles se pueden encontrar en el Desarrollador de Android

 31
Author: Csaba Toth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-19 05:39:23

En las versiones de Google Play Services anteriores a la 6.5, tenías que compilar todo el paquete de API en tu app. En algunos casos, hacerlo dificultó mantener el número de métodos en tu app (incluidas las API de framework, los métodos de biblioteca y tu propio código) por debajo del límite de 65.536.

Desde la versión 6.5, puede compilar selectivamente las API de Google Play Service en su aplicación. Por ejemplo, para incluir solo las API de Google Fit y Android Wear, reemplace la línea siguiente en tu constitución.archivo 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 en aquí

 8
Author: akshay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-27 08:31:38

Usa proguard para aligerar tu apk, ya que los métodos que no se utilicen no estarán en tu compilación final. Compruebe que tiene lo siguiente en su archivo de configuración de proguard para usar proguard con guayaba (mis disculpas si ya tiene esto, no se sabía en el momento de escribir):

# 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 v7 appcompat también reducirá mucho el número de métodos (según la experiencia personal). Las instrucciones se encuentran :

 7
Author: petey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-20 17:21:27

Puedes usar Jar Jar Links para reducir las enormes bibliotecas externas como los servicios de Google Play (métodos de 16K!)

En su caso, simplemente arrancará todo del tarro de Servicios de Google Play, excepto common internal y drive subpaquetes.

 7
Author: pixel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-19 11:52:07

Para los usuarios de Eclipse que no usan Gradle, hay herramientas que descompondrán el jar de Google Play Services y lo reconstruirán solo con las partes que desee.

Utilizo strip_play_services.sh por dextorer .

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

 4
Author: Brian White,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-24 13:20:14

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

 3
Author: prmottajr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-09 22:06:21

El soporte Multi-dex es va a ser la solución oficial para este problema. Ver mi respuesta aquí para los detalles.

 2
Author: Alex Lipov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:38

Si no se usa multidex que hace que el proceso de compilación sea muy lento. Puedes hacer lo siguiente. Como yahska mencionado use la 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 Compilando selectivamente las API en su ejecutable

Si esto no es suficiente, puedes usar el script de gradle. Ponga 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/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
}

}

Luego aplique este script en su construir.gradle, así

apply plugin: 'com.android.application'
apply from: 'strip_play_services.gradle'
 2
Author: Lemberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:33

Si utiliza los servicios de Google Play, es posible que sepa que agrega métodos de 20k+. Como ya se mencionó, Android Studio tiene la opción de inclusión modular de servicios específicos, pero los usuarios atrapados 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 Google Play Services, edite el suministrado .conf archivo según sea necesario y ejecutar el script de shell.

Un ejemplo de su el uso es aquí.

 1
Author: Tom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-28 03:19:44

Si utiliza los servicios de Google Play, es posible que sepa que agrega métodos de 20k+. Como ya se mencionó, Android Studio tiene la opción de inclusión modular de servicios específicos, pero los usuarios atrapados 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 Google Play Services, edite el suministrado .conf archivo según sea necesario y ejecutar el script de shell.

Un ejemplo de su uso está aquí.

Tal como él dijo, remplazo compile 'com.google.android.gms:play-services:9.0.0' solo con las bibliotecas que necesitaba y funcionó.

 1
Author: Tamir Gilany,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-22 13:52:39