Desarrollo iterativo rápido para Android
En esta página, se describe cómo bazel mobile-install
hace que el desarrollo iterativo
para Android. Describe las ventajas de este enfoque frente a
desafíos del método tradicional
de instalación de aplicaciones.
Resumen
Para instalar pequeños cambios en una app para Android con rapidez, haz lo siguiente:
- Busca la regla
android_binary
de la app que quieres instalar. - Inhabilita ProGuard quitando el atributo
proguard_specs
. - Establece el atributo
multidex
ennative
. - Establece el atributo
dex_shards
en10
. - Conecta tu dispositivo que ejecuta ART (no Dalvik) por USB y habilita la depuración por USB en él.
- Ejecuta
bazel mobile-install :your_target
. El inicio de la app será un poco más lento de lo habitual. - Edita el código o los recursos de Android.
- Ejecuta
bazel mobile-install --incremental :your_target
. - Disfruta de no tener que esperar mucho.
Estas son algunas opciones de línea de comandos para Bazel que pueden ser útiles:
--adb
le indica a Bazel qué objeto binario de adb usar.--adb_arg
se puede usar para agregar argumentos adicionales a la línea de comandos deadb
. Una aplicación útil de esto es seleccionar en qué dispositivo quieres instalarlo si tienes varios dispositivos conectados a tu estación de trabajo:bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target
--start_app
inicia la app automáticamente.
Si tienes dudas, consulta ejemplo o comunícate con nosotros.
Introducción
Uno de los atributos más importantes de la cadena de herramientas de un desarrollador es la velocidad. es una gran diferencia entre cambiar el código y ver que se ejecuta en un segundo y tener que esperar minutos, a veces horas, antes de recibir comentarios dependiendo de si los cambios hacen lo que esperas.
Lamentablemente, la cadena de herramientas tradicional de Android para compilar un .apk implica muchos pasos monolíticos y secuenciales, y todos deben realizarse para compilar una app para Android. En Google, esperar cinco minutos para crear una línea fue algo inusual en proyectos más grandes, como Google Maps.
bazel mobile-install
hace que el desarrollo iterativo para Android sea mucho más rápido, ya que
con una combinación de reducción de cambios, fragmentación de trabajos y manipulación inteligente de
Componentes internos de Android, todo sin cambiar el código de tu app.
Problemas con la instalación de apps tradicionales
La compilación de una app para Android tiene algunos problemas, como los siguientes:
Conversión a DEX. De forma predeterminada, “dx” se invoca exactamente una vez en la compilación y no cómo reutilizar el trabajo de compilaciones anteriores: convierte a DEX en cada método nuevamente, incluso aunque solo se cambió un método.
Subir datos al dispositivo. adb no usa el ancho de banda completo de una conexión USB 2.0, y las apps más grandes pueden tardar mucho tiempo en subirse. Toda la aplicación subir, incluso si solo cambiaron partes pequeñas, como un recurso o un único método, por lo que puede ser un gran cuello de botella.
Compilación en código nativo. En Android L, se introdujo ART, un nuevo tiempo de ejecución de Android, que compila apps con anticipación en lugar de hacerlo justo a tiempo como Dalvik. Esto hace que las apps sean mucho más rápidas, a costa de una instalación más larga. tiempo. Esta es una buena compensación para los usuarios, ya que suelen instalar una app. una vez y lo usan muchas veces, pero esto hace que el desarrollo sea más lento se instala varias veces y cada versión se ejecuta, como máximo, varias veces.
El enfoque de bazel mobile-install
bazel mobile-install
implementa las siguientes mejoras:
Dexing fragmentado Después de compilar el código Java de la app, Bazel fragmenta los archivos de clase en partes de tamaño aproximadamente iguales y llama a
dx
por separado en ellas.dx
no se invoca en los fragmentos que no cambiaron desde la última compilación.Transferencia de archivos incremental. Recursos de Android, archivos .dex y aplicaciones nativas se quitan del .apk principal y se almacenan en un archivo directorio de instalación para dispositivos móviles. Esto permite actualizar el código y los recursos de Android de forma independiente sin reinstalar toda la app. Por lo tanto, la transferencia de los archivos lleva menos tiempo y solo los archivos .dex que cambiaron se vuelven a compilar en el dispositivo.
Carga de partes de la app desde fuera del .apk. Se coloca una pequeña aplicación de stub en el .apk que carga recursos de Android, código Java y código nativo desde el directorio de instalación para dispositivos móviles integrado en el dispositivo y, luego, transfiere el control a la app real. Todo esto es transparente para la app, excepto en algunos casos extremos que se describen a continuación.
Dexing fragmentado
La conversión a DEX fragmentada es bastante sencilla: una vez que se compilan los archivos .jar, se crea un
herramienta
los fragmenta en archivos .jar separados de tamaño similar; luego, los invoca
dx
en aquellas que se modificaron desde la compilación anterior. La lógica que
determina qué fragmentos a DEX no son específicos de Android: solo usa el
algoritmo general de reducción de cambios de Bazel.
La primera versión del algoritmo de fragmentación simplemente ordenó los archivos .class alfabéticamente y luego cortaba la lista en partes de igual tamaño, pero esto demostró ser subóptimo: si se agrega o quita una clase (incluso una clase anidada o uno), haría que todas las clases alfabéticamente cambiaran de a una, lo que provocará nuevamente la conversión a DEX de esos fragmentos. Por lo tanto, se decidió dividir los paquetes de Java en lugar de las clases individuales. Por supuesto, esto aún genera indexación de muchos fragmentos si se agrega o quita un paquete nuevo, pero eso es mucho menos frecuente que agregar o quitar una sola clase.
El archivo BUILD controla la cantidad de fragmentos (con el atributo android_binary.dex_shards
). En un mundo ideal, Bazel determinaría automáticamente cuántos fragmentos son mejores, pero actualmente Bazel debe conocer el conjunto de acciones (por ejemplo, los comandos que se ejecutarán durante la compilación) antes de ejecutar cualquiera de ellos, por lo que no puede determinar la cantidad óptima de fragmentos porque no sabe cuántas clases de Java habrá en la app. En términos generales, cuantos más fragmentos haya, más rápidas serán la compilación y la instalación, pero más lento será el inicio de la app, ya que el vinculador dinámico tiene que hacer más trabajo. Por lo general, el punto ideal es entre 10 y 50 gemas.
Transferencia de archivos incremental
Después de compilar la app, el siguiente paso es instalarla, preferentemente con el menor esfuerzo posible. La instalación consiste en los siguientes pasos:
- Instalar el archivo .apk (por lo general, con
adb install
) - Subir los archivos .dex, los recursos de Android y las bibliotecas nativas al directorio de instalación para dispositivos móviles
No hay mucha incrementalidad en el primer paso: la app está instalada
o no. Actualmente, Bazel depende del usuario para indicar si debe realizar este paso.
a través de la opción de línea de comandos --incremental
porque no puede determinar en
todos los casos si es necesario.
En el segundo paso, los archivos de la app de la compilación se comparan con un archivo de manifiesto integrado en el dispositivo que enumera qué archivos de la app están en el dispositivo y sus sumas de comprobación. Los archivos nuevos se suben al dispositivo y los archivos que cambiaron se actualicen y los archivos que se hayan eliminado se borrarán dispositivo. Si el manifiesto no está presente, se supone que se debe subir cada archivo.
Ten en cuenta que es posible engañar al algoritmo de instalación incremental cambiar un archivo en el dispositivo, pero no su suma de comprobación en el manifiesto. Esto podría protección mediante el cálculo de la suma de comprobación de los archivos en el pero se consideró que no valía la pena el aumento en el tiempo de instalación.
La aplicación de stub
La aplicación stub es donde la magia para cargar los dex, el código nativo y
Se ejecutan los recursos de Android del directorio mobile-install
en el dispositivo.
La carga real se implementa mediante la subclasificación de BaseDexClassLoader
y es una técnica bastante bien documentada. Esto ocurre antes de que se cargue ninguna de las clases de la app, de modo que las clases de la aplicación que se encuentran en el APK se puedan colocar en el directorio mobile-install
integrado en el dispositivo para que se puedan actualizar sin adb install
.
Esto debe ocurrir antes de que se cargue cualquiera de las clases de la app, de modo que no haya ninguna clase de aplicación en el .apk, lo que significaría que los cambios en esas clases requerirían una reinstalación completa.
Esto se logra reemplazando la clase Application
especificada en
AndroidManifest.xml
con el
aplicación stub. Esta
toma el control del inicio de la app y modifica el cargador de clases
de recursos de forma adecuada en el primer momento (su constructor), utilizando
Reflexión de Java sobre los aspectos internos del framework de Android.
Otra cosa que hace la aplicación auxiliar es copiar las bibliotecas nativas
instalada por instalación móvil en otra ubicación. Esto es necesario porque el
el vinculador dinámico necesita que se configure el bit X
en los archivos, lo que no es posible
para cualquier ubicación a la que pueda acceder un adb
no raíz.
Una vez que se hace todo esto, la aplicación stub crea una instancia de
clase Application
real, lo que cambia todas las referencias a sí misma por el valor
aplicación dentro del framework de Android.
Resultados
Rendimiento
En general, bazel mobile-install
genera una aceleración de 4 a 10 veces en la compilación y la instalación de apps grandes después de un pequeño cambio.
Se calcularon las siguientes cifras para algunos productos de Google:
Esto, por supuesto, depende de la naturaleza del cambio: la compilación nuevamente después de cambiar una biblioteca base lleva más tiempo.
Limitaciones
Los trucos que ejecuta la aplicación de stub no funcionan en todos los casos. En los siguientes casos, se destaca dónde no funciona según lo esperado:
Cuando se transmite
Context
a la claseApplication
enContentProvider#onCreate()
Se llama a este método durante la aplicación inicio antes de que podamos reemplazar la instancia deApplication
por lo queContentProvider
seguirá haciendo referencia a la aplicación auxiliar. en lugar de la real. Se podría decir que esto no es un error, ya que no se supone que debes realizar una conversión descendente deContext
de esta manera, pero parece que esto sucede en algunas apps de Google.Los recursos que instala
bazel mobile-install
solo están disponibles desde la app. Si otras apps acceden a los recursos a través dePackageManager#getApplicationResources()
, estos recursos provendrán de la última instalación no incremental.Dispositivos que no ejecutan ART Si bien la aplicación de stub funciona bien en Froyo y versiones posteriores, Dalvik tiene un error que hace que piense que la app es incorrecta si su código se distribuye en varios archivos .dex en ciertos casos, por ejemplo, cuando se usan anotaciones de Java de una manera específica. Siempre que tu app no genere estos errores, también debería funcionar con Dalvik (sin embargo, ten en cuenta que la compatibilidad con versiones anteriores de Android no es exactamente nuestro enfoque).