instalación para dispositivos móviles de Bazel

Informar un problema Ver fuente Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Desarrollo iterativo rápido para Android

En esta página, se describe cómo bazel mobile-install acelera el desarrollo iterativo para Android. Describe los beneficios de este enfoque en comparación con los desafíos del método tradicional de instalación de aplicaciones.

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. Inhabilita Proguard quitando el atributo proguard_specs.
  3. Establece el atributo multidex en native.
  4. Establece el atributo dex_shards en 10.
  5. Conecta tu dispositivo que ejecuta ART (no Dalvik) a través de USB y habilita la depuración por USB en él.
  6. Ejecuta bazel mobile-install :your_target. El inicio de la app será un poco más lento de lo habitual.
  7. Editar el código o los recursos de Android
  8. Ejecuta bazel mobile-install --incremental :your_target.
  9. Disfruta de no tener que esperar mucho.

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 --adb_arg=-s --adb_arg=<SERIAL> :your_target
  • --start_app inicia la app automáticamente

Si tienes dudas, consulta el 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: hay una gran diferencia entre cambiar el código y verlo ejecutarse en un segundo, y tener que esperar minutos, a veces horas, antes de obtener comentarios sobre si los cambios hacen lo que esperas.

Desafortunadamente, la cadena de herramientas tradicional de Android para compilar un 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, ya que usa una combinación de reducción de cambios, 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 de apps tradicionales

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

  • Dexing De forma predeterminada, "dx" se invoca exactamente una vez en la compilación y no sabe cómo reutilizar el trabajo de compilaciones anteriores: vuelve a dexear cada método, aunque solo se haya cambiado uno.

  • Se suben datos al dispositivo. adb no usa todo el ancho de banda de una 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 esto puede ser un cuello de botella importante.

  • Compilación en código nativo Android L introdujo ART, un nuevo tiempo de ejecución de Android, que compila las apps por adelantado en lugar de compilarlas justo a tiempo como Dalvik. Esto hace que las apps sean mucho más rápidas, pero el tiempo de instalación es más largo. Este es un buen equilibrio para los usuarios, ya que suelen instalar una app una vez y usarla muchas veces, pero genera un desarrollo más lento en el que una app se instala muchas veces y cada versión se ejecuta como máximo un puñado de veces.

El enfoque de bazel mobile-install

bazel mobile-installrealiza 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 igual y, luego, invoca dx por separado en cada una de ellas. dx no se invoca en las particiones que no cambiaron desde la última compilación.

  • Transferencia incremental de archivos. Los recursos de Android, los archivos .dex y las bibliotecas nativas se quitan del archivo .apk principal y se almacenan en un directorio de instalación móvil independiente. 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.

  • Cargar partes de la app desde fuera del archivo .apk En el archivo .apk, se incluye una pequeña aplicación auxiliar que carga recursos de Android, código Java y código nativo desde el directorio de instalación móvil 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

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 aproximadamente igual y, luego, invoca dx en los que se cambiaron desde la compilación anterior. La lógica que determina qué fragmentos se deben convertir en dex no es específica de Android: solo usa el algoritmo general de eliminació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, pero esto resultó ser subóptimo: si se agregaba o quitaba una clase (incluso una anidada o anónima), todas las clases que le seguían alfabéticamente se desplazaban en uno, lo que provocaba que se volviera a generar el dexing de esos fragmentos. Por lo tanto, se decidió fragmentar los paquetes de Java en lugar de las clases individuales. Por supuesto, esto aún genera la 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.

La cantidad de fragmentos se controla con el archivo BUILD (mediante 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 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, el punto óptimo se encuentra entre 10 y 50 fragmentos.

Transferencia de archivos incremental

Después de compilar la app, el siguiente paso es instalarla, preferentemente con el menor esfuerzo posible. La instalación consta de los siguientes pasos:

  1. Instalar el archivo .apk (generalmente con adb install)
  2. Subir los archivos .dex, los recursos de Android y las bibliotecas nativas al directorio mobile-install

No hay mucha incrementalidad en el primer paso: la app se instala o no. Actualmente, Bazel depende de que el usuario indique si debe realizar este paso a través de la opción de línea de comandos --incremental, ya que 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 en el dispositivo que enumera qué archivos de la app se encuentran en el dispositivo y sus sumas de verificación. Se suben los archivos nuevos al dispositivo, se actualizan los archivos que cambiaron y se borran los archivos que se quitaron del 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 cambiando un archivo en el dispositivo, pero no su suma de verificación en el manifiesto. Esto se podría haber evitado si se hubiera calculado la suma de verificación de los archivos en el dispositivo, 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 de código auxiliar es donde sucede la magia para cargar los archivos dex, el código nativo y los recursos de Android desde el directorio mobile-install integrado en el dispositivo.

La carga real se implementa a través de la subclasificación de BaseDexClassLoader y es una técnica razonablemente bien documentada. Esto sucede antes de que se cargue alguna de las clases de la app, de modo que cualquier clase de la aplicación que se encuentre en el APK se pueda colocar en el directorio mobile-install del dispositivo para que se pueda actualizar sin adb install.

Esto debe ocurrir antes de que se cargue cualquiera de las clases de la app, de modo que no se necesite ninguna clase de aplicación en el archivo .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 por la aplicación stub. Esto toma el control cuando se inicia la app y ajusta el cargador de clases y el administrador de recursos de manera adecuada en el momento más oportuno (su constructor) con la reflexión de Java sobre los elementos internos del framework de Android.

Otra tarea que realiza la aplicación de stub es copiar las bibliotecas nativas instaladas por mobile-install a otra ubicación. Esto es necesario porque el vinculador dinámico necesita que se establezca el bit X en los archivos, lo que no es posible hacer para ninguna ubicación a la que pueda acceder un adb que no sea raíz.

Una vez que se realizan todas estas acciones, la aplicación de código auxiliar instancia la clase Application real y cambia todas las referencias a sí misma por la aplicación real 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.

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

Por supuesto, esto depende de la naturaleza del cambio: la recompilación después de cambiar una biblioteca base lleva más tiempo.

Limitaciones

Los trucos que realiza la aplicación de código auxiliar no funcionan en todos los casos. En los siguientes casos, se destaca dónde no funciona según lo esperado:

  • Cuando Context se convierte en la clase Application en ContentProvider#onCreate(). Este método se llama durante el inicio de la aplicación antes de que tengamos la oportunidad de reemplazar la instancia de la clase Application. Por lo tanto, ContentProvider seguirá haciendo referencia a la aplicación de código auxiliar en lugar de la real. Podría decirse que no es un error, ya que no deberías hacer una conversión de tipo hacia abajo de Context de esta manera, pero parece que esto sucede en algunas apps de Google.

  • Los recursos instalados por bazel mobile-install solo están disponibles desde la app. Si otras apps acceden a los recursos a través de PackageManager#getApplicationResources(), estos recursos provendrán de la última instalación no incremental.

  • Dispositivos que no ejecutan ART Si bien la aplicación de código auxiliar 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 y cuando tu app no active 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).