
/ PxHere / PD
Optimizar el peso del APK es una tarea no trivial, pero muy relevante en los días de la aplicación instantánea. Habilitar Proguard le ahorrará código innecesario si sus dependencias pueden determinarse en la etapa de compilación, pero hay varios otros tipos de archivos en el APK que pueden excluirse del ensamblaje.
Debajo del gato sobre cómo hacer dependencias, definidas en la etapa de compilación, qué archivos se pueden excluir del ensamblaje y cómo hacerlo, así como, analizaremos cómo excluir componentes no utilizados del ensamblaje si tiene varias aplicaciones con una base de código común.
Antes de leer
- Antes de aplicar los consejos del artículo, optimice el APK para la guía de Google . Este artículo es para aquellos que no tienen suficientes optimizaciones estándar.
- Por "proguard" me refiero a un compilador optimizador con minificación.
- Por componente, me refiero a una determinada característica del producto desde el punto de vista comercial. En nuestro caso, esto es solo una colección de archivos en un paquete determinado. Tenemos un módulo gradle para toda la aplicación.
El peso de nuestro APK optimizado para Google fue de 4.4
.
Archivos extra
Comencemos con uno simple. Si no utiliza kotlin-reflect , puede excluir la metainformación sobre las clases de kotlin del ensamblaje. Puede hacer esto de la siguiente manera:
En build.gradle (Module: app)
android { packagingOptions { exclude("META-INF/*.kotlin_module") exclude("**.kotlin_builtins") exclude("**.kotlin_metadata") } }
La reflexión de Java no necesita *.kotlin_module
, *.kotlin_builtins
y *.kotlin_metadata
. Determinar qué reflejo está utilizando es muy simple. Si escribe obj::class.<method>
, entonces usa la reflexión de kotlin, si obj::class.java.<method>
, luego java reflection.
El resultado de la optimización para nosotros: -602.1 kb
Dependencias
Las bibliotecas a veces agregan dependencias para casos que nunca suceden en su aplicación. Por ejemplo, ktor-client extrae kotlin-reflect junto con él (¡0.5 mb!).
minifyEnabled = true
con tales casos de la siguiente manera: recopilé el APK con minifyEnabled = true
, lo lancé al analizador de Android Studio, descargué mapping.txt
y busqué paquetes que, en teoría, no deberían estar presentes en el ensamblaje. Por ejemplo, kotlin.reflect
. Después de ejecutar la ./gradlew app:dependencies
en la carpeta del proyecto para buscar dependencias (no olvide aumentar la longitud del historial en el terminal. ¡El árbol de dependencias puede ser grande!). Desde este árbol es fácil entender lo que se refiere a dependencias innecesarias y excluirlas. En build.gradle
su módulo:
dependencies { implementation("io.ktor:ktor-client-core:$ktorVersion") { exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect") } implementation("io.ktor:ktor-client-okhttp:$ktorVersion") { exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect") } }
Este código elimina la dependencia de la biblioteca cliente -ktor en kotlin-reflect . Si desea excluir algo más, sustituya sus valores.
!!! ¡Usa este consejo con mucho cuidado! Antes de eliminar dependencias, asegúrese de no necesitarlas. Si no lo hace, la aplicación puede comenzar a caer en producción.
El resultado de la optimización para nosotros: -500.3 kb
Valide su XML
Desafortunadamente, proguard no elimina los archivos de marcado XML adicionales de la carpeta de diseño. ¡XML no utilizado puede usar widgets "pesados" y Proguard tampoco podrá excluirlos del ensamblaje! Para evitar esto, elimine los recursos no utilizados con Refactor -> Remove unused resources...
Comprueba tu di
Si usted, como nosotros, utiliza el tiempo de ejecución DI, compruebe si tiene proveedores para esas dependencias que no está utilizando. Proguard no puede excluirlos del ensamblado porque no están sin usar desde el punto de vista del compilador. Los usas cuando construyes un gráfico de dependencia.
Excluir las dependencias de depuración de las versiones de lanzamiento
Las herramientas de depuración pueden ocupar mucho espacio inesperadamente. ¡Por ejemplo, stetho
pesa aproximadamente 0.2
después de la compresión! En cualquier caso, es mejor excluir toda la infraestructura de depuración de la versión de lanzamiento para que nadie pueda aprender demasiado sobre su aplicación simplemente descargándola de Google Play.
Puede hacer diferentes versiones de los mismos archivos para depurar y liberar. Para hacer esto, en la carpeta src
, junto a main
, cree las carpetas de debug
y release
. Ahora puede escribir la función initStetho
que inicializa Stetho en el archivo src/debug/java/your/pkg/Stetho.kt
y la función initStetho
que no hace nada en el src/debug/java/your/pkg/Stetho.kt
src/release/java/your/pkg/Stetho.kt
.
Por si acaso, asegúrese de que esta dependencia se incluya solo en las compilaciones de depuración. Puede hacer esto reemplazando la implementation
con debugImplementation
en build.gradle
. La mayoría de las veces, proguard elimina archivos innecesarios incluso sin este paso, pero no siempre. La respuesta a la pregunta "¿por qué?" a continuación en el texto del artículo .
A veces, en la misma base de código, se emiten varias versiones diferentes de una aplicación. Estas pueden ser diferentes versiones para diferentes países o regiones, o, como en nuestro caso, para diferentes clientes. A continuación hay consejos sobre cómo descargar la plataforma.

/ PxHere / PD
Nuestra experiencia
Estamos desarrollando un diseñador de aplicaciones móviles E-SHOP . Tenemos varias docenas de clientes y cada uno tiene su propio conjunto individual de componentes. Algunos componentes son utilizados por todos los clientes, algunos son solo parte. Nuestra tarea es incluir en el ensamblaje del cliente solo aquellos componentes que necesita.
Excepción de bandera
Para cada cliente creamos un producto por separado. Esto es conveniente porque es fácil crear diferentes recursos para diferentes clientes, el IDE proporciona una interfaz gráfica para cambiar entre sabores y los cachés funcionan bien. Y también puede generar su propio BuildConfig.java
para cada cliente. Los valores de campo de esta clase se conocen en la etapa de compilación. ¡Esto es lo que necesitamos! Cree un campo de tipo boolean
para cada componente.
android { productFlavors { client1 { buildConfigField("boolean", "IS_CATALOG_ENABLED", "true") } client2 { buildConfigField("boolean", "IS_CATALOG_ENABLED", "false") } } }
Esta es una versión simplificada de la configuración. El presente es complejo debido a la integración con nuestro IC.
¡Ahora se sabe si el componente está activo en la etapa de compilación, y Proguard puede excluirlo del ensamblaje!
XML nuevamente
¡Ahora el problema con los diseños XML no utilizados adquiere una nueva dimensión! No puede simplemente tomar y quitar el marcado de un componente simplemente porque algunos clientes no lo necesitan.
En nuestra aplicación XML de uno de los componentes raramente utilizados, utilizamos un widget que hacía referencia a la biblioteca de reconocimiento de imágenes firebase.ml.vision
. Pesa alrededor de 0.2 mb, que es mucho. Se decidió agregar este widget con código en lugar de declararlo en el marcado. Después de eso, Proguard pudo excluir la vision
de la asamblea para los clientes que no la necesitan.
El resultado de la optimización para nosotros: -222.3 kb para el APK promedio
@Keep
Hay 2 formas de decirle a Proguard que su clase no puede ser minimizada: escriba una regla en el archivo proguard-rules.pro
o @Keep
anotación @Keep
. En la biblioteca play-services-vision
, esta anotación está en la clase raíz. Por lo tanto, 0.2 mb quedaron muertos incluso en aquellas aplicaciones cliente que no necesitan reconocimiento de imagen.
No encontré una forma simple y segura de eliminar esta anotación. Si sabe cómo, escriba los comentarios.
Afortunadamente, la biblioteca firebase.ml.vision
, que es una versión más nueva de play-services-vision
, no usa esta anotación y resolvimos el problema yendo a ella.
Y nuevamente DI
Por último pero no menos importante. DI para componentes desconectados. Aquí todo es simple: para cada componente usamos nuestro propio contenedor y conectamos las dependencias generales a través de un módulo separado.
El resultado de optimización para nosotros: -20.1 kb para el APK promedio
Conclusiones
- El peso del APK promedio ha disminuido de
4.4
a 3.1
, y el mínimo - ¡a 2.5
! - El código de la aplicación no fue dañado, sino mejorado. Ahora es más fácil trabajar con DI
Todas las optimizaciones presentadas en el artículo son "frutas bajas". Son bastante fáciles de implementar y obtienen rápidamente el resultado. Hasta -43% para un APK ya optimizado en nuestro caso. Espero haberte ahorrado tiempo enumerando todo en un solo lugar.
Gracias a todos!