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 comparação com os
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:
- Encontre a regra
android_binary
do app que você quer instalar. - Remova o atributo
proguard_specs
para desativar o ProGuard. - Defina o atributo
multidex
comonative
. - Defina o atributo
dex_shards
como10
. - Conecte o dispositivo que executa o ART (não o Dalvik) via USB e ative a depuração USB nele.
- Execute
bazel mobile-install :your_target
. A inicialização do app será um pouco mais lenta do que o normal. - Edite o código ou os recursos do Android.
- Execute
bazel mobile-install --incremental :your_target
. - Aproveite a oportunidade de não ter que esperar muito.
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 mais argumentos à linha de comando deadb
. 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 --adb_arg=-s --adb_arg=<SERIAL> :your_target
--start_app
inicia o app automaticamente
Em caso de dúvida, veja o exemplo ou entre em contato com nossa equipe.
Introdução
Um dos atributos mais importantes do conjunto de ferramentas de um desenvolvedor é a velocidade: há muita diferença entre alterar o código e vê-lo executado em um segundo e ter que esperar minutos, às vezes horas, antes de receber feedback sobre se as alterações fazem o esperado.
Infelizmente, o conjunto de ferramentas tradicional do Android para criar um arquivo .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 alteração de linha única não era incomum em projetos maiores, como o Google Maps.
A bazel mobile-install
torna o desenvolvimento iterativo do Android muito mais rápido
usando uma combinação de poda de mudanças, fragmentação de trabalho e manipulação inteligente dos
componentes 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:
Dexação. Por padrão, "dx" é invocado exatamente uma vez no build e não sabe como reutilizar o trabalho de builds anteriores: ele dexa todos os métodos novamente, mesmo que apenas um deles tenha sido modificado.
Upload de dados para o dispositivo. O adb não usa toda a largura de banda de uma conexão USB 2.0, e o upload de apps maiores pode levar muito tempo. O app inteiro é enviado, mesmo que apenas pequenas partes tenham sido alteradas, por exemplo, um recurso ou único método, o que pode ser um grande gargalo.
Compilação para código nativo. O Android L lançou o ART, um novo ambiente de execução do Android, que compila apps com antecedência em vez de compilá-los no momento certo, como o Dalvik. Isso torna os apps muito mais rápidos, mas com um tempo de instalação mais longo. Essa é uma boa compensação para os usuários, porque eles normalmente instalam um app uma vez e o usam muitas vezes, mas resulta em desenvolvimento mais lento, em que um app é instalado muitas vezes e cada versão é executada, no máximo, poucas vezes.
A abordagem de bazel mobile-install
bazel mobile-install
faz as seguintes melhorias:
Dexação fragmentada. Depois de criar o código Java do app, o Bazel fragmenta os arquivos de classe em partes de tamanho aproximadamente igual e invoca
dx
separadamente nesses arquivos.dx
não é invocado em fragmentos que não mudaram desde o último build.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 separado de instalação para dispositivos móveis. Isso possibilita atualizar o código e os recursos do Android de maneira 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.
Carregando partes do app de fora do .apk. Um pequeno aplicativo stub é colocado no .apk que carrega recursos Android, código Java e código nativo do diretório de instalação de dispositivos móveis no dispositivo e, em seguida, transfere o controle para o app real. Tudo isso é transparente para o app, exceto em alguns casos específicos descritos abaixo.
Dexação fragmentada
A dexação fragmentada é razoavelmente direta: 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
naqueles que foram modificados desde o build anterior. A lógica que
determina quais fragmentos para dex não é específica do Android: ela usa apenas o
algoritmo de remoção de mudanças gerais do Bazel.
A primeira versão do algoritmo de fragmentação simplesmente ordenou os arquivos .class em ordem alfabética e depois cortou a lista em partes de tamanho igual, mas isso não foi o ideal: se uma classe fosse adicionada ou removida (mesmo uma aninhada ou anônima), isso faria com que todas as classes depois dela mudassem por uma ordem alfabética, resultando na dexação dos fragmentos novamente. Portanto, foi decidido fragmentar 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 os melhores, mas atualmente ele precisa conhecer o conjunto de ações (por exemplo, comandos a serem executados durante a compilação) antes de executar qualquer uma delas. Portanto, ele não consegue determinar o número ideal de fragmentos, porque não sabe quantas classes Java haverá no app. Em geral, quanto mais fragmentos e instalações o app terá, quanto mais os fragmentos e o trabalho de build ficarão. O ponto ideal geralmente fica entre 10 e 50 fragmentos.
Transferência incremental de arquivos
Depois de criar o app, a próxima etapa é instalá-lo, de preferência com o menos esforço possível. A instalação consiste nas seguintes etapas:
- Como instalar o .apk (geralmente usando
adb install
) - Fazer upload dos arquivos .dex, recursos do Android e bibliotecas nativas para o diretório de instalação móvel
Não há muita incrementabilidade na primeira etapa: o app está instalado
ou não. Atualmente, o Bazel depende do usuário para indicar se precisa executar essa etapa
usando a opção de linha de comando --incremental
, porque não consegue determinar em
todos os casos se é necessário.
Na segunda etapa, os arquivos do app no build são comparados a um arquivo de manifesto no dispositivo que lista os arquivos do app que estão no dispositivo e as somas de verificação correspondentes. Todos os novos arquivos são enviados para o dispositivo, os que foram modificados são atualizados e os arquivos removidos são excluídos do dispositivo. Se o manifesto não estiver presente, presume-se que todos os arquivos precisam ser enviados.
É possível enganar o algoritmo de instalação incremental alterando um arquivo no dispositivo, mas não a soma de verificação dele no manifesto. Isso poderia ter sido protegido pelo cálculo da soma de verificação dos arquivos no dispositivo, mas isso foi considerado não digno do aumento no tempo de instalação.
O aplicativo Stub
É no aplicativo stub que a mágica para carregar os dexes, o código nativo e os
recursos Android do diretório mobile-install
no dispositivo.
O carregamento real é implementado pela subclasse BaseDexClassLoader
e é uma
técnica razoavelmente bem documentada. Isso acontece antes que qualquer uma das classes
do app seja carregada. Assim, todas as classes de app que estão no apk podem 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, para que nenhuma classe de app precise 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. Isso
assume o controle quando o app é iniciado e ajusta o carregador de classes e o
gerenciador de recursos de forma adequada no primeiro momento (o construtor) usando
reflexão Java nos componentes internos do framework do Android.
Outra coisa que o aplicativo stub faz é copiar as bibliotecas nativas
instaladas pela instalação de dispositivos móveis 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 em nenhum local acessível por um adb
não raiz.
Depois que tudo isso é feito, o aplicativo stub instancia a
classe Application
real, mudando todas as referências para si mesmo ao aplicativo
real no framework do Android.
Resultados
Desempenho
Em geral, bazel mobile-install
resulta em uma velocidade de 4 a 10 vezes na criação
e instalação de apps grandes após uma pequena mudança.
Os seguintes números foram calculados para alguns produtos do Google:
Obviamente, 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 usa não funcionam em todos os casos. Os casos a seguir destacam onde ele não funciona como esperado:
Quando
Context
é transmitido para a classeApplication
emContentProvider#onCreate()
. Esse método é chamado durante a inicialização do aplicativo antes de podermos substituir a instância da classeApplication
. Portanto,ContentProvider
ainda fará referência ao aplicativo stub em vez do aplicativo real. Pode-se argumentar que isso não é um bug, já que você não precisa rebaixar oContext
dessa forma, mas isso parece acontecer em alguns apps no Google.Os recursos instalados pelo
bazel mobile-install
só estão disponíveis dentro do app. Se os recursos forem acessados por outros apps viaPackageManager#getApplicationResources()
, esses recursos serão da última instalação não incremental.Dispositivos que não executam o ART. Embora o aplicativo stub funcione bem no Froyo e em versões mais recentes, o Dalvik tem um bug que o faz pensar que o app está incorreto se o código for distribuído por vários arquivos .dex em determinados casos, por exemplo, quando anotações Java são usadas de uma maneira específica. Contanto que seu app não aborde esses bugs, ele também vai funcionar com o Dalvik. No entanto, o suporte a versões antigas do Android não é exatamente nosso foco.