Filtrado de recursos del monolito de Play Services para hacer su APK más pequeño

Se ha escrito mucho sobre la naturaleza monolítica de los Servicios de Google Play y por qué debería dividirse en más bibliotecas. Por ahora la solución para mantener su APK pequeño es usar proguard para quitar las referencias no utilizadas. Esto funciona bastante bien para classes.dex, pero no para los recursos incluidos.

Consigo alrededor de 1 MB de recursos adicionales no utilizados y con una aplicación Android Wear que se acopla. Así que mi APK es 2 MB más grande de lo necesario.

Me pregunto si hay alguna manera directa en Gradle de excluir algunos recursos procedentes de las dependencias AAR del APK resultante.

Parece que las opciones de AAPT en el complemento de Gradle Android sólo permiten filtrar activos .

Estaba pensando en enganchar en algún script aapt personalizado que llamaría a eliminar para una lista de recursos utilizando aapt antes de firmar el APK para la liberación.

¿Alguien más tiene una solución más simple?

Después de algunas investigaciones encontré la siguiente solución subóptima. Tuve que enumerar todos los recursos no necesarios manualmente (por suerte los patrones son aplicables) y asegúrese también de eliminar todos los archivos donde se hace referencia. A continuación se muestra un ejemplo que hace que mi APK aplicación Wear de 1,5 MB en 300kb y el APK está funcionando normalmente sin problemas.

Tuve que crear mi propia tarea stripResources y conectarlo entre estándar de Android plug-in de tareas: mergeReleaseResources y mergeReleaseResources .

 task stripResources << { println "Custom resource stripping in: $buildDir" delete fileTree(dir: "$buildDir", include: "**/layout/confirmation_activity_layout.xml") delete fileTree(dir: "$buildDir", include: "**/layout/watch_card_content.xml") delete fileTree(dir: "$buildDir", include: "**/common_signin*.png") delete fileTree(dir: "$buildDir", include: "**/drawable/common_signin*.xml") delete fileTree(dir: "$buildDir", include: "**/generic_confirmation*.png") delete fileTree(dir: "$buildDir", include: "**/drawable/confirmation_*.xml") delete fileTree(dir: "$buildDir", include: "**/drawable/card_background.xml") delete fileTree(dir: "$buildDir", include: "**/card_frame*.png") delete fileTree(dir: "$buildDir", include: "**/go_to*.png") delete fileTree(dir: "$buildDir", include: "**/drawable/go_to_*.xml") delete fileTree(dir: "$buildDir", include: "**/ic_plusone*.png") delete fileTree(dir: "$buildDir", include: "**/powered_by_google*.png") // if you only have English you can teh following to filter out some GPS texts wich also take few hundreds of kb // delete fileTree(dir: "$buildDir", include: "**/values-*/values.xml") } tasks.whenTaskAdded { task -> if (task.name == 'processReleaseManifest') { task.dependsOn stripResources } } 

Puedes hacer una tarea similar para Android APK regular.

A partir de la versión 0.14 del complemento Android Gradle, esto se puede lograr automáticamente como se menciona en este post :

 android { buildTypes { release { minifyEnabled true shrinkResources true } } } 

shrinkResources es un indicador que indica al compilador que omita cualquier recurso que no esté referenciado. minifyEnabled es el nuevo nombre de runProguard , que debe estar habilitado para que shrinkResources funcione.

Gracias a la perspicaz respuesta de Petr Nalevka he llegado con la siguiente solución (aka fea hack).

Este código elimina todos los elementos plegables de PNG bajo hdpi, xhdpi, xxhdpi y así sucesivamente, manteniendo sólo los drawables bajo mdpi (los que tienen el tamaño de archivo más pequeño). Dibujos sólo se eliminan si tienen un hermano en mdpi, es decir, si se puede eliminar con seguridad. Los Servicios de Google Play deben seguir funcionando plenamente.

Una tarea se registra para cada variante de aplicación, por lo que este código funciona con múltiples sabores de producto.

 applicationVariants.all { variant -> def variantCamelName = variant.name.substring(0, 1).toUpperCase() + variant.name.substring(1) def stripResourcesTask = task("strip${variantCamelName}Resources") { doFirst { def resDir = "$buildDir/intermediates/res/merged/$variant.dirName" def gpsDir = "$buildDir/intermediates/exploded-aar/com.google.android.gms" fileTree(dir: "$gpsDir", include: "**/drawable-*/*.png").each { resFile -> if (!resFile.parentFile.name.endsWith("-mdpi")) { def resName = resFile.name def resDirName = resFile.parentFile.name def mdpiFile = "$resFile.parentFile.parentFile/drawable-mdpi/$resName" if (file(mdpiFile).file) { def files = fileTree(dir: "$resDir", include: "**/${resDirName}/${resName}") // files.each { f -> println "Deleting $f..." } delete files } } } } } tasks["merge${variantCamelName}Resources"].finalizedBy stripResourcesTask } 

Probado con Android Studio 1.3.0, Gradle plugin versión 1.3.0, herramientas de compilación versión 22.0.1 y GPS versión 7.5.0.

Soy un poco de un noob Groovy, por lo que esta no es probablemente la solución más elegante.

  • Depuración de archivos de compilación de Gradle en Intellij / Android Studio
  • Usa android-maps-utils en Android Studio
  • Gradle configuration.apt definición
  • Cómo controlar Android Emulator de Gradle
  • No se pueden cambiar las dependencias de la configuración (después de activar la ejecución instantánea)
  • Llamar a Gradle buildConfig varias veces
  • Gradle android plugin variant dependencies: Método DSL no encontrado
  • Gradle invocation "completar con éxito con 1 error" en Ubuntu 13.10
  • ¿Qué errores se detectan en "build" en Android Studio - rol de Gradle
  • Documentación Android DSL para gradle
  • Quitar la biblioteca externa de clases obfuscated.jar (en Android Studio / Gradle)
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.