Créez une application Android. Tâche avec un astérisque



Bonjour, Habr! En été, j'ai parlé au Meetup Summer Droid avec un rapport sur la construction d'une application Android. La version vidéo peut être trouvée ici: habr.com/ru/company/funcorp/blog/462825 . Et pour ceux qui aiment en savoir plus, je viens d'écrire cet article.

Il s'agit de ce que c'est - une application Android. Nous allons nous assembler de différentes manières Bonjour tout le monde!: Commencez à partir de la console et voyez ce qui se passe sous le capot des systèmes de construction, puis retournez dans le passé, souvenez-vous de Maven et découvrez les solutions Bazel et Buck modernes. Et enfin, tout cela est comparable.

Nous avons pensé à un éventuel changement dans le système d'assemblage lorsque nous avons commencé un nouveau projet. Il nous a semblé que c'était une bonne occasion de chercher des alternatives à Gradle. De plus, il est plus facile de le faire au début que de traduire un projet existant. Les défauts de Gradle suivants nous ont poussés à cette étape:

  • il a certainement des problèmes avec l'assemblage incrémental, bien qu'il puisse voir des progrès dans cette direction;
  • il fait mal avec de très grands projets monolithiques;
  • il arrive qu'un démon commence depuis très longtemps;
  • exigeant sur la machine sur laquelle il fonctionne.

APK


Tout d'abord, rappelez-vous en quoi consiste l'application Android: code compilé, ressources et AndroidManifest.xml.



Les sources se trouvent dans le fichier classes.dex (il peut y avoir plusieurs fichiers, selon la taille de l'application) dans un format dex spécial avec lequel la machine virtuelle Android peut fonctionner. Aujourd'hui, c'est ART, sur des appareils plus anciens - Dalvik. De plus, vous pouvez trouver le dossier lib, où les sources natives sont organisées en sous-dossiers. Ils seront nommés en fonction de l'architecture du processeur cible, par exemple x86, arm, etc. Si vous utilisez exoplayer, vous avez probablement lib. Et le dossier aidl, qui contient des interfaces de communication interprocessus. Ils vous seront utiles si vous devez accéder à un service exécuté dans un autre processus. Ces interfaces sont utilisées à la fois dans Android lui-même et dans GooglePlayServices.

Diverses ressources non compilées comme des images se trouvent dans le dossier res. Toutes les ressources compilées, telles que les styles, les lignes, etc., sont fusionnées dans un fichier resource.arsc. Dans le dossier des ressources, en règle générale, ils mettent tout ce qui ne tient pas dans les ressources, par exemple, les polices personnalisées.

En plus de tout cela, l'APK contient AndroidManifest.xml. Nous y décrivons divers composants de l'application, tels que l'activité, le service, différentes autorisations, etc. Il se trouve sous forme binaire, et pour regarder à l'intérieur, il devra d'abord être converti en un fichier lisible par l'homme.

CONSOLE


Maintenant que nous savons en quoi consiste l'application, nous pouvons essayer de construire Hello, world! à partir de la console à l'aide des outils fournis par le SDK Android. Il s'agit d'une étape assez importante pour comprendre le fonctionnement des systèmes de génération, car ils dépendent tous de ces utilitaires à un degré ou à un autre. Puisque le projet est écrit en Kotlin, nous avons besoin de son compilateur pour la ligne de commande. Il est facile de télécharger séparément.

L'assemblage de l'application peut être divisé en plusieurs étapes:

  • Téléchargez et décompressez toutes les bibliothèques dont dépend le projet. Dans mon cas, il s'agit de la bibliothèque de compatibilité descendante appcompat, qui, à son tour, dépend du noyau appcompat, nous la pompons donc également;
  • générer R.java. Cette merveilleuse classe contient les identifiants de toutes les ressources de l'application et est utilisée pour y accéder en code;
  • nous compilons les sources en bytecode et les traduisons en Dex, car la machine virtuelle Android ne sait pas comment travailler avec le bytecode habituel;
  • nous emballons tout dans l'APK, mais alignons d'abord toutes les ressources incompressibles, telles que les images, par rapport au début du fichier. Cela permet au prix d'une augmentation complètement insignifiante de la taille de l'APK d'accélérer considérablement son travail. Ainsi, le système peut directement mapper les ressources à la RAM en utilisant la fonction mmap ().
  • signer la demande. Cette procédure protège l'intégrité de l'APK et confirme la paternité. Et grâce à cela, par exemple, le Play Market peut vérifier que l'application a été créée par vous.

script de construction
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 


Selon les chiffres, il s'avère qu'un assemblage propre prend 7 secondes, et l'assemblage incrémentiel n'est pas en retard, car nous ne mettons rien en cache et nous reconstruisons tout à chaque fois.

Maven


Il a été développé par les gars de l'Apache Software Foundation pour construire des projets Java. Les configurations de build pour cela sont décrites en XML. Les premières révisions de Maven ont été collectées par Ant, et maintenant elles sont passées à la dernière version stable.

Pour Maven:

  • Il prend en charge la mise en cache des artefacts d'assemblage, c'est-à-dire la construction incrémentielle doit être plus rapide que propre;
  • capable de résoudre les dépendances de tiers. C'est-à-dire Lorsque vous spécifiez une dépendance sur une bibliothèque tierce dans la configuration Maven ou Gradle, vous n'avez pas à vous soucier de ce dont elle dépend;
  • Il y a beaucoup de documentation détaillée, car elle est sur le marché depuis un certain temps.
  • et cela peut être un mécanisme de construction familier si vous êtes récemment entré dans le monde du développement Android depuis le backend.

Inconvénients de Maven:

  • Dépend de la version de Java installée sur la machine sur laquelle l'assemblage a lieu;
  • Le plugin Android est désormais pris en charge par des développeurs tiers: personnellement, je considère cela comme un inconvénient très important, car un jour ils pourraient cesser de le faire;
  • XML n'est pas très approprié pour décrire les configurations de construction en raison de sa redondance et de sa lourdeur;
  • bien, et comme nous le verrons plus tard, il fonctionne plus lentement que Gradle, au moins sur un projet de test.

Pour construire, nous devons créer pom.xml, qui contient une description de notre projet. Dans l'en-tête, indiquez les informations de base sur l'artefact collecté, ainsi que la version de 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> 


En termes de chiffres, tout n'est pas trop rose. Un assemblage propre prend environ 12 secondes, tandis qu'un incrémentiel - 10. Cela signifie que Maven réutilise en quelque sorte les artefacts des assemblages précédents, ou, à mon avis, il est plus probable que le plug-in pour la construction d'un projet Android l'empêche de le faire

Maintenant, ils utilisent tout cela, je pense, tout d'abord, les créateurs du plugin sont les gars de la simplicité. Des informations plus fiables sur ce problème sont introuvables.

Bazel


Les ingénieurs dans les entrailles de Google ont inventé Bazel pour construire leurs projets et l'ont récemment transféré en open source. Pour la description des build-configs Skylark ou Starlark de type python, les deux noms ont leur place. Il est assemblé à l'aide de sa dernière version stable.

Avantages de Bazel:

  • prise en charge de différents langages de programmation. Si vous croyez à la documentation, il sait comment collecter des projets pour iOs, Android ou même un backend;
  • Peut mettre en cache les artefacts précédemment collectés
  • capable de travailler avec les dépendances Maven;
  • Bazel a, à mon avis, un support très cool pour les projets distribués. Il peut spécifier des révisions spécifiques des référentiels git en tant que dépendances, et il les déchargera et les mettra en cache pendant le processus de construction. Pour prendre en charge l'évolutivité, Bazel peut, par exemple, distribuer diverses cibles sur des serveurs de build basés sur le cloud, ce qui vous permet de créer rapidement des projets volumineux.

Inconvénients de Bazel:

  • tout ce charme est très difficile à maintenir, car les configurations de build sont très détaillées et décrivent le montage à un niveau bas;
  • entre autres, il semble que Bazel se développe activement. Pour cette raison, certains exemples ne sont pas collectés et ceux qui sont collectés peuvent utiliser la fonctionnalité obsolète, qui est marquée comme obsolète;
  • la documentation laisse désormais beaucoup à désirer, surtout par rapport à Gradle;
  • sur les petits projets, l'échauffement et l'analyse des build-configs peuvent prendre plus de temps que l'assemblage lui-même, ce qui n'est pas bon, à mon avis.

Conceptuellement, la configuration de base de Bazel se compose de WORKSPACE, où nous décrivons toutes sortes de choses globales pour un projet, et BUILD, qui contient directement des cibles pour l'assemblage.
Décrivons WORKSPACE. Puisque nous avons un projet Android, la première chose que nous configurons est le SDK Android. De plus, une règle de déchargement des configurations est importée ici. Ensuite, puisque le projet est écrit en Kotlin, il faut en préciser les règles. Ici, nous faisons cela, en faisant référence à une révision spécifique directement à partir du référentiel git.

ESPACE DE TRAVAIL
 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() 


Commençons maintenant sur la CONSTRUCTION.

Nous importons d'abord la règle d'assemblage de Kotlin et décrivons ce que nous voulons collecter. Dans notre cas, il s'agit d'une application Android, nous utilisons donc android_binary, où nous définissons le manifeste, le SDK minimum, etc. Notre application dépendra de la source, nous les mentionnons donc dans les dépôts et passons à ce qu'ils sont et où les trouver. Le code dépendra également des ressources et de la bibliothèque compatible avec l'application. Pour les ressources, nous utilisons la cible habituelle pour assembler les sources Android, mais nous ne lui attribuons que des ressources sans classes java. Et nous décrivons quelques règles qui importent des bibliothèques tierces. Il mentionne également appcompat_core, dont dépend appcompat.

CONSTRUIRE
 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", ) 


En chiffres pour un si petit projet, tout semble triste. Plus d'une demi-minute pour une construction propre Bonjour tout le monde! - beaucoup. Le temps de construction incrémentiel est également loin d'être parfait.

Bazel est utilisé par ses créateurs (Google) pour certains de leurs projets, y compris ceux de serveur, ainsi que Dropbox et Huawei, qui collectent des applications mobiles pour eux. Et le fameux Dagger 2 va également à Bazel.

Buck


Il a été inventé par des transfuges de Google vers Facebook. Il a utilisé Python pour décrire les configurations, puis a migré vers le Skylark mentionné aujourd'hui. Il va, tout d'un coup, utiliser le système Ant.

Buck Pros:

  • prend en charge différents langages de programmation et peut créer à la fois Andriod et iOS;
  • Peut mettre en cache les artefacts précédemment collectés
  • Buck a créé sa propre implémentation dex, qui fonctionne plus rapidement que celle standard et se bloque avec le démon système. Ils économisent donc du temps sur l'initialisation dex. Les ingénieurs ont vraiment beaucoup optimisé. Par exemple, Buck ne collecte pas le code qui dépend de la bibliothèque si l'interface n'a pas changé lors du changement des internes de la bibliothèque. De même pour les ressources: si les identifiants n'ont pas changé, alors lors du changement de ressources, le code n'est pas remonté.
  • il y a un plugin qui peut cacher Buck derrière la configuration de Gredlovsky. C'est-à-dire vous obtenez quelque chose comme un projet Gradle normal, qui est en fait construit par Buck.

Contre Buck:

  • il est aussi difficile à entretenir que Bazel. C'est-à-dire ici, il est également nécessaire de décrire des règles de bas niveau qui décrivent clairement le processus d'assemblage;
  • Entre autres choses, Buck ne peut pas résoudre seul les dépendances Maven.

Alors, qu'est-ce que la configuration d'assemblage pour Hello, world! à travers buck? Nous décrivons ici un fichier de configuration, où nous indiquons que nous voulons construire un projet Android qui sera signé avec une clé de débogage. L'application dépendra également de la source-lib dans le tableau deps. Vient ensuite la cible avec les paramètres de signature. J'utilise une clé de débit fournie avec le SDK Android. Immédiatement après, il suit l'objectif, qui collectera les sources de Kotlin pour nous. Comme Bazel, cela dépend des ressources et des bibliothèques de compatibilité.

Nous les décrivons. Il y a un objectif distinct pour les ressources dans Buck, donc les vélos ne sont pas utiles. Voici les règles pour les bibliothèques tierces téléchargées.

CONSTRUIRE
 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", ) 


Tout cela va très vite. Un assemblage propre prend un peu plus de 7 secondes, tandis qu'un assemblage incrémental est complètement invisible 200 millisecondes. Je pense que c'est un très bon résultat.

C'est ce que fait Facebook. En plus de leur application phare, ils récupèrent Facebook Messenger pour eux. Et Uber, qui a fait le plugin pour Gradle et Airbnb avec Lyft.

Conclusions


Maintenant que nous avons parlé de chaque système de construction, nous pouvons les comparer les uns aux autres en utilisant l'exemple Hello, world! L'ensemble console plaît par sa stabilité. Le temps d'exécution du script à partir du terminal peut être considéré comme la référence pour l'assemblage de builds propres, car les coûts tiers pour l'analyse des scripts sont minimes ici. Dans ce cas, j'appellerais Maven un étranger évident pour une augmentation extrêmement insignifiante de l'assemblage incrémentiel. Bazel analyse les configurations pendant très longtemps et s'initialise: il y a une idée qu'il cache en quelque sorte les résultats de l'initialisation, car la construction incrémentielle qu'il exécute est beaucoup plus rapide que clean. Buck est le leader incontesté de cette collection. Assemblage très rapide, propre et incrémental.



Comparez maintenant les avantages et les inconvénients. Je n'inclurai pas Maven dans la comparaison, car il perd clairement face à Gradle et n'est presque jamais utilisé sur le marché. J'unis Buck et Bazel, car ils ont à peu près les mêmes avantages et inconvénients.

Donc, à propos de Gradle:

  • la première et, à mon avis, la chose la plus importante est que c'est simple. Très simple;
  • prêt à l'emploi, décharge et décharge les dépendances;
  • pour lui, il y a beaucoup de formations et de documentation différentes;
  • activement soutenu par Google et la communauté. Grande intégration avec Android Studio, l'outil de développement phare actuel. Et toutes les nouvelles fonctionnalités pour créer une application Android apparaissent d'abord dans Gradle.

À propos de Buck / Bazel:

  • peut certainement être très rapide par rapport à Gradle. Je pense que cela est particulièrement visible sur les très grands projets
  • Vous pouvez conserver un projet dans lequel il y aura des codes source à la fois iOS et Android, et les assembler avec un système de construction. Cela permet à certaines parties de l'application de tâtonner entre les plates-formes. Par exemple, c'est ainsi que va le chrome;
  • forcé de décrire les dépendances en détail et ainsi obliger littéralement le développeur à être multi-modulaire.

N'oubliez pas les inconvénients.

Gradle paie pour sa simplicité en étant lent et inefficace.
Buck / Bazel, au contraire, en raison de sa vitesse, souffre de la nécessité de décrire plus en détail le processus de construction dans les configurations. Eh bien, étant donné qu'ils sont apparus sur le marché relativement récemment, il n'y a pas beaucoup de documentation et diverses feuilles de triche.

iFUNNY


Vous avez peut-être une question sur la façon dont nous collectons iFunny. Comme beaucoup - en utilisant Gradle. Et il y a des raisons à cela:

  1. On ne sait pas encore quel type de gain en vitesse d'assemblage cela nous donnera. Une version propre d'iFunny prend près de 3 minutes, et incrémentielle - environ une minute, ce qui n'est en fait pas très long.
  2. Les configurations de construction Buck ou Bazel sont plus difficiles à maintenir. Dans le cas de Buck, vous devez également surveiller la pertinence des bibliothèques connectées et des bibliothèques dont elles dépendent.
  3. Il est banalement coûteux de transférer un projet existant de Gradle à Buck / Bazel, surtout dans des conditions de profit incompréhensible.

Si votre projet va prendre plus de 45 minutes et qu'il y a environ 20 personnes dans l'équipe de développement Android, alors il est logique de penser à changer le système de construction. Si vous et votre ami sciez une startup, utilisez Gradle et laissez tomber ces pensées.



Je serai heureux de discuter des perspectives d'alternatives à Gradle dans les commentaires!
Lien vers le projet

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


All Articles