Crie um aplicativo para Android. Tarefa com um asterisco



Olá Habr! No verão, falei no Summer Droid Meetup com um relatório sobre a criação de um aplicativo Android. A versão em vídeo pode ser encontrada aqui: habr.com/ru/company/funcorp/blog/462825 . E para quem gosta de ler mais, acabei de escrever este artigo.

É sobre o que é - um aplicativo Android. Coletaremos de diferentes maneiras Olá, mundo!: Comece pelo console e veja o que acontece sob os bastidores dos sistemas de compilação, depois volte ao passado, lembre-se do Maven e aprenda sobre as modernas soluções Bazel e Buck. E, finalmente, tudo isso é comparável.

Pensamos em uma possível mudança no sistema de montagem quando iniciamos um novo projeto. Pareceu-nos que esta é uma boa oportunidade para procurar algumas alternativas para Gradle. Além disso, é mais fácil fazer isso no início do que traduzir um projeto existente. As seguintes falhas da Gradle nos levaram a essa etapa:

  • ele definitivamente tem problemas com a montagem incremental, embora possa ver progresso nessa direção;
  • ele se sai mal com projetos monolíticos muito grandes;
  • acontece que um demônio começa por muito tempo;
  • exigente na máquina em que está sendo executada.

APK


Antes de tudo, lembre-se do que o aplicativo Android consiste: código compilado, recursos e AndroidManifest.xml.



As fontes estão no arquivo classes.dex (pode haver vários arquivos, dependendo do tamanho do aplicativo) em um formato dex especial com o qual a máquina virtual Android pode trabalhar. Hoje é ART, em dispositivos mais antigos - Dalvik. Além disso, você pode encontrar a pasta lib, onde as fontes nativas são organizadas em subpastas. Eles serão nomeados dependendo da arquitetura do processador de destino, por exemplo, x86, arm, etc. Se você usa exoplayer, provavelmente possui lib. E a pasta aidl, que contém interfaces de comunicação entre processos. Eles serão úteis se você precisar acessar um serviço em execução em outro processo. Essas interfaces são usadas no Android e no GooglePlayServices.

Vários recursos não compilados, como imagens, estão na pasta res. Todos os recursos compilados, como estilos, linhas, etc., são mesclados em um arquivo resource.arsc. Na pasta de ativos, como regra, eles colocam tudo o que não se encaixa nos recursos, por exemplo, fontes personalizadas.

Além de tudo isso, o APK contém AndroidManifest.xml. Nele, descrevemos vários componentes do aplicativo, como Atividade, Serviço, permissões diferentes etc. Ele está na forma binária e, para olhar para dentro, primeiro terá que ser convertido em um arquivo legível por humanos.

CONSOLE


Agora que sabemos o que o aplicativo consiste, podemos tentar criar Olá, mundo! do console usando as ferramentas fornecidas pelo SDK do Android. Este é um passo muito importante para entender como os sistemas de construção funcionam, porque todos eles contam com esses utilitários em um grau ou outro. Como o projeto está escrito no Kotlin, precisamos do seu compilador para a linha de comando. É fácil fazer o download separadamente.

A montagem do aplicativo pode ser dividida nas seguintes etapas:

  • Baixe e descompacte todas as bibliotecas das quais o projeto depende. No meu caso, essa é a biblioteca de compatibilidade com versões anteriores do appcompat, que, por sua vez, depende do appcompat-core, por isso também desenvolvemos;
  • gerar R.java. Essa classe maravilhosa contém os identificadores de todos os recursos no aplicativo e é usada para acessá-los no código;
  • compilamos as fontes em bytecode e as traduzimos em Dex, porque a máquina virtual Android não sabe como trabalhar com o bytecode usual;
  • empacotamos tudo no APK, mas primeiro alinhamos todos os recursos incompressíveis, como imagens, em relação ao início do arquivo. Isso permite que o preço de um aumento completamente insignificante no tamanho do APK acelere significativamente seu trabalho. Assim, o sistema pode mapear diretamente os recursos para a RAM usando a função mmap ().
  • assine o aplicativo. Este procedimento protege a integridade do APK e confirma a autoria. E graças a isso, por exemplo, o Play Market pode verificar se o aplicativo foi criado por você.

script de construção
function preparedir() { rm -r -f $1 mkdir $1 } PROJ="src/main" LIBS="libs" LIBS_OUT_DIR="$LIBS/out" BUILD_TOOLS="$ANDROID_HOME/build-tools/28.0.3" ANDROID_JAR="$ANDROID_HOME/platforms/android-28/android.jar" DEBUG_KEYSTORE="$(echo ~)/.android/debug.keystore" GEN_DIR="build/generated" KOTLIN_OUT_DIR="$GEN_DIR/kotlin" DEX_OUT_DIR="$GEN_DIR/dex" OUT_DIR="out" libs_res="" libs_classes="" preparedir $LIBS_OUT_DIR aars=$(ls -p $LIBS | grep -v /) for filename in $aars; do DESTINATION=$LIBS_OUT_DIR/${filename%.*} echo "unpacking $filename into $DESTINATION" unzip -o -q $LIBS/$filename -d $DESTINATION libs_res="$libs_res -S $DESTINATION/res" libs_classes="$libs_classes:$DESTINATION/classes.jar" done preparedir $GEN_DIR $BUILD_TOOLS/aapt package -f -m \ -J $GEN_DIR \ -M $PROJ/AndroidManifest.xml \ -S $PROJ/res \ $libs_res \ -I $ANDROID_JAR --auto-add-overlay preparedir $KOTLIN_OUT_DIR compiledKotlin=$KOTLIN_OUT_DIR/compiled.jar kotlinc $PROJ/java $GEN_DIR -include-runtime \ -cp "$ANDROID_JAR$libs_classes"\ -d $compiledKotlin preparedir $DEX_OUT_DIR dex=$DEX_OUT_DIR/classes.dex $BUILD_TOOLS/dx --dex --output=$dex $compiledKotlin preparedir $OUT_DIR unaligned_apk=$OUT_DIR/unaligned.apk $BUILD_TOOLS/aapt package -f -m \ -F $unaligned_apk \ -M $PROJ/AndroidManifest.xml \ -S $PROJ/res \ $libs_res \ -I $ANDROID_JAR --auto-add-overlay cp $dex . $BUILD_TOOLS/aapt add $unaligned_apk classes.dex rm classes.dex aligned_apk=$OUT_DIR/aligned.apk $BUILD_TOOLS/zipalign -f 4 $unaligned_apk $aligned_apk $BUILD_TOOLS/apksigner sign --ks $DEBUG_KEYSTORE $aligned_apk 


De acordo com as figuras, verifica-se que uma montagem limpa leva 7 segundos, e a montagem incremental não fica para trás, porque não armazenamos nada em cache e reconstruímos tudo sempre.

Maven


Foi desenvolvido pelo pessoal da Apache Software Foundation para criar projetos Java. As configurações de compilação são descritas em XML. As primeiras revisões do Maven foram coletadas pelo Ant e agora elas mudaram para a versão estável mais recente.

Prós do Maven:

  • Ele suporta artefatos de montagem de cache, ou seja, a construção incremental deve ser mais rápida que limpa;
  • capaz de resolver dependências de terceiros. I.e. Quando você especifica uma dependência em uma biblioteca de terceiros na configuração do Maven ou Gradle, não precisa se preocupar com o que depende;
  • Existe um monte de documentação detalhada, porque ela está no mercado há algum tempo.
  • e pode ser um mecanismo de criação familiar se você veio recentemente ao mundo do desenvolvimento do Android a partir do back-end.

Contras de Maven:

  • Depende da versão do Java instalada na máquina na qual a montagem ocorre;
  • O plug-in Android agora é suportado por desenvolvedores de terceiros: eu pessoalmente considero isso uma desvantagem muito significativa, porque um dia eles podem parar de fazer isso;
  • XML não é muito adequado para descrever configurações de compilação devido à sua redundância e complexidade;
  • bem, e como veremos mais adiante, corre mais devagar que Gradle, pelo menos em um projeto de teste.

Para construir, precisamos criar o pom.xml, que contém uma descrição do nosso projeto. No cabeçalho, indique as informações básicas sobre o artefato coletado, bem como a versão do Kotlin.

build config pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>myapplication</artifactId> <version>1.0.0</version> <packaging>apk</packaging> <name>My Application</name> <properties> <kotlin.version>1.3.41</kotlin.version> </properties> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>com.google.android</groupId> <artifactId>android</artifactId> <version>4.1.1.4</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>process-sources</phase> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.simpligility.maven.plugins</groupId> <artifactId>android-maven-plugin</artifactId> <extensions>true</extensions> <configuration> <sdk> <platform>28</platform> <buildTools>28.0.3</buildTools> </sdk> <failOnNonStandardStructure>false</failOnNonStandardStructure> </configuration> </plugin> </plugins> </build> </project> 


Em termos de números, nem tudo é róseo. Um assembly limpo leva cerca de 12 segundos, enquanto um incremental - 10. Isso significa que o Maven, de alguma forma, reutiliza artefatos de assemblies anteriores ou, na minha opinião, é mais provável que o plug-in para a construção de um projeto Android o impeça de fazer isso

Agora eles estão usando tudo isso, acho que, antes de tudo, os criadores do plugin são os caras da simplicidade. Não foi possível encontrar informações mais confiáveis ​​sobre esse problema.

Bazel


Engenheiros nas entranhas do Google inventaram o Bazel para construir seus projetos e, relativamente recentemente, o transferiram para o código aberto. Para a descrição de build-configs como Skylark ou Starlark como python, ambos os nomes têm um lugar para estar. É montado usando sua própria versão estável mais recente.

Prós de Bazel:

  • suporte para diferentes linguagens de programação. Se você acredita na documentação, ele sabe como coletar projetos para iOs, Android ou mesmo um back-end;
  • Pode armazenar em cache artefatos coletados anteriormente
  • capaz de trabalhar com dependências do Maven;
  • Bazel tem um suporte muito legal, na minha opinião, para projetos distribuídos. Ele pode especificar revisões específicas dos repositórios git como dependências, e ele as descarregará e armazenará em cache durante o processo de compilação. Para oferecer suporte à escalabilidade, o Bazel pode, por exemplo, distribuir vários destinos em servidores de construção baseados na nuvem, o que permite criar rapidamente projetos volumosos.

Contras de Bazel:

  • todo esse encanto é muito difícil de manter, porque as configurações de compilação são muito detalhadas e descrevem a montagem em um nível baixo;
  • entre outras coisas, parece que Bazel está agora se desenvolvendo ativamente. Por esse motivo, alguns exemplos não são coletados e os que são coletados podem usar a funcionalidade desatualizada, marcada como obsoleta;
  • a documentação agora também deixa muito a desejar, especialmente quando comparada com Gradle;
  • em pequenos projetos, o aquecimento e a análise das configurações de construção podem levar mais tempo que o próprio assembly, o que não é bom, na minha opinião.

Conceitualmente, a configuração básica do Bazel consiste em WORKSPACE, onde descrevemos todos os tipos de coisas globais para um projeto, e BUILD, que contém alvos diretamente para montagem.
Vamos descrever o espaço de trabalho. Como temos um projeto Android, a primeira coisa que configuramos é o SDK do Android. Além disso, uma regra para descarregar configurações é importada aqui. Então, como o projeto está escrito em Kotlin, devemos especificar as regras para ele. Aqui fazemos isso, referindo-se a uma revisão específica diretamente do repositório git.

ESPAÇO DE TRABALHO
 android_sdk_repository( name = "androidsdk", api_level = 28, build_tools_version = "28.0.3" ) load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # # KOTLIN RULES # RULES_KOTLIN_VERSION = "990fcc53689c8b58b3229c7f628f843a60cb9f5c" http_archive( name = "io_bazel_rules_kotlin", url = "https://github.com/bazelbuild/rules_kotlin/archive/%s.zip" % RULES_KOTLIN_VERSION, strip_prefix = "rules_kotlin-%s" % RULES_KOTLIN_VERSION ) load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains") kotlin_repositories() kt_register_toolchains() 


Agora vamos começar no BUILD.

Primeiro, importamos a regra para montar o Kotlin e descrevemos o que queremos coletar. No nosso caso, este é um aplicativo Android; portanto, usamos android_binary, onde definimos o manifesto, o SDK mínimo etc. Nosso aplicativo dependerá da fonte, portanto, nós os mencionamos em deps e passamos para o que são e onde encontrá-los. O código também dependerá dos recursos e da biblioteca appcompat. Para recursos, usamos o destino usual para montar fontes do Android, mas apenas atribuímos recursos a ele sem classes java. E descrevemos algumas regras que importam bibliotecas de terceiros. Ele também menciona appcompat_core, do qual depende o appcompat.

CONSTRUIR
 load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library") android_binary( name = "app", custom_package = "com.example.myapplication", manifest = "src/main/AndroidManifest.xml", manifest_values = { "minSdkVersion": "15", }, deps = [ ":lib", ], ) kt_android_library( name = "lib", srcs = glob(["src/main/java/**/*"]), deps = [ ":res", ":appcompat", ], ) android_library( name = "res", resource_files = glob(["src/main/res/**/*"]), manifest = "src/main/AndroidManifest.xml", custom_package = "com.example.myapplication", ) aar_import( name = "appcompat", aar = "libs/appcompat.aar", deps = [ ":appcompat_core", ] ) aar_import( name = "appcompat_core", aar = "libs/core.aar", ) 


Em números para um projeto tão pequeno, tudo parece triste. Mais de meio minuto para uma construção limpa Olá, mundo! muito. O tempo de construção incremental também está longe de ser perfeito.

O Bazel é usado por seus criadores (Google) em alguns de seus projetos, incluindo servidores, assim como Dropbox e Huawei, que coletam aplicativos móveis para eles. E o notório Dagger 2 também está indo para Bazel.

Buck


Foi inventado por desertores do Google para o Facebook. Ele usou o Python para descrever as configurações e depois migrou para o Skylark mencionado hoje. De repente, ele está usando o sistema Ant.

Buck Pros:

  • suporta linguagens de programação diferentes e pode criar Andriod e iOS;
  • Pode armazenar em cache artefatos coletados anteriormente
  • Buck fez sua própria implementação de dex, que funciona mais rápido que o padrão e trava com o daemon do sistema. Portanto, eles economizam tempo na inicialização do dex. Os engenheiros realmente otimizaram muito. Por exemplo, Buck não coleta código que depende da biblioteca se a interface não mudou ao alterar os elementos internos da biblioteca. Da mesma forma para recursos: se os identificadores não foram alterados, ao alterar recursos, o código não é remontado.
  • existe um plugin que pode ocultar Buck por trás da configuração de Gredlovsky. I.e. você obtém algo como um projeto Gradle normal, que é realmente construído através do Buck.

Contras Buck:

  • é tão difícil de manter quanto Bazel. I.e. aqui também é necessário descrever regras de baixo nível que descrevam claramente o processo de montagem;
  • entre outras coisas, Buck não pode resolver as dependências do Maven por conta própria.

Então, o que o assembly configura para Olá, mundo! através do fanfarrão? Aqui, descrevemos um arquivo de configuração, onde indicamos que queremos criar um projeto Android que será assinado com uma chave de depuração. O aplicativo também dependerá da fonte - lib na matriz deps. Em seguida, vem o alvo com configurações de assinatura. Estou usando uma chave de débito que acompanha o SDK do Android. Imediatamente depois de seguir o objetivo, que coletará as fontes de Kotlin para nós. Como o Bazel, depende de recursos e bibliotecas de compatibilidade.

Nós os descrevemos. Há um alvo separado para recursos em Buck, portanto, as bicicletas não são úteis. A seguir, estão as regras para bibliotecas de terceiros baixadas.

CONSTRUIR
 android_binary( name = 'app', manifest = 'src/main/AndroidManifest.xml', manifest_entries = { 'min_sdk_version': 15, }, keystore = ':debug_keystore', deps = [ ':lib', ], ) keystore( name = 'debug_keystore', store = 'debug.keystore', properties = 'debug.keystore.properties', ) android_library( name = 'lib', srcs = glob(['src/main/java/*.kt']), deps = [ ':res', ':compat', ':compat_core', ], language = 'kotlin', ) android_resource( name = 'res', res = "src/main/res", package = 'com.example.myapplication', ) android_prebuilt_aar( name = 'compat', aar = "libs/appcompat.aar", ) android_prebuilt_aar( name = 'compat_core', aar = "libs/core.aar", ) 


Essa coisa toda está indo muito rapidamente. Uma montagem limpa leva um pouco mais de 7 segundos, enquanto uma montagem incremental é completamente invisível em 200 milissegundos. Eu acho que esse é um resultado muito bom.

É isso que o Facebook faz. Além do aplicativo principal, eles coletam o Facebook Messenger para eles. E o Uber, que criou o plugin para o Gradle e o Airbnb com a Lyft.

Conclusões


Agora que falamos sobre cada sistema de compilação, podemos compará-los entre si usando o exemplo Olá, mundo! O conjunto do console agrada com sua estabilidade. O tempo de execução do script no terminal pode ser considerado a referência para a montagem de compilações limpas, porque os custos de terceiros para a análise de scripts são mínimos aqui. Nesse caso, eu chamaria Maven de um estranho óbvio por um aumento extremamente insignificante na montagem incremental. O Bazel analisa as configurações por um longo tempo e inicializa: existe uma idéia de que de alguma forma armazena em cache os resultados da inicialização, porque a construção incremental é executada muito mais rápido que a limpeza. Buck é o líder indiscutível desta coleção. Montagem rápida e incremental muito rápida.



Agora compare os prós e contras. Não incluirei o Maven na comparação, porque claramente perde para a Gradle e quase nunca é usado no mercado. Uno Buck e Bazel, porque eles têm aproximadamente as mesmas vantagens e desvantagens.

Então, sobre Gradle:

  • o primeiro e, na minha opinião, o mais importante é que é simples. Muito simples;
  • pronto para uso, descarrega e descarrega dependências;
  • para ele, existem muitos treinamentos e documentação diferentes;
  • com suporte ativo do Google e da comunidade. Ótima integração com o Android Studio, a atual ferramenta de desenvolvimento principal. E todos os novos recursos para a criação de um aplicativo Android aparecem pela primeira vez em Gradle.

Sobre Buck / Bazel:

  • definitivamente pode ser muito rápido em comparação com o Gradle. Eu acredito que isso é especialmente visível em projetos muito grandes
  • Você pode manter um projeto no qual haverá códigos-fonte para iOS e Android e montá-los com um sistema de compilação. Isso permite que algumas partes do aplicativo se atrapalhem entre plataformas. Por exemplo, é assim que o Chromium está indo;
  • forçado a descrever dependências em detalhes e, assim, literalmente forçar o desenvolvedor a ser multi-modular.

Não se esqueça dos contras.

Gradle paga por sua simplicidade, sendo lento e ineficiente.
Buck / Bazel, pelo contrário, devido à sua velocidade, sofre com a necessidade de descrever o processo de compilação nas configurações com mais detalhes. Bem, desde que apareceram no mercado há relativamente pouco tempo, não há muita documentação e várias dicas.

iFUNNY


Talvez você tenha alguma dúvida de como coletamos o iFunny. Assim como muitos - usando Gradle. E há razões para isso:

  1. Ainda não está claro que tipo de ganho em velocidade de montagem isso nos dará. Uma compilação limpa do iFunny leva quase 3 minutos e é incremental - cerca de um minuto, o que na verdade não é muito longo.
  2. As configurações de compilação Buck ou Bazel são mais difíceis de manter. No caso de Buck, você também precisa monitorar a relevância das bibliotecas conectadas e das bibliotecas das quais elas dependem.
  3. É banal caro transferir um projeto existente de Gradle para Buck / Bazel, especialmente em condições de lucro incompreensível.

Se o seu projeto demorar mais de 45 minutos e houver cerca de 20 pessoas na equipe de desenvolvimento do Android, faz sentido pensar em mudar o sistema de compilação. Se você e seu amigo estão vendo uma startup, use Gradle e abandone esses pensamentos.



Ficarei feliz em discutir as perspectivas de alternativas ao Gradle nos comentários!
Link para o projeto

Source: https://habr.com/ru/post/pt469771/


All Articles