bazel mobile-install

Reportar um problema Ver a fonte Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Desenvolvimento iterativo rápido para Android

Nesta página, descrevemos como o bazel mobile-install acelera muito o desenvolvimento iterativo para Android. Ele descreve os benefícios dessa abordagem em comparação com os inconvenientes de etapas separadas de build e instalação.

Resumo

Para instalar pequenas mudanças em um app Android muito rapidamente, faça o seguinte:

  1. Encontre a regra android_binary do app que você quer instalar.
  2. Conecte o dispositivo ao adb.
  3. Execute bazel mobile-install :your_target. A inicialização do app vai ser um pouco mais lenta do que o normal.
  4. Edite o código ou os recursos do Android.
  5. Execute bazel mobile-install :your_target.
  6. Aproveite uma instalação incremental rápida e mínima.

Algumas opções de linha de comando para o Bazel que podem ser úteis:

  • --adb informa ao Bazel qual binário adb usar.
  • --adb_arg pode ser usado para adicionar argumentos extras à linha de comando de adb. Uma aplicação útil disso é selecionar em qual dispositivo você quer instalar se tiver vários dispositivos conectados à sua estação de trabalho: bazel mobile-install :your_target -- --adb_arg=-s --adb_arg=<SERIAL>

Em caso de dúvidas, consulte o exemplo, entre em contato com a gente no Google Grupos ou informe um problema no GitHub.

Introdução

Um dos atributos mais importantes de uma cadeia de ferramentas de desenvolvedor é a velocidade. Há uma grande diferença entre mudar o código e vê-lo ser executado em um segundo e ter que esperar minutos, às vezes horas, antes de receber feedback sobre se as mudanças fazem o que você espera.

Infelizmente, a cadeia de ferramentas tradicional do Android para criar um .apk envolve muitas etapas monolíticas e sequenciais, e todas elas precisam ser realizadas para criar um app Android. No Google, esperar cinco minutos para criar uma mudança de uma única linha não era incomum em projetos maiores, como o Google Maps.

O bazel mobile-install acelera muito o desenvolvimento iterativo para Android usando uma combinação de remoção de mudanças, fragmentação de trabalho e manipulação inteligente de elementos internos do Android, tudo sem mudar o código do app.

Problemas com a instalação tradicional de apps

A criação de um app Android tem alguns problemas, incluindo:

  • Dexing. Por padrão, a ferramenta Dexer (historicamente dx, agora d8 ou r8) é invocada exatamente uma vez no build e não sabe como reutilizar o trabalho de builds anteriores: ela dexes todos os métodos novamente, mesmo que apenas um tenha sido alterado.

  • Fazer upload de dados para o dispositivo. O adb não usa toda a largura de banda de uma conexão USB 2.0, e apps maiores podem levar muito tempo para serem enviados. Todo o app é enviado, mesmo que apenas pequenas partes tenham mudado, por exemplo, um recurso ou um único método. Isso pode ser um grande gargalo.

  • Compilação para código nativo. O Android L introduziu o ART, um novo ambiente de execução do Android, que compila apps antecipadamente em vez de compilar no momento certo, como o Dalvik. Isso torna os apps muito mais rápidos, mas aumenta o tempo de instalação. Essa é uma boa troca para os usuários, porque eles normalmente instalam um app uma vez e o usam muitas vezes. No entanto, isso resulta em um desenvolvimento mais lento, em que um app é instalado muitas vezes e cada versão é executada no máximo algumas vezes.

A abordagem de bazel mobile-install

O bazel mobile-install faz as seguintes melhorias:

  • Desaçucaramento e dexing fragmentados. Depois de criar o código Java do app, o Bazel fragmenta os arquivos de classe em partes de tamanho aproximadamente igual e invoca d8 separadamente nelas. d8 não é invocado em fragmentos que não mudaram desde a última build. Esses fragmentos são compilados em APKs fragmentados separados.

  • Transferência incremental de arquivos. Os recursos do Android, os arquivos .dex e as bibliotecas nativas são removidos do .apk principal e armazenados em um diretório mobile-install separado. Isso permite atualizar o código e os recursos do Android de forma independente sem reinstalar todo o app. Assim, a transferência dos arquivos leva menos tempo, e apenas os arquivos .dex que foram alterados são recompilados no dispositivo.

  • Instalação fragmentada. A instalação em dispositivos móveis usa a ferramenta apkdeployer do Android Studio para combinar APKs fragmentados no dispositivo conectado e oferecer uma experiência coesa.

Dexing fragmentado

O dexing fragmentado é razoavelmente simples: depois que os arquivos .jar são criados, uma ferramenta os fragmenta em arquivos .jar separados de tamanho aproximadamente igual e invoca d8 naqueles que foram alterados desde o build anterior. A lógica que determina quais fragmentos serão dexados não é específica do Android: ela apenas usa o algoritmo geral de remoção de mudanças do Bazel.

A primeira versão do algoritmo de fragmentação simplesmente ordenava os arquivos .class em ordem alfabética e dividia a lista em partes de tamanho igual. No entanto, isso se mostrou subótimo: se uma classe fosse adicionada ou removida (mesmo uma aninhada ou anônima), todas as classes alfabeticamente depois dela seriam deslocadas em uma posição, resultando na indexação desses fragmentos novamente. Por isso, decidimos fragmentar pacotes do Java em vez de classes individuais. É claro que isso ainda resulta na indexação de muitos fragmentos se um novo pacote for adicionado ou removido, mas isso é muito menos frequente do que adicionar ou remover uma única classe.

O número de fragmentos é controlado pela configuração da linha de comando usando a flag --define=num_dex_shards=N. Em um mundo ideal, o Bazel determinaria automaticamente quantos fragmentos são melhores, mas atualmente ele precisa saber o conjunto de ações (por exemplo, comandos a serem executados durante o build) antes de executar qualquer uma delas. Assim, não é possível determinar o número ideal de fragmentos porque ele não sabe quantas classes Java haverá no app. Em geral, quanto mais fragmentos, mais rápido será o build e a instalação, mas mais lento será a inicialização do app, porque o vinculador dinâmico precisa fazer mais trabalho. O ideal é entre 10 e 50 fragmentos.

Implantação incremental

A transferência e a instalação incrementais de fragmentos de APK agora são processadas pelo utilitário apkdeployer descrito em "A abordagem de instalação em dispositivos móveis". Enquanto as versões anteriores (nativas) da instalação em dispositivos móveis exigiam o rastreamento manual das instalações iniciais e a aplicação seletiva da flag --incremental em instalações subsequentes, a versão mais recente em rules_android foi muito simplificada. A mesma invocação de instalação em dispositivos móveis pode ser usada quantas vezes o app for instalado ou reinstalado.

Em um nível alto, a ferramenta apkdeployer é um wrapper para vários subcomandos adb. A lógica principal do ponto de entrada pode ser encontrada na classe com.android.tools.deployer.Deployer, com outras classes de utilidade colocalizadas no mesmo pacote. A classe Deployer ingere, entre outras coisas, uma lista de caminhos para dividir APKs e um protobuf com informações sobre a instalação, além de aproveitar recursos de implantação para Android App Bundles para criar uma sessão de instalação e implantar divisões de apps de forma incremental. Consulte as classes ApkPreInstaller e ApkInstaller para detalhes da implementação.

Resultados

Desempenho

Em geral, bazel mobile-install resulta em uma aceleração de 4 a 10 vezes na criação e instalação de apps grandes após uma pequena mudança.

Os números a seguir foram calculados para alguns produtos do Google:

Isso, é claro, depende da natureza da mudança: a recompilação após mudar uma biblioteca de base leva mais tempo.

Limitações

Os truques do aplicativo stub não funcionam em todos os casos. Os casos a seguir destacam situações em que ele não funciona como esperado:

  • A instalação em dispositivos móveis só é compatível com as regras do Starlark de rules_android. Consulte o "breve histórico de instalações em dispositivos móveis" para mais detalhes.

  • Apenas dispositivos que executam o ART são compatíveis. A instalação em dispositivos móveis usa recursos de API e de tempo de execução que só existem em dispositivos com ART, e não Dalvik. Qualquer ambiente de execução do Android mais recente que o Android L (API 21+) é compatível.

  • O Bazel precisa ser executado com uma ferramenta de tempo de execução Java e uma versão de linguagem 17 ou mais recente.

  • As versões do Bazel anteriores à 8.4.0 precisam especificar algumas flags adicionais para mobile-install. Consulte o tutorial do Bazel para Android. Essas flags informam ao Bazel onde está o aspecto de instalação móvel do Starlark e quais regras são compatíveis.

Uma breve história da instalação em dispositivos móveis

As versões anteriores do Bazel incluíam nativamente regras de build e teste integradas para linguagens e ecossistemas conhecidos, como C++, Java e Android. Por isso, elas eram chamadas de regras nativas. O Bazel 8 (lançado em 2024) removeu o suporte a essas regras porque muitas delas foram migradas para a linguagem Starlark. Confira mais detalhes na postagem do blog sobre o LTS do Bazel 8.0.

As regras nativas legadas do Android também ofereciam suporte a uma versão nativa legada da funcionalidade de instalação em dispositivos móveis. Agora, isso é chamado de "instalação para dispositivos móveis v1" ou "instalação para dispositivos móveis nativa". Essa funcionalidade foi excluída no Bazel 8, junto com as regras integradas do Android.

Agora, toda a funcionalidade de instalação em dispositivos móveis, bem como todas as regras de build e teste do Android, são implementadas em Starlark e residem no repositório rules_android do GitHub. A versão mais recente é conhecida como "mobile-install v3" ou "MIv3".

Observação sobre a nomenclatura: em um determinado momento, havia uma "mobile-install v2" disponível apenas internamente no Google, mas ela nunca foi publicada externamente. Somente a v3 continua sendo usada para a implantação de regras_android internas do Google e de código aberto.