instalación para dispositivos móviles de Bazel

7.3 · 7.2 · 7.1 · 7.0 · 6.5

Desarrollo iterativo rápido para Android

En esta página, se describe cómo bazel mobile-install hace que el desarrollo iterativo para Android sea mucho más rápido. En él, se describen los beneficios de este enfoque en comparación con los desafíos del método tradicional de instalación de apps.

Resumen

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

  1. Busca la regla android_binary de la app que quieres instalar.
  2. Para inhabilitar Proguard, quita 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) por 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 línea de comandos de Bazel que pueden ser útiles:

  • --adb le indica a Bazel qué objeto binario de adb usar.
  • Se puede usar --adb_arg para agregar argumentos adicionales a la línea de comandos de adb. Una aplicación útil de esto es seleccionar el dispositivo en el que deseas realizar la instalación 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 muchas diferencias 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 sobre 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 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 hace que el desarrollo iterativo para Android sea mucho más rápido con una combinación de poda de cambios, fragmentación de trabajo y manipulación inteligente de las partes internas de Android, todo sin cambiar el código de tu app.

Problemas con la instalación tradicional de apps

La compilación de 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 volver a usar el trabajo de compilaciones anteriores: vuelve a dexificar todos los métodos, aunque solo se haya cambiado uno.

  • 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. Se sube toda la app, incluso si solo cambiaron partes pequeñas, como un recurso o un solo método, por lo que esto puede ser un gran cuello de botella.

  • Compilación en código nativo. Android L 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 un tiempo de instalación más prolongado. Esta es una buena opción para los usuarios, ya que, por lo general, instalan una app una vez y la usan muchas veces, pero genera un desarrollo más lento, en el que se instala una app muchas veces y cada versión se ejecuta como máximo unas pocas veces.

El enfoque de bazel mobile-install

bazel mobile-install realiza 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 los invoca a dx por separado. No se invoca dx en los fragmentos que no cambiaron desde la última compilación.

  • Transferencia de archivos incremental. Los recursos de Android, los archivos .dex y las bibliotecas nativas se quitan del .apk principal y se almacenan en un directorio de instalación para dispositivos móviles 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 los archivos lleva menos tiempo y solo los archivos .dex que cambiaron se vuelven a compilar en el dispositivo.

  • Cargar 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 fragmentación de dex es bastante sencilla: una vez que se compilan los archivos .jar, una herramienta los divide en archivos .jar separados de tamaño aproximadamente igual y, luego, invoca dx en los que se modificaron desde la compilación anterior. La lógica que determina qué fragmentos se deben convertir a dex no es específica de Android: solo usa el algoritmo general de poda de cambios de Bazel.

La primera versión del algoritmo de fragmentación simplemente ordenó alfabéticamente los archivos .class y, luego, cortó la lista en partes de igual tamaño, pero esto resultó ser poco óptimo: si se agregaba o quitaba una clase (incluso una anidada o anónima), todas las clases cambiarían alfabéticamente después de ella, lo que generaría dex esos fragmentos nuevamente. Por lo tanto, se decidió dividir los paquetes de Java en lugar de las clases individuales. Por supuesto, esto aún genera un proceso de dexing a 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 de 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 consta de los siguientes pasos:

  1. Instalar el archivo .apk (por lo general, con adb install)
  2. 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 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 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, los que cambiaron se actualizan y los que se quitaron se borran del dispositivo. Si el manifiesto no está presente, se considerará que todos los archivos deben subirse.

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 comprobación en el manifiesto. Se podría haber protegido contra esto calculando 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 stub es donde se produce la magia para cargar los dexes, el código nativo y los recursos de Android desde el directorio mobile-install integrado 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.

Para ello, se reemplaza la clase Application especificada en AndroidManifest.xml por la aplicación de 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 temprano (su constructor) con la reflexión de Java en las partes internas del framework de Android.

Otra cosa que hace la aplicación de stub es copiar las bibliotecas nativas que instala mobile-install en 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 para ninguna ubicación a la que pueda acceder un adb que no sea raíz.

Una vez que se completan todos estos pasos, la aplicación del stub crea una instancia de la clase Application real y cambia todas las referencias a sí misma a 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.

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 como se espera:

  • Cuando Context se transmite a la clase Application en ContentProvider#onCreate() Se llama a este método 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 stub en lugar de a la real. Podría decirse que este no es un error, ya que no se supone que reduzcas Context de esta forma, pero, al parecer, 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 de PackageManager#getApplicationResources(), estos recursos serán de la última instalación no incremental.

  • Dispositivos que no ejecutan ART Si bien la aplicación stub funciona bien en Froyo y versiones posteriores, Dalvik tiene un error que hace pensar que la app es incorrecta si su código se distribuye en varios archivos .dex en ciertos casos, por ejemplo, cuando las anotaciones de Java se usan de 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).