Carga de Clase Personalizada en Dalvik con Gradle (Android New Build System)

Según la introducción de Custom Class Loading en Dalvik por Fred Chung en el Blog de desarrolladores de Android:

La máquina virtual Dalvik proporciona instalaciones para que los desarrolladores realicen cargas de clase personalizadas. En lugar de cargar archivos ejecutables ("dex") de Dalvik desde la ubicación predeterminada, una aplicación puede cargarlos desde ubicaciones alternativas, como almacenamiento interno o en la red.

Sin embargo, no muchos desarrolladores tienen la necesidad de hacer carga de clase personalizada. Pero los que hacen y siguen las instrucciones en ese blog, podrían tener algunos problemas imitando el mismo comportamiento con Gradle, el nuevo sistema de compilación para Android introducido en Google I / O 2013.

¿Cómo exactamente uno puede adaptar el nuevo sistema de construcción para realizar los mismos pasos intermediarios que en el sistema de construcción antiguo (basado en Ant)?

Mi equipo y yo recientemente llegamos a las referencias de 64K método en nuestra aplicación, que es el número máximo de apoyo en un archivo dex. Para evitar esta limitación, necesitamos particionar parte del programa en múltiples archivos dex secundarios y cargarlos en tiempo de ejecución.

Seguimos la entrada del blog mencionada en la pregunta para el antiguo, basado en Ant, construir el sistema y todo estaba funcionando bien. Pero recientemente sentimos la necesidad de pasar al nuevo sistema de construcción, basado en Gradle.

Esta respuesta no pretende reemplazar la publicación completa del blog con un ejemplo completo. En su lugar, simplemente explicará cómo usar Gradle para ajustar el proceso de construcción y lograr lo mismo. Tenga en cuenta que esta es probablemente sólo una forma de hacerlo y cómo lo estamos haciendo actualmente en nuestro equipo. No significa necesariamente que es la única manera.

Nuestro proyecto está estructurado un poco diferente y este ejemplo funciona como un proyecto individual de Java que compilará todo el código fuente en archivos .class, los reunirá en un solo archivo .dex y para terminar, empaquetará ese único archivo .dex en un archivo .jar archivo.

Empecemos …

En la raíz build.gradle tenemos el siguiente fragmento de código para definir algunos valores por defecto:

ext.androidSdkDir = System.env.ANDROID_HOME if(androidSdkDir == null) { Properties localProps = new Properties() localProps.load(new FileInputStream(file('local.properties'))) ext.androidSdkDir = localProps['sdk.dir'] } ext.buildToolsVersion = '18.0.1' ext.compileSdkVersion = 18 

Necesitamos el código anterior porque aunque el ejemplo es un proyecto individual de Java, todavía necesitamos usar componentes del SDK de Android. Y también necesitaremos algunas de las otras propiedades más adelante … Así que, en el build.gradle del proyecto principal, tenemos esta dependencia:

 dependencies { compile files("${androidSdkDir}/platforms/android-${compileSdkVersion}/android.jar") } 

También estamos simplificando los conjuntos de fuentes de este proyecto, que tal vez no sea necesario para su proyecto:

 sourceSets { main { java.srcDirs = ['src'] } } 

A continuación, cambiamos la configuración predeterminada de la tarea build-in jar para incluir simplemente el archivo classes.dex en lugar de todos los archivos .class:

 configure(jar) { include 'classes.dex' } 

Ahora necesitamos tener una nueva tarea que realmente ensamblará todos los archivos .class en un único archivo .dex. En nuestro caso, también necesitamos incluir la biblioteca JAR de Protobuf en el archivo .dex. Así que estoy incluyendo eso en el ejemplo aquí:

 task dexClasses << { String protobufJarPath = '' String cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : '' configurations.compile.files.find { if(it.name.startsWith('protobuf-java')) { protobufJarPath = it.path } } exec { commandLine "${androidSdkDir}/build-tools/${buildToolsVersion}/dx${cmdExt}", '--dex', "--output=${buildDir}/classes/main/classes.dex", "${buildDir}/classes/main", "${protobufJarPath}" } } 

Además, asegúrese de tener la siguiente importación en algún lugar (normalmente en la parte superior, por supuesto) en su archivo build.gradle :

 import org.apache.tools.ant.taskdefs.condition.Os 

Ahora debemos hacer que la tarea jar dependa de nuestra tarea dexClasses , para asegurarnos de que nuestra tarea se ejecuta antes de que el archivo .jar final se ensambla. Lo hacemos con una simple línea de código:

 jar.dependsOn(dexClasses) 

Y ya terminamos … Simplemente invoca Gradle con la tarea habitual de assemble y tu archivo .jar final, ${buildDir}/libs/${archivesBaseName}.jar contendrá un solo archivo classes.dex (además del MANIFEST.MF archivo). Simplemente copie eso en su carpeta de activos de la aplicación (siempre puede automatizarlo con Gradle como lo hemos hecho pero eso está fuera del alcance de esta pregunta) y siga el resto de la entrada del blog.

Si usted tiene alguna pregunta, sólo gritar en los comentarios. Voy a tratar de ayudar a lo mejor de mis habilidades.

El complemento Android Studio Gradle ahora proporciona compatibilidad multidex nativa, que resuelve eficazmente el límite del método Android 65k sin tener que cargar manualmente las clases de un archivo jar, y por lo tanto hace que el blog de Fred Chung sea obsoleto para ese propósito. Sin embargo, cargar clases personalizadas de un archivo jar en tiempo de ejecución en Android sigue siendo útil con el propósito de extensibilidad (por ejemplo, crear un marco de complemento para la aplicación ), por lo que abordaré el escenario de uso siguiente:

He creado un puerto de la aplicación de ejemplo original en el blog de Fred Chung a Android Studio en mi página github aquí usando el complemento de la biblioteca de Android en lugar del complemento de Java. En lugar de intentar modificar el proceso dex existente para dividir en dos módulos como en el blog, he puesto el código que queremos entrar en el archivo jar en su propio módulo, y agregó una tarea personalizada assembleExternalJar que dexes lo necesario Class después de que la tarea de assemble principal haya finalizado.

Aquí es parte relevante del archivo build.gradle para la biblioteca. Si el módulo de biblioteca tiene dependencias que no están en el proyecto principal, es probable que necesite modificar este script para agregarlas.

 apply plugin: 'com.android.library' // ... see github project for the full build.gradle file // Define some tasks which are used in the build process task copyClasses(type: Copy) { // Copy the assembled *.class files for only the current namespace into a new directory // get directory for current namespace (PLUGIN_NAMESPACE = 'com.example.toastlib') def namespacePath = PLUGIN_NAMESPACE.replaceAll("\\.","/") // set source and destination directories from "build/intermediates/classes/release/${namespacePath}/" into "build/intermediates/dex/${namespacePath}/" // exclude classes which don't have a corresponding .java entry in the source directory def remExt = { name -> name.lastIndexOf('.').with {it != -1 ? name[0..<it] : name} } eachFile {details -> def thisFile = new File("${projectDir}/src/main/java/${namespacePath}/", remExt(details.name)+".java") if (!(thisFile.exists())) { details.exclude() } } } task assembleExternalJar << { // Get the location of the Android SDK ext.androidSdkDir = System.env.ANDROID_HOME if(androidSdkDir == null) { Properties localProps = new Properties() localProps.load(new FileInputStream(file('local.properties'))) ext.androidSdkDir = localProps['sdk.dir'] } // Make sure no existing jar file exists as this will cause dx to fail new File("${buildDir}/intermediates/dex/${PLUGIN_NAMESPACE}.jar").delete(); // Use command line dx utility to convert *.class files into classes.dex inside jar archive String cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : '' exec { commandLine "${androidSdkDir}/build-tools/${BUILD_TOOLS_VERSION}/dx${cmdExt}", '--dex', "--output=${buildDir}/intermediates/dex/${PLUGIN_NAMESPACE}.jar", "${buildDir}/intermediates/dex/" } copyJarToOutputs.execute() } task copyJarToOutputs(type: Copy) { // Copy the built jar archive to the outputs folder from 'build/intermediates/dex/' into 'build/outputs/' include '*.jar' } // Set the dependencies of the build tasks so that assembleExternalJar does a complete build copyClasses.dependsOn(assemble) assembleExternalJar.dependsOn(copyClasses) 

Para obtener información más detallada, consulte el código fuente completo de la aplicación de ejemplo en mi github.

Vea mi respuesta aquí . Los puntos clave son:

  • Utilice la propiedad additionalParameters en las tareas dexCamelCase creadas dinámicamente para pasar --multi-dex a dx y crear múltiples archivos dex.
  • Utilice el cargador de clases multidex para utilizar los múltiples archivos dex.
  • Error JSON.simple: java.util.zip.ZipException: entrada duplicada: org / hamcrest / BaseDescription.class
  • Proyecto de gradle de Android proyecto de compilación en un árbol de directorio diferente
  • Gradle kotlin Método no soportado Dependencies.getAtoms ()
  • ¿Cómo puedo crear un módulo de prueba de Android en IntelliJ 13 para un proyecto Android de Gradle?
  • Anotación Los procesadores generaron recursos no empaquetados a APK
  • Importar proyecto de biblioteca varias veces en Android Studio 1.3.1 hace que la biblioteca desaparezca
  • ¿Cómo evitar que el estudio de Android use el camino absoluto?
  • Gradle no pudo resolver la biblioteca en Android Studio
  • Anula la versión de Java cuando construye un proyecto Cordova con gradle
  • Construir apk con error gradle
  • Utilizar Gradle para dividir bibliotecas externas en archivos separados dex para resolver el límite de métodos de Android Dalvik 64k
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.