
Halo, Habr! Di musim panas, saya berbicara di Summer Droid Meetup dengan laporan tentang membangun aplikasi Android. Versi video dapat ditemukan di sini:
habr.com/ru/company/funcorp/blog/462825 . Dan bagi mereka yang suka membaca lebih lanjut, saya hanya menulis artikel ini.
Ini tentang apa itu - aplikasi Android. Kami akan mengumpulkan dengan cara yang berbeda Halo, dunia!: Mulai dari konsol dan lihat apa yang terjadi di bawah tenda sistem pembangunan, lalu kembali ke masa lalu, ingat Maven dan pelajari tentang solusi Bazel dan Buck modern. Dan akhirnya, semua ini sebanding.
Kami memikirkan kemungkinan perubahan dalam sistem perakitan ketika kami memulai proyek baru. Tampaknya bagi kami bahwa ini adalah kesempatan yang baik untuk mencari beberapa alternatif untuk Gradle. Selain itu, lebih mudah untuk melakukan ini di awal daripada menerjemahkan proyek yang ada. Kelemahan Gradle berikut mendorong kami ke langkah ini:
- dia jelas memiliki masalah dengan perakitan tambahan, meskipun dia bisa melihat kemajuan ke arah ini;
- dia mengerjakan proyek monolitik yang sangat besar dengan buruk;
- itu terjadi bahwa iblis mulai untuk waktu yang sangat lama;
- menuntut mesin yang menjalankannya.
APK
Pertama-tama, ingat apa yang terdiri dari aplikasi Android: kode terkompilasi, sumber daya dan AndroidManifest.xml.

Sumber ada dalam file classes.dex (mungkin ada beberapa file, tergantung pada ukuran aplikasi) dalam format dex khusus yang dapat digunakan oleh mesin virtual Android. Hari ini adalah ART, pada perangkat yang lebih lama - Dalvik. Selain itu, Anda dapat menemukan folder lib, di mana sumber asli diatur dalam subfolder. Mereka akan dinamai tergantung pada arsitektur prosesor target, misalnya x86, arm, dll. Jika Anda menggunakan exoplayer, maka Anda mungkin memiliki lib. Dan folder aidl, yang berisi antarmuka komunikasi antarproses. Mereka akan berguna jika Anda perlu mengakses layanan yang berjalan dalam proses lain. Antarmuka semacam itu digunakan baik di Android itu sendiri maupun di dalam GooglePlayServices.
Berbagai sumber daya yang tidak dikompilasi seperti gambar ada di folder res. Semua sumber daya yang dikompilasi, seperti gaya, baris, dll., Digabungkan menjadi file resource.arsc. Di folder aset, sebagai aturan, mereka meletakkan segala sesuatu yang tidak sesuai dengan sumber daya, misalnya, font khusus.
Selain semua ini, APK berisi AndroidManifest.xml. Di dalamnya, kami menjelaskan berbagai komponen aplikasi, seperti Aktivitas, Layanan, berbagai izin, dll. Itu terletak dalam bentuk biner, dan untuk melihat ke dalam, itu pertama-tama harus dikonversi menjadi file yang dapat dibaca manusia.
CONSOLE
Sekarang kita tahu apa yang terdiri dari aplikasi, kita dapat mencoba membangun Hello, world! dari konsol menggunakan alat yang disediakan SDK Android. Ini adalah langkah yang cukup penting dalam memahami cara kerja sistem membangun, karena mereka semua bergantung pada utilitas ini pada tingkat tertentu. Karena proyek ini ditulis dalam Kotlin, kita memerlukan kompilernya untuk baris perintah. Mudah mengunduh secara terpisah.
Perakitan aplikasi dapat dibagi menjadi langkah-langkah berikut:
- Unduh dan buka semua pustaka tempat proyek ini bergantung. Dalam kasus saya, ini adalah perpustakaan kompatibilitas mundur appcompat, yang, pada gilirannya, tergantung pada appcompat-core, jadi kami juga memompakannya;
- menghasilkan R.java. Kelas yang luar biasa ini berisi pengidentifikasi semua sumber daya dalam aplikasi dan digunakan untuk mengaksesnya dalam kode;
- kami mengkompilasi sumber menjadi bytecode dan menerjemahkannya ke Dex, karena mesin virtual Android tidak tahu cara bekerja dengan bytecode yang biasa;
- kami mengemas semua yang ada di APK, tetapi pertama-tama luruskan semua sumber daya yang tidak dapat dimampatkan, seperti gambar, relatif terhadap awal file. Ini memungkinkan harga peningkatan yang sama sekali tidak signifikan dalam ukuran APK untuk mempercepat pekerjaannya secara signifikan. Dengan demikian, sistem dapat secara langsung memetakan sumber daya ke RAM menggunakan fungsi mmap ().
- menandatangani aplikasi. Prosedur ini melindungi integritas APK dan mengkonfirmasi kepengarangan. Dan berkat ini, misalnya, Play Market dapat memverifikasi bahwa aplikasi itu dibuat oleh Anda.
buat skripfunction 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
Menurut angka-angka, ternyata rakitan bersih membutuhkan waktu 7 detik, dan rakitan tambahan tidak tertinggal, karena kami tidak menembolok apa pun dan membangun kembali semuanya setiap waktu.
Maven
Ini dikembangkan oleh orang-orang di Yayasan Perangkat Lunak Apache untuk membangun proyek Java. Buat konfigurasi untuknya dijelaskan dalam XML. Revisi awal Maven dikumpulkan oleh Ant, dan sekarang mereka telah beralih ke rilis stabil terbaru.
Pro Maven:
- Ini mendukung artefak rakitan caching, mis. membangun bertahap harus lebih cepat daripada bersih;
- mampu menyelesaikan dependensi pihak ketiga. Yaitu Saat Anda menentukan ketergantungan pada perpustakaan pihak ketiga di konfigurasi Maven atau Gradle, Anda tidak perlu khawatir tentang apa itu tergantung;
- Ada banyak dokumentasi terperinci, karena sudah ada di pasaran cukup lama.
- dan itu bisa menjadi mekanisme pembangunan yang akrab jika Anda baru saja datang ke dunia pengembangan Android dari backend.
Kekurangan Maven:
- Tergantung pada versi Java yang diinstal pada mesin tempat perakitan berlangsung;
- Plugin Android sekarang didukung oleh pengembang pihak ketiga: Saya pribadi menganggap ini kelemahan yang sangat signifikan, karena suatu hari mereka mungkin berhenti melakukan ini;
- XML tidak terlalu cocok untuk mendeskripsikan konfigurasi build karena redundansi dan ketidaknyamanannya;
- baik, dan seperti yang akan kita lihat nanti, itu berjalan lebih lambat dari Gradle, setidaknya pada proyek uji.
Untuk membangun, kita perlu membuat pom.xml, yang berisi deskripsi proyek kami. Di tajuk menunjukkan informasi dasar tentang artefak yang dikumpulkan, serta versi Kotlin.
membangun 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>
Dari segi angka, semuanya tidak terlalu cerah. Perakitan yang bersih membutuhkan waktu sekitar 12 detik, sementara yang tambahan - 10. Ini berarti bahwa Maven entah bagaimana menggunakan kembali artefak dari rakitan sebelumnya, atau, menurut pendapat saya, kemungkinan plug-in untuk membangun proyek Android mencegahnya melakukan hal ini.
Sekarang mereka menggunakan semua ini, saya pikir, pertama-tama, pembuat plugin adalah orang-orang dari kesederhanaan. Informasi yang lebih dapat dipercaya tentang masalah ini tidak dapat ditemukan.
Bazel
Para insinyur di perut Google menciptakan Bazel untuk membangun proyek mereka dan baru-baru ini memindahkannya ke open source. Untuk deskripsi build-configs seperti Python-Skylark atau Starlark digunakan, kedua nama memiliki tempat untuk menjadi. Itu dirakit menggunakan rilis stabil terbaru sendiri.
Pro Bazel:
- dukungan untuk berbagai bahasa pemrograman. Jika Anda meyakini dokumentasinya, maka ia tahu cara mengumpulkan proyek untuk iOs, Android, atau bahkan backend;
- Dapat men-cache artefak yang sebelumnya dikumpulkan
- mampu bekerja dengan dependensi Maven;
- Bazel, menurut pendapat saya, sangat mendukung untuk proyek-proyek yang didistribusikan. Dia dapat menentukan revisi spesifik dari repositori git sebagai dependensi, dan dia akan menurunkannya dan menyimpannya selama proses build. Untuk mendukung skalabilitas, Bazel dapat, misalnya, mendistribusikan berbagai target pada server build berbasis cloud, yang memungkinkan Anda untuk dengan cepat membangun proyek besar.
Kekurangan Bazel:
- semua pesona ini sangat sulit dipertahankan, karena konfigurasi pembuatannya sangat rinci dan menggambarkan perakitan pada tingkat rendah;
- antara lain, tampaknya Bazel sekarang aktif berkembang. Karena itu, beberapa contoh tidak dikumpulkan, dan yang dikumpulkan dapat menggunakan fungsi yang sudah usang, yang ditandai sebagai usang;
- dokumentasi sekarang juga menyisakan banyak yang diinginkan, terutama bila dibandingkan dengan Gradle;
- pada proyek kecil, pemanasan dan analisis build-config mungkin memakan waktu lebih lama daripada perakitan itu sendiri, yang menurut saya tidak bagus.
Secara konseptual, konfigurasi Bazel dasar terdiri dari WORKSPACE, di mana kami menggambarkan segala macam hal global untuk suatu proyek, dan BUILD, yang berisi target langsung untuk perakitan.
Mari kita jelaskan WORKSPACE. Karena kami memiliki proyek Android, hal pertama yang kami konfigurasi adalah Android SDK. Juga, aturan untuk membongkar konfigurasi diimpor di sini. Kemudian, karena proyek ditulis dalam Kotlin, kita harus menentukan aturan untuk itu. Di sini kita melakukan ini, merujuk pada revisi spesifik langsung dari repositori git.
WORKSPACE 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")
Sekarang mari kita mulai pada BUILD.
Pertama kita mengimpor aturan untuk merakit Kotlin dan menjelaskan apa yang ingin kita kumpulkan. Dalam kasus kami, ini adalah aplikasi Android, jadi kami menggunakan android_binary, tempat kami mengatur manifes, SDK minimum, dll. Aplikasi kita akan bergantung pada sumbernya, jadi kami menyebutkannya dalam deps dan beralih ke apa mereka dan di mana menemukannya. Kode juga akan bergantung pada sumber daya dan pustaka appcompat. Untuk sumber daya, kami menggunakan target yang biasa untuk mengumpulkan sumber-sumber android, tetapi kami hanya menetapkan sumber daya untuk itu tanpa kelas java. Dan kami menjelaskan beberapa aturan yang mengimpor perpustakaan pihak ketiga. Itu juga menyebutkan appcompat_core, yang bergantung pada appcompat.
BANGUN 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", )
Dalam jumlah untuk proyek sekecil itu, semuanya terlihat menyedihkan. Lebih dari setengah menit menuju bangunan yang bersih Halo, dunia! - banyak. Waktu bangun tambahan juga jauh dari sempurna.
Bazel digunakan oleh penciptanya (Google) untuk beberapa proyek mereka, termasuk yang server, serta Dropbox dan Huawei, yang mengumpulkan aplikasi seluler untuk mereka. Dan Belati 2 yang terkenal juga akan pergi ke Bazel.
Buck
Itu ditemukan oleh pembelot dari Google ke Facebook. Dia menggunakan Python untuk menggambarkan konfigurasi, dan kemudian bermigrasi ke Skylark yang disebutkan hari ini. Dia pergi, tiba-tiba, menggunakan sistem Semut.
Buck Pros:
- mendukung berbagai bahasa pemrograman dan dapat membangun Android dan iOS;
- Dapat men-cache artefak yang sebelumnya dikumpulkan
- Buck membuat implementasi dex mereka sendiri, yang bekerja lebih cepat daripada yang standar dan hang dengan daemon sistem. Jadi mereka menghemat waktu pada inisialisasi dex. Insinyur telah benar-benar dioptimalkan. Misalnya, Buck tidak mengumpulkan kode yang bergantung pada pustaka jika antarmuka tidak berubah saat mengubah pustaka internal. Demikian pula untuk sumber daya: jika pengidentifikasi tidak berubah, maka saat mengubah sumber daya, kode tidak dipasang kembali.
- ada sebuah plugin yang dapat menyembunyikan Buck di belakang konfigurasi Gredlovsky. Yaitu Anda mendapatkan sesuatu seperti proyek Gradle normal, yang sebenarnya dibangun melalui Buck.
Cons Buck:
- sulit dipertahankan seperti Bazel. Yaitu di sini juga perlu untuk menggambarkan aturan tingkat rendah yang dengan jelas menggambarkan proses perakitan;
- antara lain, Buck tidak dapat menyelesaikan ketergantungan Maven sendiri.
Jadi, apa konfigurasi konfigurasi untuk Hello, world! melalui uang? Di sini kami menjelaskan satu file konfigurasi, di mana kami mengindikasikan bahwa kami ingin membangun proyek Android yang akan ditandatangani dengan kunci debug. Aplikasi juga akan tergantung pada source - lib di array deps. Berikutnya adalah target dengan pengaturan tanda tangan. Saya menggunakan kunci debit yang disertakan dengan Android SDK. Segera setelah itu mengikuti target, yang akan mengumpulkan sumber-sumber Kotlin untuk kita. Seperti Bazel, itu tergantung pada sumber daya dan kompatibilitas perpustakaan.
Kami menggambarkannya. Ada target terpisah untuk sumber daya di Buck, jadi sepeda tidak berguna. Berikut adalah aturan untuk perpustakaan pihak ketiga yang diunduh.
BANGUN 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", )
Semua ini berjalan sangat cepat. Rakitan bersih membutuhkan waktu lebih dari 7 detik, sementara rakitan tambahan benar-benar tidak terlihat 200 milidetik. Saya pikir ini adalah hasil yang sangat bagus.
Inilah yang dilakukan Facebook. Selain aplikasi utama mereka, mereka mengumpulkan Facebook Messenger untuk mereka. Dan Uber, yang membuat plugin untuk Gradle dan Airbnb dengan Lyft.
Kesimpulan
Sekarang kita telah berbicara tentang masing-masing sistem pembangunan, kita dapat membandingkan satu sama lain menggunakan contoh Halo, dunia! Perakitan konsol menyenangkan dengan stabilitasnya. Waktu eksekusi skrip dari terminal dapat dianggap sebagai referensi untuk perakitan build bersih, karena biaya pihak ketiga untuk skrip parsing minimal di sini. Dalam hal ini, saya akan menyebut Maven orang luar yang jelas untuk peningkatan yang sangat tidak signifikan dalam perakitan tambahan. Bazel mem-parsing konfigurasi untuk waktu yang sangat lama dan menginisialisasi: ada ide bahwa entah bagaimana cache hasil inisialisasi, karena penambahan incremental itu berjalan jauh lebih cepat daripada bersih. Buck adalah pemimpin koleksi ini. Sangat cepat perakitan bersih dan tambahan.

Sekarang bandingkan pro dan kontra. Saya tidak akan memasukkan Maven dalam perbandingan, karena jelas kehilangan ke Gradle dan hampir tidak pernah digunakan di pasar. Saya menyatukan Buck dan Bazel, karena mereka memiliki kelebihan dan kekurangan yang kira-kira sama.
Jadi, tentang Gradle:
- yang pertama dan, menurut saya, yang paling penting adalah sederhana. Sangat sederhana;
- di luar kotak, bongkar dan keluarkan dependensi;
- baginya ada banyak pelatihan dan dokumentasi yang berbeda;
- didukung secara aktif oleh Google dan komunitas. Integrasi hebat dengan Android Studio, alat pengembangan andalan saat ini. Dan semua fitur baru untuk membangun aplikasi Android pertama kali muncul di Gradle.
Tentang Buck / Bazel:
- pasti bisa sangat cepat dibandingkan dengan Gradle. Saya percaya bahwa ini terutama terlihat pada proyek yang sangat besar
- Anda dapat menyimpan satu proyek di mana akan ada kode sumber iOS dan Android, dan kumpulkan dengan satu sistem build. Hal ini memungkinkan beberapa bagian aplikasi meraba-raba antar platform. Misalnya, ini adalah bagaimana Chromium berjalan;
- dipaksa untuk menjelaskan dependensi secara terperinci dan dengan demikian secara harfiah memaksa pengembang untuk menjadi multi-modular.
Jangan lupa tentang kontra.
Gradle membayar kesederhanaannya dengan menjadi lambat dan tidak efisien.
Buck / Bazel, sebaliknya, karena kecepatannya, menderita dari kebutuhan untuk menggambarkan proses pembangunan di konfigurasi secara lebih rinci. Yah, karena mereka muncul di pasar relatif baru-baru ini, tidak ada banyak dokumentasi dan berbagai lembar contekan.
iFUNNY
Mungkin Anda memiliki pertanyaan bagaimana kami mengumpulkan iFunny. Sama seperti banyak - menggunakan Gradle. Dan ada alasan untuk ini:
- Belum jelas apa keuntungan dalam kecepatan perakitan ini akan memberi kita. Membangun iFunny yang bersih membutuhkan waktu hampir 3 menit, dan tambahan - sekitar satu menit, yang sebenarnya tidak terlalu lama.
- Konfigurasi buck atau Bazel build lebih sulit dipertahankan. Dalam kasus Buck, Anda juga perlu memantau relevansi perpustakaan yang terhubung dan perpustakaan tempat mereka bergantung.
- Adalah hal biasa untuk mentransfer proyek yang ada dari Gradle ke Buck / Bazel, terutama dalam kondisi laba yang tidak dapat dipahami.
Jika proyek Anda akan memakan waktu lebih dari 45 menit dan ada sekitar 20 orang di tim pengembangan Android, maka masuk akal untuk berpikir tentang mengubah sistem pembangunan. Jika Anda dan teman Anda melihat startup, maka gunakan Gradle dan lepaskan pemikiran ini.

Saya akan dengan senang hati membahas prospek alternatif untuk Gradle di komentar!
Tautan ke proyek