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 mucho el desarrollo iterativo para Android. Se describen los beneficios de este enfoque en comparación con las desventajas de los pasos de compilación e instalación separados.

Resumen

Para instalar pequeños cambios en una app para Android muy rápido, 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 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 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>.

En caso de duda, consulta el ejemplo, comunícate con nosotros en Grupos de Google, o registra un problema de 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-install realiza las siguientes mejoras:

  • Desazucarado 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 fragmentos que no cambiaron desde la última compilación. Luego, estos fragmentos se compilan en APK 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. Mobile-install usa la herramienta apkdeployer de Android Studio para combinar APK fragmentados en el dispositivo conectado y proporcionar 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 d8en 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, mediante 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 transferencia e instalación de fragmentos de APK incrementales ahora se controla con la apkdeployer utilidad que se describe en "El enfoque de mobile-install". Mientras que las versiones anteriores (nativas) de mobile-install requerían el seguimiento manual de las instalaciones por primera vez y la aplicación selectiva de la marca --incremental en la instalación posterior, la versión más reciente en rules_android se simplificó en gran medida. Se puede usar la misma invocación de mobile-install, independientemente de cuántas veces se haya instalado o reinstalado la app.

En un nivel superior, la herramienta apkdeployer es un wrapper alrededor de varios subcomandos adb. La lógica principal del punto de entrada se puede encontrar en la com.android.tools.deployer.Deployer clase, con otras clases de utilidad ubicadas en el mismo paquete. La clase Deployer incorpora, entre otras cosas, una lista de rutas de acceso para dividir APKs y un protobuf con información sobre la instalación, y aprovecha las funciones de implementación para paquetes de aplicaciones para Android 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 de 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:

  • Mobile-install solo es compatible con las reglas de Starlark de rules_android. Consulta la "breve historia de mobile-install" para obtener más detalles.

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

  • Bazel debe ejecutarse con una herramienta de tiempo de ejecución de Java y una versión de lenguaje 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. Estas marcas informan a Bazel dónde está el aspecto de mobile-install de Starlark y qué reglas se admiten.

Breve historia de mobile-install

Las versiones anteriores de Bazel incluían de forma nativa reglas de compilación y prueba integradas para lenguajes y ecosistemas populares, como C++, Java y Android. Por lo tanto, estas reglas se denominaban 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 de Bazel 8.0 LTS" para obtener más detalles.

Las reglas nativas heredadas de Android también admitían una versión nativa heredada de la funcionalidad de mobile-install. Ahora, se denomina "mobile-install v1" o "mobile-install nativo". Esta funcionalidad se borró en Bazel 8, junto con las reglas integradas de Android.

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

Nota sobre la denominación: En un momento, hubo una "mobile-install v2" disponible solo de forma interna en Google, pero nunca se publicó de forma externa, y solo se sigue usando la v3 para la implementación de reglas_android internas de Google y de OSS.