bazel mobile-install

Informar um problema Ver código-fonte Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Desenvolvimento iterativo rápido para Android

Esta página descreve como o bazel mobile-install torna o desenvolvimento iterativo para Android muito mais rápido. Ele descreve os benefícios dessa abordagem em relação aos desafios do método tradicional de instalação de apps.

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. Desative o Proguard removendo o atributo proguard_specs.
  3. Defina o atributo multidex como native.
  4. Defina o atributo dex_shards como 10.
  5. Conecte o dispositivo que usa o ART (não o Dalvik) por USB e ative a depuração USB nele.
  6. Execute bazel mobile-install :your_target. A inicialização do app será um pouco mais lenta do que o normal.
  7. Edite o código ou os recursos do Android.
  8. Execute bazel mobile-install --incremental :your_target.
  9. Aproveite não ter que esperar muito.

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

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

Em caso de dúvida, consulte o exemplo ou entre em contato conosco.

Introdução

Um dos atributos mais importantes da cadeia de ferramentas de um 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 atendem às suas expectativas.

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

O bazel mobile-install torna o desenvolvimento iterativo para Android muito mais rápido usando uma combinação de poda 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, o "dx" é invocado exatamente uma vez no build e não sabe como reutilizar o trabalho de builds anteriores: ele dexifica 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 fazer upload. O app inteiro é enviado, mesmo que apenas pequenas partes tenham mudado, por exemplo, um recurso ou um método único. Isso pode ser um gargalo.

  • Compilação para código nativo. O Android L introduziu o ART, um novo tempo de execução do Android, que compila apps com antecedência, em vez de fazer isso no momento, 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 geralmente instalam um app uma vez e 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 de bazel mobile-install

O bazel mobile-installfaz as seguintes melhorias:

  • Dexing fragmentado. Depois de criar o código Java do app, o Bazel fragmenta os arquivos de classe em partes de tamanho aproximado e invoca dx separadamente nelas. dx não é invocado em fragmentos que não mudaram desde o último build.

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

  • Carregar partes do app de fora do .apk. Um pequeno aplicativo stub é colocado no arquivo .apk que carrega recursos Android, código Java e código nativo do diretório de instalação móvel no dispositivo e transfere o controle para o app real. Tudo isso é transparente para o app, exceto em alguns casos descritos abaixo.

Dexing fragmentado

A dexação fragmentada é razoavelmente simples: depois que os arquivos .jar são criados, uma ferramenta os fragmenta em arquivos .jar separados de tamanho aproximadamente igual e, em seguida, invoca dx nos que foram alterados desde o build anterior. A lógica que determina quais fragmentos serão dex não é específica do Android: ela apenas usa o algoritmo de poda de mudanças geral do Bazel.

A primeira versão do algoritmo de fragmentação simplesmente ordenava os arquivos .class em ordem alfabética e depois dividia a lista em partes de tamanhos iguais, mas isso não era ótimo: se uma classe fosse adicionada ou removida (mesmo que anônima ou aninhada), todas as classes depois dela seriam deslocadas em uma posição, resultando na indexação dos fragmentos novamente. Portanto, foi decidido dividir os pacotes Java em vez de classes individuais. É claro que isso ainda resulta na dexaçã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 pelo arquivo BUILD (usando o atributo android_binary.dex_shards). 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. Portanto, ele não pode determinar o número ideal de fragmentos porque não sabe quantas classes Java haverá no app. De modo geral, quanto mais fragmentos, mais rápido o build e a instalação serão, mas a inicialização do app fica mais lenta, porque o vinculador dinâmico precisa fazer mais trabalho. O ponto ideal geralmente fica entre 10 e 50 fragmentos.

Transferência de arquivos incrementais

Depois de criar o app, a próxima etapa é instalá-lo, de preferência com o menor esforço possível. A instalação consiste nas seguintes etapas:

  1. Instalar o .apk (normalmente usando adb install)
  2. Fazer upload dos arquivos .dex, recursos do Android e bibliotecas nativas para o diretório de instalação em dispositivos móveis

Não há muita incrementalidade na primeira etapa: o app é instalado ou não. Atualmente, o Bazel depende do usuário para indicar se ele precisa realizar essa etapa com a opção de linha de comando --incremental, porque não é possível determinar em todos os casos se ela é necessária.

Na segunda etapa, os arquivos do app do build são comparados a um arquivo de manifesto no dispositivo que lista quais arquivos do app estão no dispositivo e os checksums. Os arquivos novos são enviados por upload para o dispositivo, os arquivos alterados são atualizados e os arquivos removidos são excluídos do dispositivo. Se o manifesto não estiver presente, será considerado que todos os arquivos precisam ser enviados.

É possível enganar o algoritmo de instalação incremental mudando um arquivo no dispositivo, mas não a soma de verificação dele no manifesto. Isso poderia ter sido protegido computando a soma de verificação dos arquivos no dispositivo, mas isso foi considerado não valer o aumento no tempo de instalação.

O aplicativo Stub

É no aplicativo stub que acontece a mágica para carregar os dexes, o código nativo e os recursos do Android do diretório mobile-install no dispositivo.

O carregamento real é implementado pela subclassificação de BaseDexClassLoader e é uma técnica razoavelmente bem documentada. Isso acontece antes que qualquer uma das classes do app seja carregada, para que todas as classes do aplicativo que estão no APK possam ser colocadas no diretório mobile-install no dispositivo para que possam ser atualizadas sem adb install.

Isso precisa acontecer antes que qualquer uma das classes do app seja carregada. Assim, nenhuma classe de app precisa estar no .apk, o que significa que as mudanças nessas classes exigiriam uma reinstalação completa.

Isso é feito substituindo a classe Application especificada em AndroidManifest.xml pelo aplicativo stub. Ele assume o controle quando o app é iniciado e ajusta o carregador de classe e o gerenciador de recursos de maneira adequada no momento mais cedo (o construtor) usando a reflexão do Java nas partes internas do framework do Android.

Outra coisa que o aplicativo stub faz é copiar as bibliotecas nativas instaladas pela instalação móvel para outro local. Isso é necessário porque o vinculador dinâmico precisa que o bit X seja definido nos arquivos, o que não é possível fazer para qualquer local acessível por um adb não raiz.

Depois de fazer tudo isso, o aplicativo de stub instancia a classe Application real, mudando todas as referências a si mesmo para o app real no framework do Android.

Resultados

Desempenho

Em geral, o bazel mobile-install resulta em um aumento de 4 a 10 vezes na velocidade de build 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 recompilation após mudar uma biblioteca base leva mais tempo.

Limitações

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

  • Quando Context é convertido para a classe Application em ContentProvider#onCreate(). Esse método é chamado durante a inicialização do aplicativo antes que tenhamos a chance de substituir a instância da classe Application. Portanto, ContentProvider ainda vai fazer referência ao aplicativo stub em vez do real. Provavelmente, isso não é um bug, já que não é possível converter Context dessa forma, mas isso parece acontecer em alguns apps do Google.

  • Os recursos instalados por bazel mobile-install só ficam disponíveis no app. Se eles forem acessados por outros apps usando PackageManager#getApplicationResources(), eles serão da última instalação não incremental.

  • Dispositivos que não estão executando o ART. Embora o aplicativo stub funcione bem no Froyo e versões mais recentes, o Dalvik tem um bug que faz com que ele pense que o app está incorreto se o código for distribuído em vários arquivos .dex em determinados casos, por exemplo, quando as anotações Java são usadas de uma forma específica. Se o app não apresentar esses bugs, ele também vai funcionar com o Dalvik. Observe, no entanto, que o suporte a versões antigas do Android não é exatamente o nosso foco.