bazel mobile-install

Desenvolvimento iterativo rápido para Android

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

Resumo

Para instalar pequenas mudanças rápidas em um app Android, siga estas etapas:

  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 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 do 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úvida, consulte o exemplo, entre em contato conosco nos Grupos do Google, ou registre 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 ao invés de minutos ou horas aguardando o resultado desejado.

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 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 indexa todos os métodos novamente, mesmo que apenas um método 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 compilá-los no momento da execução, 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, mas 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 do bazel mobile-install

O bazel mobile-install faz as seguintes melhorias:

  • Simplificação e dexing fragmentados. Depois de criar o código Java do app, o Bazel divide os arquivos de classe em partes de tamanho aproximadamente igual e invoca d8 em cada uma delas. O d8 não é invocado em fragmentos que não foram alterados desde o último 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. O mobile-install 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. Mas os resultados não saíram como esperado: 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 DEX desses fragmentos novamente. Por isso, decidimos fragmentar pacotes 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 acontece com menos frequência 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 trabalhar mais. 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 apkdeployer utilitário descrito em "A abordagem do mobile-install". Enquanto as versões anteriores (nativas) do mobile-install exigiam o rastreamento manual das instalações pela primeira vez e a aplicação seletiva da flag na instalação subsequente, a versão mais recente em rules_android foi muito simplificada.--incremental A mesma invocação de mobile-install pode ser usada, independentemente de quantas vezes o app foi instalado ou reinstalado.

Em um nível alto, a ferramenta apkdeployer é um wrapper em torno de vários subcomandos adb. A lógica principal do ponto de entrada pode ser encontrada na com.android.tools.deployer.Deployer classe, com outras classes de utilitários localizadas 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, e aproveita os 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 ApkPreInstaller e ApkInstaller classes para detalhes de 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, depende da natureza da mudança: a recompilação depois de mudar uma biblioteca de base leva mais tempo.

Limitações

Os truques que o aplicativo stub faz não funcionam em todos os casos. Os casos a seguir destacam onde ele não funciona como esperado:

  • O mobile-install só é compatível com as regras do Starlark de rules_android. Consulte a "breve história do mobile-install" para mais detalhes.

  • Somente dispositivos que executam o ART são compatíveis. O mobile-install usa recursos de API e de ambiente de execução que só existem em dispositivos que executam o ART, não o Dalvik. Qualquer ambiente de execução do Android mais recente que o Android L (API 21 ou mais recente) precisa ser compatível.

  • O próprio Bazel precisa ser executado com um ambiente de execução Java e uma versão de linguagem de 17 ou mais recente.

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

Uma breve história do mobile-install

As versões anteriores do Bazel incluíam nativamente regras de build e teste integradas para linguagens e ecossistemas populares, como C++, Java e Android. Essas regras 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. Consulte a "postagem do blog sobre o LTS do Bazel 8.0" para mais detalhes.

As regras nativas legadas do Android também ofereciam suporte a uma versão nativa legada da funcionalidade de mobile-install. Isso agora é chamado de "mobile-install v1" ou "mobile-install nativo". Essa funcionalidade foi excluída no Bazel 8, junto com as regras integradas do Android.

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

Observação de nomenclatura: houve um "mobile-install v2" disponível apenas internamente no Google em um momento, mas ele nunca foi publicado externamente, e apenas a v3 continua a ser usada para a implantação de regras_android internas e de código aberto do Google.