instalación para dispositivos móviles de Bazel

Desarrollo iterativo rápido para Android

En esta página, se describe cómo bazel mobile-install acelera el desarrollo iterativo para Android. Se describen los beneficios de este enfoque en comparación con los inconvenientes de los pasos de compilación e instalación separados.

Resumen

Para instalar pequeños cambios en una app para Android muy rápidamente, haz lo siguiente:

  1. Busca la regla android_binary de la app que quieres instalar.
  2. Conecta tu dispositivo a adb.
  3. Ejecuta bazel mobile-install :your_target. El inicio de la app será un poco más lento de lo habitual.
  4. Edita el código o los recursos de Android
  5. Ejecuta bazel mobile-install :your_target.
  6. Disfruta de una instalación incremental rápida y mínima.

Estas son algunas opciones de la línea de comandos de 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 de adb. Una aplicación útil de esto es seleccionar en qué dispositivo quieres instalar la app si tienes varios dispositivos conectados a tu estación de trabajo: bazel mobile-install :your_target -- --adb_arg=-s --adb_arg=<SERIAL>.

Si tienes dudas, consulta el ejemplo, comunícate con nosotros en Google Groups o informa un problema en GitHub.

Introducción

Uno de los atributos más importantes de la cadena de herramientas de un desarrollador es la velocidad: hay una gran diferencia entre cambiar el código y verlo ejecutarse en un segundo, por un lado, y tener que esperar minutos, a veces horas, para saber si los cambios que hiciste dieron resultado, por el otro.

Desafortunadamente, la cadena de herramientas tradicional de Android para compilar un paquete .apk implica muchos pasos monolíticos y secuenciales, y todos ellos deben realizarse para compilar una app para Android. En Google, esperar cinco minutos para compilar un cambio de una sola línea no era inusual en proyectos más grandes, como Google Maps.

bazel mobile-install acelera mucho el desarrollo iterativo para Android mediante una combinación de reducción, fragmentación del trabajo y manipulación inteligente de los elementos internos de Android, todo sin cambiar el código de tu app.

Problemas con la instalación tradicional de apps

Compilar una app para Android plantea algunos problemas, como los siguientes:

  • Conversión a .dex (dexing). De forma predeterminada, la herramienta Dexer (históricamente dx, ahora d8 o r8) se invoca exactamente una vez en la compilación y no sabe cómo reutilizar el trabajo de compilaciones anteriores: vuelve a convertir cada método, aunque solo se haya cambiado uno.

  • Carga de datos al dispositivo. El adb no usa todo el ancho de banda de la conexión USB 2.0, y las apps más grandes pueden tardar mucho en subirse. Se sube toda la app, incluso si solo cambiaron partes pequeñas, por ejemplo, un recurso o un solo método, por lo que puede generarse un cuello de botella importante.

  • Compilación en código nativo. Android L incorporó ART, un nuevo entorno de ejecución de Android, que compila las apps por adelantado en lugar de compilarlas en el momento como Dalvik. Esto hace que las apps sean mucho más rápidas, pero el tiempo de instalación es más largo. Se trata de una buena relación de tiempos para los usuarios, ya que estos suelen instalar una app una vez y usarla muchas veces. Sin embargo, enlentece el proceso de desarrollo, en el que las apps se instalan muchas veces pero cada versión se ejecuta muy pocas.

El enfoque de bazel mobile-install

bazel mobile-installrealiza las siguientes mejoras:

  • Desugarización y dexing fragmentados. Después de compilar el código Java de la app, Bazel fragmenta los archivos de clase en partes de tamaño similar e invoca d8 por separado en algunas de ellas. d8 no se invoca en los fragmentos que no cambiaron desde la última compilación. Luego, estos fragmentos se compilan en APKs fragmentados separados.

  • Transferencia incremental de archivos. Los recursos de Android, los archivos .dex y las bibliotecas nativas se quitan del paquete .apk principal y se almacenan en un directorio mobile-install específico. 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 archivos lleva menos tiempo y solo se vuelven a compilar en el dispositivo los archivos .dex que cambiaron.

  • Instalación fragmentada. La instalación desde dispositivos móviles usa la herramienta apkdeployer de Android Studio para combinar los APKs fragmentados en el dispositivo conectado y brindar una experiencia cohesiva.

Dexing fragmentado

El dexing fragmentado es bastante sencillo: una vez que se compilan los archivos .jar, una herramienta los fragmenta en archivos .jar separados de tamaño similar y, luego, invoca d8 en los que se cambiaron desde la compilación anterior. La lógica que determina qué fragmentos convertir no es específica de Android: se usa el algoritmo general de reducción de cambios de Bazel.

La primera versión del algoritmo de fragmentación simplemente ordenaba los archivos .class alfabéticamente y, luego, dividía la lista en partes del mismo tamaño, lo que no era eficiente: si se agregaba o quitaba una clase (incluso una anidada o anónima), todas las clases que le seguían alfabéticamente se desplazaban una posición, lo que provocaba que se volviera a aplicar el proceso de dexing para esos fragmentos. Por lo tanto, se decidió fragmentar los paquetes de Java en lugar de las clases individuales. Por supuesto, aún se aplica el proceso de dexing a varios fragmentos si se agrega o quita un paquete, pero eso es mucho menos frecuente que agregar o quitar una sola clase.

La cantidad de fragmentos se controla con la configuración de la línea de comandos, a través de la marca --define=num_dex_shards=N. En un mundo ideal, Bazel determinaría automáticamente cuántos fragmentos conviene crear, pero en la actualidad debe conocer el conjunto de acciones (por ejemplo, los comandos que se ejecutarán durante la compilación) antes de ejecutar cualquiera de ellas, por lo que no puede determinar la cantidad óptima de fragmentos porque no sabe cuántas clases de Java habrá finalmente en la app. En términos generales, cuantos más fragmentos haya, más rápida será la compilación y la instalación, pero más lento será el inicio de la app, ya que el vinculador dinámico tendrá que hacer más trabajo. Por lo general, la cantidad óptima se encuentra entre 10 y 50 fragmentos.

Implementación incremental

La utilidad apkdeployer que se describe en "El enfoque de mobile-install" ahora controla la instalación y la transferencia incrementales de fragmentos de APK. Mientras que las versiones anteriores (nativas) de mobile-install requerían un seguimiento manual de las instalaciones por primera vez y la aplicación selectiva de la marca --incremental en instalaciones posteriores, la versión más reciente en rules_android se simplificó en gran medida. Se puede usar la misma invocación de instalación desde dispositivos móviles, independientemente de la cantidad de veces que se haya instalado o reinstalado la app.

A grandes rasgos, la herramienta de apkdeployer es un wrapper para varios subcomandos de adb. La lógica principal del punto de entrada se encuentra en la clase com.android.tools.deployer.Deployer, con otras clases de utilidad ubicadas en el mismo paquete. La clase Deployer ingiere, entre otras cosas, una lista de rutas de acceso a los APK divididos y un archivo .proto con información sobre la instalación, y aprovecha las funciones de implementación de los Android App Bundles para crear una sesión de instalación y, luego, implementar de forma incremental las divisiones de la app. Consulta las clases ApkPreInstaller y ApkInstaller para obtener detalles sobre la implementación.

Resultados

Rendimiento

En general, bazel mobile-install acelera de 4 a 10 veces la compilación e instalación de apps grandes tras un pequeño cambio.

Los siguientes números se calcularon para algunos productos de Google:

Por supuesto, la reducción real dependerá de la naturaleza del cambio: volver a compilar una app después de cambiar una biblioteca base lleva más tiempo.

Limitaciones

Los trucos de la aplicación stub no funcionan en todos los casos. A continuación se señalan escenarios en los que no se logra la reducción esperada:

  • La instalación en dispositivos móviles solo se admite a través de las reglas de Starlark de rules_android. Consulta el "breve historial de la instalación en dispositivos móviles" para obtener más detalles.

  • Solo se admiten dispositivos que ejecutan ART. La instalación desde dispositivos móviles usa funciones de API y de tiempo de ejecución que solo existen en dispositivos que ejecutan ART, no Dalvik. Cualquier entorno de ejecución de Android más reciente que Android L (API 21+) debería ser compatible.

  • Bazel debe ejecutarse con un tiempo de ejecución de Java de herramientas y una versión de lenguaje de 17 o superior.

  • Las versiones de Bazel anteriores a la 8.4.0 deben especificar algunas marcas adicionales para mobile-install. Consulta el instructivo de Bazel para Android. Estos parámetros indican a Bazel dónde se encuentra el aspecto de instalación para dispositivos móviles de Starlark y qué reglas se admiten.

Breve historia de las instalaciones en dispositivos móviles

Las versiones anteriores de Bazel incluían de forma nativa reglas integradas de compilación y prueba para lenguajes y ecosistemas populares, como C++, Java y Android. Por lo tanto, estas reglas se denominaron reglas nativas. Bazel 8 (lanzado en 2024) quitó la compatibilidad con estas reglas porque muchas de ellas se habían migrado al lenguaje Starlark. Consulta la entrada de blog sobre la versión 8.0 LTS de Bazel para obtener más detalles.

Las reglas nativas heredadas de Android también admitían una versión nativa heredada de la funcionalidad de instalación móvil. Ahora se conoce como "instalación desde dispositivos móviles v1" o "instalación desde dispositivos móviles nativa". Esta funcionalidad se borró en Bazel 8, junto con las reglas integradas de Android.

Ahora, todas las funciones de instalación en dispositivos móviles, así como todas las reglas de compilación y prueba de Android, se implementan en Starlark y residen en el repositorio de GitHub de rules_android. La versión más reciente se conoce como "mobile-install v3" o "MIv3".

Nota sobre la nomenclatura: En un momento, Google solo tenía disponible de forma interna una "instalación móvil v2", pero nunca se publicó de forma externa, y solo se sigue usando la versión 3 para la implementación de reglas_android internas de Google y de OSS.