Kaspersky Mobile Talks # 1. Multi-modularitas

Pada akhir Februari, kami meluncurkan format baru untuk pertemuan pengembang Android dari Kaspersky Mobile Talks . Perbedaan utama dari aksi unjuk rasa biasa adalah bahwa alih-alih ratusan pendengar dan presentasi yang indah, pengembang "berpengalaman" berkumpul pada beberapa topik yang berbeda untuk membahas hanya satu topik: bagaimana mereka mengimplementasikan multi-modularitas dalam aplikasi mereka, masalah apa yang mereka hadapi, dan bagaimana mereka menyelesaikannya.



Isi


  1. Latar belakang
  2. Mediator di HeadHunter. Alexander Blinov
  3. Modul Domain Tinkoff Vladimir Kokhanov, Alexander Zhukov
  4. Analisis dampak di Avito. Evgeny Krivobokov, Mikhail Yudin
  5. Seperti di Tinkoff, mereka mengurangi waktu perakitan untuk PR dari empat puluh menit menjadi empat. Vladimir Kokhanov
  6. Tautan yang bermanfaat


Sebelum melanjutkan ke konten langsung pertemuan di kantor Lab Kaspersky, mari kita ingat dari mana mod untuk membagi aplikasi menjadi modul berasal (selanjutnya, modul dipahami sebagai modul Gradle, bukan Belati, kecuali dinyatakan lain).


Topik multi-modularitas telah ada di benak komunitas Android selama bertahun-tahun. Salah satu yang mendasar dapat dianggap sebagai laporan oleh Denis Neklyudov di St. Petersburg "Mobius" tahun lalu. Dia mengusulkan membagi aplikasi monolitik, yang telah lama berhenti menjadi klien tipis, menjadi modul untuk meningkatkan kecepatan membangun.
Tautan ke laporan: Presentasi , Video


Lalu ada laporan oleh Vladimir Tagakov dari Yandex.Maps tentang menghubungkan modul menggunakan Dagger. Dengan demikian, mereka memecahkan masalah mengalokasikan satu komponen kartu untuk digunakan kembali di banyak aplikasi Yandex lainnya.
Tautan ke laporan: Presentasi , Video


Kaspersky Lab juga tidak lepas dari tren: pada bulan September, Evgeni Matsyuk menulis artikel tentang cara menghubungkan modul menggunakan Belati dan pada saat yang sama membangun arsitektur multi-modul secara horizontal, tidak lupa untuk mengikuti prinsip-prinsip Arsitektur Bersih secara vertikal.
Tautan ke artikel


Dan pada musim dingin Mobius ada dua laporan sekaligus. Pertama, Alexander Blinov berbicara tentang multi-modularitas dalam aplikasi HeadHunter menggunakan Tusuk Gigi sebagai DI, dan tepat setelahnya Artem Zinnatulin berbicara tentang rasa sakit dari 800+ modul di Lyft. Sasha mulai berbicara tentang multi-modularitas, sebagai cara untuk meningkatkan arsitektur aplikasi, dan tidak hanya mempercepat perakitan.
Blinov Report: Presentasi , Video
Laporan Zinnatulin: Video


Mengapa saya memulai artikel dengan retrospektif? Pertama, ini akan membantu Anda mempelajari topik dengan lebih baik jika Anda membaca tentang multi-modularitas untuk pertama kalinya. Dan kedua, pidato pertama di pertemuan kami dimulai dengan presentasi mini oleh Alexey Kalaida dari perusahaan Stream, yang menunjukkan bagaimana mereka membagi aplikasi mereka ke dalam modul berdasarkan artikel Zhenya (dan beberapa poin bagi saya mirip dengan pendekatan Vladimir).


Fitur utama dari pendekatan ini mengikat ke UI: setiap modul terhubung sebagai layar terpisah - sebuah fragmen di mana dependensi ditransfer dari modul aplikasi utama, termasuk FragmentManager. Pertama, kolega mencoba menerapkan multi-modularitas melalui injector proxy, yang diusulkan Zhenya dalam artikel tersebut. Tetapi pendekatan ini tampak luar biasa: ada masalah ketika satu fitur bergantung pada yang lain, yang, pada gilirannya, tergantung pada yang ketiga - kami harus menulis injector proxy untuk setiap modul fitur. Pendekatan yang didasarkan pada komponen UI memungkinkan Anda untuk tidak menulis injektor, memungkinkan dependensi pada tingkat ketergantungan fragmen target.


Batasan utama yang dimiliki oleh implementasi ini: fitur harus berupa fragmen (atau tampilan lain); Kehadiran fragmen bersarang, yang mengarah ke pelat ketel besar. Jika suatu fitur mengimplementasikan fitur lain, itu harus ditambahkan ke peta ketergantungan, yang diperiksa oleh Belati ketika mengompilasinya. Ketika ada banyak fitur seperti itu, kesulitan muncul pada saat menghubungkan grafik ketergantungan.



Setelah laporan Alexey, Alexander Blinov naik ke lantai. Menurutnya, implementasi terkait dengan UI akan cocok untuk wadah DI di Flutter. Kemudian diskusi beralih ke diskusi multi-modul di HeadHunter. Tujuan dari pembagian mereka ke dalam modul adalah kemungkinan isolasi fitur dan meningkatkan kecepatan perakitan.


Sebelum membaginya menjadi modul, penting untuk mempersiapkan. Pertama, Anda bisa membuat grafik dependensi - misalnya, menggunakan alat tersebut . Ini akan membantu mengisolasi komponen dengan jumlah dependensi minimum dan menyingkirkan yang tidak perlu (chop). Hanya setelah ini, komponen yang paling sedikit terhubung dapat dipilih menjadi modul.


Alexander mengingat hal-hal utama yang ia bicarakan dengan lebih terperinci di Mobius. Salah satu tugas kompleks yang harus dipertimbangkan arsitektur adalah menggunakan kembali satu modul dari berbagai tempat dalam aplikasi. Dalam contoh dengan aplikasi jam, ini adalah modul resume, yang harus dapat diakses baik ke modul daftar lowongan (VacanciesList), ketika pengguna pergi ke resume yang ia kirimkan untuk lowongan ini, dan ke modul respon negatif (Negosiasi). Untuk lebih jelasnya, saya menggambar ulang gambar yang Sasha gambarkan di flipchart.



Setiap modul berisi dua entitas utama: Dependensi - dependensi yang dibutuhkan modul ini, dan API - metode yang disediakan modul ke modul lainnya. Komunikasi antara modul dilakukan oleh mediator, yang merupakan struktur datar di modul aplikasi utama. Setiap fitur memiliki satu pilihan. Mediator sendiri termasuk dalam MediatorManager tertentu dalam modul aplikasi proyek. Dalam kode, tampilannya seperti ini:


object MediatorManager { val chatMediator: ChatMediator by lazy { ChatMediator() } val someMediator: ... } class TechSupportMediator { fun provideComponent(): SuppportComponent { val deps = object : SuppportComponentDependencies { override fun getInternalChat{ MediatorManager.rootMediator.api.openInternalChat() } } } } class SuppportComponent(val dependencies) { val api: SupportComponentApi = ... init { SupportDI.keeper.installComponent(this) } } interface SuppportComponentDependencies { fun getSmth() fun close() { scopeHolder.destroyCoordinator < -ref count } } 

Alexander berjanji akan segera menerbitkan plug-in untuk membuat modul di Android Studio, yang digunakan untuk menghilangkan copy-paste di perusahaan mereka, serta contoh proyek multi-modul konsol.


Beberapa fakta lagi tentang hasil pemisahan modul aplikasi hh saat ini:


  • ~ 83 modul fitur.
  • Untuk melakukan uji A / B, fitur dapat diganti seluruhnya oleh modul fitur di tingkat mediator.
  • Grafik Pemindaian Gradle menunjukkan bahwa setelah kompilasi modul secara paralel, proses dexing yang agak lama terjadi (dalam hal ini, dua: untuk pencari kerja dan pemberi kerja):


Yang berikut mengambil lantai dari Alexander dan Vladimir dari Tinkoff:
Skema arsitektur multi-modul mereka terlihat seperti ini:


Modul dibagi menjadi dua kategori: modul fitur dan modul domain.
Modul fitur berisi logika bisnis dan fitur UI. Mereka bergantung pada modul domain, tetapi tidak dapat saling bergantung.


Modul domain berisi kode untuk bekerja dengan sumber data, yaitu, beberapa model, DAO (untuk bekerja dengan database), API (untuk bekerja dengan jaringan) dan repositori (menggabungkan karya API dan DAO). Modul-domain, tidak seperti modul-fitur, dapat saling bergantung.


Koneksi antara domain dan modul fitur terjadi sepenuhnya di dalam modul fitur (yaitu, dalam terminologi hh, Dependecies dan dependensi API dari modul Domain sepenuhnya diselesaikan dalam modul fitur yang menggunakannya, tanpa menggunakan entitas tambahan seperti mediator).


Ini diikuti oleh serangkaian pertanyaan, yang saya akan taruh hampir tidak berubah di sini dalam format "tanya-jawab":


- Bagaimana otorisasi dilakukan? Bagaimana Anda menyeretnya ke modul fitur?
- Fitur dengan kami tidak bergantung pada otorisasi, karena hampir semua tindakan aplikasi terjadi di zona resmi.

- Bagaimana cara melacak dan membersihkan komponen yang tidak digunakan?
- Kami memiliki entitas seperti InjectorRefCount (diimplementasikan melalui WeakHashMap), yang ketika menghapus Aktivitas terakhir (atau fragmen) menggunakan komponen ini, menghapusnya.

- Bagaimana mengukur pemindaian dan pembersihan waktu "bersih"? Jika cache diaktifkan, pemindaian yang agak kotor diperoleh.
- Anda dapat menonaktifkan Gradle Cache (org.gradle.caching di gradle.properties).

- Bagaimana menjalankan tes Unit dari semua modul dalam mode debug? Jika Anda menjalankan uji gradle saja, tes dari semua rasa dan buildType ditarik.
(Pertanyaan ini memicu diskusi banyak peserta dalam pertemuan itu.)
- Anda dapat mencoba menjalankan testDebug.
- Kemudian modul yang tidak ada konfigurasi debug tidak akan diperketat. Mulai terlalu banyak atau terlalu sedikit.
- Anda dapat menulis tugas Gradle, yang akan mengesampingkan testDebug untuk modul tersebut, atau membuat konfigurasi debug palsu di modul build.gradle.
- Anda dapat menerapkan pendekatan ini seperti ini:

 withAndroidPlugin(project) { _, applicationExtension -> applicationExtension.testVariants.all { testVariant -> val testVariantSuffix = testVariant.testedVariant.name.capitalize() } } val task = project.tasks.register < SomeTask > ( "doSomeTask", SomeTask::class.java ) { task.dependsOn("${project.path}:taskName$testVariantSuffix") } 



Presentasi improvisasi berikutnya dibuat oleh Evgeny Krivobokov dan Mikhail Yudin dari Avito.
Mereka menggunakan mindmap untuk memvisualisasikan cerita mereka.


Sekarang proyek perusahaan memiliki> 300 modul, dengan 97% basis kode ditulis di Kotlin. Tujuan utama pengelompokan ke dalam modul adalah untuk mempercepat perakitan proyek. Penguraian modul terjadi secara bertahap, dengan bagian kode yang paling tidak tergantung dialokasikan ke modul. Untuk melakukan ini, alat dikembangkan untuk menandai ketergantungan kode sumber dalam grafik untuk analisis dampak ( laporan tentang analisis dampak di Avito ).


Dengan menggunakan alat ini, Anda dapat menandai modul fitur sebagai final sehingga modul lain tidak dapat bergantung padanya. Properti ini akan diperiksa selama analisis dampak dan memberikan penunjukan dependensi dan perjanjian eksplisit dengan tim yang bertanggung jawab atas modul. Berdasarkan grafik yang dikonstruksi, distribusi perubahan juga diperiksa untuk menjalankan tes unit untuk kode yang terpengaruh.


Perusahaan menggunakan repositori mono, tetapi hanya untuk sumber Android. Kode platform lain hidup terpisah.


Gradle digunakan untuk membangun proyek (meskipun rekan kerja sudah memikirkan pembangun seperti Buck atau Bazel yang lebih cocok untuk proyek multi-modul). Mereka sudah mencoba Kotlin DSL, dan kemudian kembali ke Groovy dalam skrip Gradle, karena tidak nyaman untuk mendukung berbagai versi Kotlin di Gradle dan dalam proyek - logika umum dimasukkan ke dalam plugin.


Gradle dapat memparalelkan tugas, cache, dan tidak mengkompilasi ulang dependensi biner jika ABI mereka tidak berubah, yang memastikan perakitan proyek multi-modul yang lebih cepat. Untuk caching yang lebih efisien, Mainfraimer dan beberapa solusi yang ditulis sendiri digunakan:


  • Saat beralih dari cabang ke cabang, Git dapat meninggalkan folder kosong yang memecah caching ( Masalah tingkat # 2463 ). Oleh karena itu, mereka dihapus secara manual menggunakan kait Git.
  • Jika Anda tidak mengontrol lingkungan pada mesin pengembang, maka berbagai versi Android SDK dan parameter lainnya dapat menurunkan caching. Selama pembangunan proyek, skrip membandingkan parameter lingkungan dengan yang diharapkan: jika versi atau parameter yang salah dipasang, versi tersebut akan turun.
  • Analytics sedang mengaktifkan / menonaktifkan parameter dan lingkungan. Ini untuk memonitor dan membantu pengembang.
  • Kesalahan build juga dikirim ke analytics. Masalah yang dikenal dan populer dimasukkan pada halaman khusus dengan solusi.

Semua ini membantu mencapai 15% cache miss pada CI dan 60-80% secara lokal.


Kiat Gradle berikut juga dapat berguna jika sejumlah besar modul muncul di proyek Anda:


  • Menonaktifkan modul melalui flag IDE tidak nyaman, flag ini dapat diatur ulang. Oleh karena itu, modul dinonaktifkan melalui settings.gradle.
  • Di studio 3.3.1 ada kotak centang "Lewati pembuatan sumber di sinkronisasi Gradle jika proyek memiliki lebih dari 1 modul". Secara default tidak aktif, lebih baik untuk mengaktifkannya.
  • Dependensi terdaftar di buildSrc untuk digunakan kembali di semua modul. Pilihan lain adalah Plugins DSL , tetapi Anda tidak dapat memasukkan aplikasi plugin ke file yang terpisah.


Pertemuan kami berakhir dengan Vladimir dari Tinkoff dengan judul clickbait laporan, "Cara Mengurangi Majelis pada PR dari 40 Menit menjadi Empat . " Bahkan, kami berbicara tentang distribusi mulai dari plug-grade: membangun apk, tes, dan analisis statis.


Awalnya, orang-orang di setiap permintaan tarik menjalankan analisis statis, langsung perakitan dan tes. Proses ini memakan waktu 40 menit, dimana hanya Lint dan SonarQube yang mengambil 25 dan hanya turun 7% dari peluncuran.


Dengan demikian, diputuskan untuk menempatkan peluncuran mereka di Ayub terpisah, yang berjalan sesuai jadwal setiap dua jam dan, jika terjadi kesalahan, mengirim pesan ke Slack.


Situasi sebaliknya menggunakan Detect. Itu jatuh hampir terus-menerus, itulah sebabnya itu dimasukkan ke dalam pemeriksaan pra-push pendahuluan.


Jadi, hanya rakitan apk dan pengujian unit yang tetap dalam verifikasi permintaan tarik. Tes mengkompilasi sumber sebelum berjalan, tetapi tidak mengumpulkan sumber daya. Karena penggabungan sumber daya hampir selalu berhasil, perakitan apk itu sendiri juga ditinggalkan.


Akibatnya, hanya peluncuran unit tes yang tetap pada permintaan tarik, yang memungkinkan kami untuk mencapai 4 menit yang ditunjukkan. Build apk dilakukan dengan permintaan tarik merger di dev.



Terlepas dari kenyataan bahwa pertemuan berlangsung hampir 4 jam, kami tidak punya waktu untuk membahas masalah pembakaran pengorganisasian navigasi dalam proyek multi-modul. Mungkin ini adalah topik untuk Pembicaraan Seluler Kaspersky berikutnya. Apalagi para peserta sangat menyukai formatnya. Beri tahu kami apa yang ingin Anda bicarakan dalam survei atau di komentar.


Dan akhirnya, tautan bermanfaat dari obrolan yang sama:


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


All Articles