Arsitektur Lapisan Eksekusi Tugas Asinkron

Dalam aplikasi seluler jejaring sosial, pengguna suka, menulis komentar, lalu membalik-balik feed, memulai video, dan meletakkan yang serupa lagi. Semua ini cepat dan hampir bersamaan. Jika implementasi logika bisnis dari aplikasi benar-benar memblokir, maka pengguna tidak akan dapat pergi ke rekaman sampai suka untuk merekam dengan segel diunggah. Tetapi pengguna tidak akan menunggu, oleh karena itu, di sebagian besar aplikasi seluler tugas asinkron berfungsi, yang dimulai dan diselesaikan secara terpisah satu sama lain. Pengguna melakukan beberapa tugas secara bersamaan dan mereka tidak memblokir satu sama lain. Satu tugas tidak sinkron dimulai dan dijalankan sementara pengguna memulai yang berikutnya.



Dalam menguraikan laporan Stepan Goncharov di AppsConf, kita akan menyentuh asynchrony: kita akan mempelajari arsitektur aplikasi mobile, membahas mengapa kita harus memisahkan lapisan untuk melakukan tugas-tugas yang tidak sinkron, kita akan menganalisis persyaratan dan solusi yang ada, kita akan melalui pro dan kontra, dan mempertimbangkan salah satu implementasi dari pendekatan ini. Kami juga belajar bagaimana mengelola tugas yang tidak sinkron, mengapa setiap tugas memiliki ID sendiri, apa saja strategi pelaksanaannya dan bagaimana mereka membantu menyederhanakan dan mempercepat pengembangan seluruh aplikasi.


Tentang pembicara: Stepan Goncharov ( stepango ) bekerja di Grab - seperti Uber, tetapi di Asia Tenggara. Dia telah terlibat dalam pengembangan Android selama lebih dari 9 tahun. Tertarik pada Kotlin sejak 2014, dan sejak 2016 - menggunakannya di prod. Diorganisir oleh Kelompok Pengguna Kotlin di Singapura. Ini adalah salah satu alasan mengapa semua contoh kode ada di Kotlin, dan bukan karena itu modis.

Kami akan melihat satu pendekatan untuk merancang komponen aplikasi Anda. Ini adalah panduan untuk bertindak bagi mereka yang ingin menambahkan komponen baru ke aplikasi, dengan mudah mendesainnya, dan kemudian mengembangkannya. Pengembang iOS dapat menggunakan pendekatan iOS. Pendekatan ini juga berlaku untuk platform lain. Saya telah tertarik dengan Kotlin sejak 2014, jadi semua contoh akan ada dalam bahasa ini. Tapi jangan khawatir - Anda dapat menulis hal yang sama dalam bahasa Swift, Objective-C, dan lainnya.

Mari kita mulai dengan masalah dan kerugian dari Ekstensi Reaktif . Masalah adalah tipikal dari primitif asinkron lainnya, jadi kami katakan RX - ingat masa depan dan janji, dan semuanya akan bekerja sama.

Masalah RX


Ambang entri tinggi . RX cukup rumit dan besar - memiliki 270 operator, dan tidak mudah untuk mengajar seluruh tim untuk menggunakannya dengan benar. Kami tidak akan membahas masalah ini - ini berada di luar cakupan laporan.

Di RX, Anda harus mengelola langganan Anda secara manual, serta memantau siklus hidup aplikasi . Jika Anda telah berlangganan Single atau Observable, Anda tidak dapat membandingkannya dengan SIngle lain , karena Anda akan selalu menerima objek baru dan akan selalu ada langganan yang berbeda untuk runtime. Di RX tidak ada cara untuk membandingkan langganan dan aliran .

Kami akan mencoba menyelesaikan beberapa masalah ini. Kami akan memecahkan setiap masalah satu kali, dan kemudian menggunakan kembali hasilnya.

Masalah nomor 1: melakukan satu tugas lebih dari sekali


Masalah umum dalam pengembangan adalah pekerjaan yang tidak perlu dan pengulangan tugas yang sama lebih dari sekali. Bayangkan kita memiliki formulir untuk memasukkan data dan tombol simpan. Ketika ditekan, permintaan dikirim, tetapi jika Anda mengklik beberapa kali saat formulir disimpan, maka beberapa permintaan yang sama akan dikirim. Kami memberikan tombol untuk menguji QA, mereka menekan 40 kali dalam satu detik - kami menerima 40 permintaan, karena, misalnya, animasi tidak punya waktu untuk bekerja.

Bagaimana cara mengatasi masalah tersebut? Masing-masing pengembang memiliki pendekatan favoritnya sendiri untuk menyelesaikan: satu akan menempel debounce , yang lain akan memblokir tombol untuk berjaga-jaga melalui clickable = false Tidak ada pendekatan umum, sehingga bug ini akan muncul atau menghilang dari aplikasi kita. Kami menyelesaikan masalah hanya ketika QA memberi tahu kami: "Oh, saya klik di sini, dan itu rusak"!

Solusi yang scalable?


Untuk menghindari situasi seperti itu, kami akan membungkus RX atau kerangka kerja asinkron lainnya - kami akan menambahkan ID ke semua operasi asinkron . Idenya sederhana - kita perlu beberapa cara untuk membandingkannya, karena biasanya metode ini tidak ada dalam kerangka kerja. Kami dapat menyelesaikan tugas, tetapi kami tidak tahu apakah itu sudah selesai atau belum.

Sebut pembungkus kami "Act" - nama lain sudah diambil. Untuk melakukan ini, buat typealias kecil dan interface sederhana di mana hanya ada satu bidang:

 typealias Id = String interface Act { val id: Id } 

Ini nyaman dan sedikit mengurangi jumlah kode. Nantinya, jika String tidak menyukainya, kami akan menggantinya dengan yang lain. Dalam potongan kode kecil ini, kami mengamati fakta lucu.

Antarmuka dapat berisi properti.

Untuk programmer yang datang dari Jawa, ini tidak terduga. Biasanya mereka menambahkan metode getId() di dalam antarmuka, tetapi ini adalah solusi yang salah, dari sudut pandang Kotlin.

Bagaimana kita mendesain?


Penyimpangan kecil. Saat mendesain, saya mematuhi dua prinsip. Yang pertama adalah memecah persyaratan komponen dan implementasi menjadi potongan-potongan kecil . Ini memungkinkan kontrol granular atas penulisan kode. Ketika Anda membuat komponen besar dan mencoba melakukan semuanya sekaligus, ini buruk. Biasanya komponen ini tidak berfungsi dan Anda mulai memasukkan kruk, jadi saya mendorong Anda untuk menulis dalam langkah-langkah kecil yang terkontrol dan menikmatinya. Prinsip kedua adalah memeriksa operabilitas setelah setiap langkah dan ulangi prosedur lagi.

Mengapa ID tidak cukup?


Mari kita kembali ke masalahnya. Kami mengambil langkah pertama - kami menambahkan ID, dan semuanya sederhana - antarmuka dan bidang. Ini tidak memberi kami apa pun, karena antarmuka tidak mengandung implementasi apa pun dan tidak berfungsi sendiri, tetapi memungkinkan Anda untuk membandingkan operasi.

Selanjutnya, kami akan menambahkan komponen yang akan memungkinkan kami untuk menggunakan antarmuka dan memahami bahwa kami ingin menjalankan beberapa jenis permintaan untuk kedua kalinya saat ini tidak diperlukan. Hal pertama yang akan kita lakukan adalah memperkenalkan abstraksi baru .

Memperkenalkan Abstraksi Baru: MapDisposable


Penting untuk memilih nama yang tepat dan abstraksi yang familier bagi pengembang yang bekerja di basis kode Anda. Karena saya punya contoh tentang RX, kami akan menggunakan konsep RX dan nama-nama yang mirip dengan yang digunakan oleh pengembang perpustakaan. Jadi kita dapat dengan mudah menjelaskan kepada rekan kerja kita apa yang mereka lakukan, mengapa, dan bagaimana cara kerjanya. Untuk memilih nama, lihat dokumentasi CompositeDiposable .

Mari kita buat antarmuka MapDisposable kecil yang berisi informasi tentang tugas-tugas saat ini dan panggilan buang () pada penghapusan . Saya tidak akan memberikan implementasinya, Anda dapat melihat semua sumber di GitHub saya .

Kami memanggil MapDisposable dengan cara ini karena komponen akan bekerja seperti Peta, tetapi akan memiliki properti CompositeDiposable.

Memperkenalkan Abstraksi Baru: ActExecutor


Komponen abstrak berikutnya adalah ActExecutor. Itu mulai atau tidak memulai tugas baru, tergantung pada MapDisposable dan delegasi penanganan kesalahan. Cara memilih nama - lihat dokumentasi .

Ambil analogi terdekat dari JDK. Ini memiliki Pelaksana di mana Anda dapat melewati utas dan melakukan sesuatu. Menurut saya ini komponen yang keren dan dirancang dengan baik, jadi mari kita ambil sebagai dasarnya.

Kami membuat ActExecutor dan antarmuka yang sederhana untuknya, mengikuti prinsip langkah-langkah kecil yang sederhana. Nama itu sendiri mengatakan bahwa itu adalah komponen yang kami kirimkan sesuatu dan mulai melakukan sesuatu. ActExecutor memiliki satu metode di mana kami melewati Act dan, untuk berjaga-jaga, menangani kesalahan, karena tanpa mereka tidak ada cara.

 interface ActExecutor { fun execute( act: Act, e: (Throwable) -> Unit = ::logError) } interface MapDisposable { fun contains(id: Id): Boolean fun add(id: Id, disposable: () -> T) fun remove(id: Id) } 

MapDisposable juga terbatas: ambil antarmuka Peta dan salin isi, add , dan remove metode dari itu. Metode add berbeda dari Map: argumen kedua adalah lambda untuk keindahan dan kenyamanan. Kemudahannya adalah kita dapat menyinkronkan lambda untuk mencegah kondisi balapan yang tidak terduga. Tetapi kita tidak akan membicarakan hal ini, kita akan melanjutkan tentang arsitektur.

Implementasi Antarmuka


Kami telah mendeklarasikan semua antarmuka dan akan mencoba menerapkan sesuatu yang sederhana. Ambil CompletableAct dan SingleAct .

 class CompletableAct ( override val id: Id, override val completable: Completable ) : Act class SingleAct<T : Any>( override val id: Id, override val single: Single<T> ) : Act 

CompletableAct adalah pembungkus lebih dari Completable. Dalam kasus kami, ini hanya berisi ID - yang kami butuhkan. SingleAct hampir sama. Kita dapat mengimplementasikan Maybe and Flowable juga, tetapi tetap pada dua implementasi pertama.

Untuk Single, kami menentukan tipe Generik <T : Any> . Sebagai pengembang Kotlin, saya lebih suka menggunakan pendekatan seperti itu.

Coba gunakan Non-Null Generics.

Sekarang kami memiliki seperangkat antarmuka, kami menerapkan beberapa logika untuk mencegah eksekusi permintaan yang sama.

 class ActExecutorImpl ( val map: MapDisposable ): ActExecutor { fun execute( act: Act, e: (Throwable) -> Unit ) = when { map.contains(act.id) -> { log("${act.id} - in progress") } else startExecution(act, e) log("${act.id} - Started") } } 

Kami mengambil Peta dan memeriksa apakah ada permintaan di dalamnya. Jika tidak, kami mulai menjalankan permintaan dan menambahkannya ke Peta tepat saat runtime. Setelah eksekusi dengan hasil apa pun: kesalahan atau keberhasilan, hapus permintaan dari Peta.

Untuk sangat penuh perhatian - tidak ada sinkronisasi, tetapi sinkronisasi ada dalam kode sumber di GitHub.

 fun startExecution(act: Act, e: (Throwable) -> Unit) { val removeFromMap = { mapDisposable.remove(act.id) } mapDisposable.add(act.id) { when (act) { is CompletableAct -> act.completable .doFinally(removeFromMap) .subscribe({}, e) is SingleAct<*> -> act.single .doFinally(removeFromMap) .subscribe({}, e) else -> throw IllegalArgumentException() } } 

Gunakan lambdas sebagai argumen terakhir untuk meningkatkan keterbacaan kode. Itu indah dan kolega Anda akan berterima kasih.

Kami akan menggunakan beberapa chip Kotlin lagi dan menambahkan fungsi ekstensi untuk Completable dan Single. Dengan mereka, kita tidak perlu mencari metode pabrik untuk membuat CompletableAct dan SingleAct - kita akan membuatnya melalui fungsi ekstensi.

 fun Completable.toAct(id: Id): Act = CompletableAct(id, this) fun <T: Any> Single<T>.toAct(id: Id): Act = SingleAct(id, this) 

Fungsi ekstensi dapat ditambahkan ke kelas apa pun.

Hasil


Kami telah menerapkan beberapa komponen dan logika yang sangat sederhana. Sekarang aturan utama yang harus kita ikuti adalah tidak memaksa berlangganan dengan tangan . Ketika kami ingin mengeksekusi sesuatu - kami memberikannya melalui Executor. Seperti halnya dengan utas - tidak ada yang memulai sendiri.

 fun act() = Completable.timer(2, SECONDS).toAct("Hello") executor.apply { execute(act()) execute(act()) execute(act()) } Hello - Act Started Hello - Act Duplicate Hello - Act Duplicate Hello - Act Finished 

Kami pernah menyetujui dalam tim, dan sekarang selalu ada jaminan bahwa sumber daya aplikasi kami tidak akan dihabiskan untuk pelaksanaan permintaan yang identik dan tidak perlu.

Masalah pertama terpecahkan. Sekarang mari kita memperluas solusi untuk memberikan fleksibilitas.

Masalah nomor 2: tugas apa yang harus dibatalkan?


Seperti halnya dalam kasus di mana perlu untuk membatalkan permintaan berikutnya , kami mungkin perlu membatalkan yang sebelumnya. Misalnya, kami mengedit informasi tentang pengguna kami untuk pertama kalinya dan mengirimkannya ke server. Untuk beberapa alasan, pengirimannya memakan waktu lama dan tidak selesai. Kami mengedit profil pengguna lagi dan mengirim permintaan yang sama untuk kedua kalinya. Dalam hal ini, tidak masuk akal untuk menghasilkan ID khusus untuk permintaan - informasi dari upaya kedua lebih relevan, dan permintaan sebelumnya dibatalkan .

Solusi saat ini tidak akan berfungsi, karena itu akan selalu membatalkan eksekusi permintaan dengan informasi yang relevan. Kita perlu mengembangkan solusi untuk mengatasi masalah dan menambah fleksibilitas. Untuk melakukan ini, mengerti apa yang kita semua inginkan? Tapi kami ingin memahami tugas apa yang harus dibatalkan, bagaimana tidak menyalin-menempel dan memanggilnya apa.

Tambahkan komponen


Kami menyebut strategi perilaku kueri dan membuat dua antarmuka untuk mereka: StrategyHolder dan Strategy . Kami juga membuat 2 objek yang bertanggung jawab atas strategi yang diterapkan.

 interface StrategyHolder { val strategy: Strategy } sealed class Strategy object KillMe : Strategy() object SaveMe : Strategy() 

Saya tidak menggunakan enum - Saya lebih menyukai kelas yang disegel . Mereka lebih ringan, mengkonsumsi lebih sedikit memori, dan mereka lebih mudah dan lebih nyaman untuk diperluas.

Kelas yang disegel lebih mudah diperluas dan ditulis lebih pendek.

Memperbarui Komponen yang Ada


Pada titik ini, semuanya sederhana. Kami memiliki antarmuka yang sederhana, sekarang akan menjadi pewaris StrategyHolder. Karena ini adalah antarmuka, tidak ada masalah dengan warisan. Dalam implementasi CompletableAct kami akan memasukkan override lain dan menambahkan nilai default di sana untuk memastikan bahwa perubahan akan tetap kompatibel dengan kode yang ada.

 interface Act : StrategyHolder { val id: String } class CompletableAct( override val id: String, override val completable: Completable, override val strategy: Strategy = SaveMe ) : Act 

Strategi


Saya memilih strategi SaveMe , yang tampak jelas bagi saya. Strategi ini hanya membatalkan permintaan berikut - permintaan pertama akan selalu hidup sampai selesai.

Kami bekerja sedikit pada implementasi kami. Kami memiliki metode eksekusi, dan sekarang kami telah menambahkan pemeriksaan strategi di sana.

  • Jika strategi SaveMe sama dengan apa yang kami lakukan sebelumnya, maka tidak ada yang berubah.
  • Jika strateginya adalah KillMe , bunuh permintaan sebelumnya dan luncurkan yang baru.

 override fun execute(act: Act, e: (Throwable) -> Unit) = when { map.contains(act.id) -> when (act.strategy) { KillMe -> { map.remove(act.id) startExecution(act, e) } SaveMe -> log("${act.id} - Act duplicate") } else -> startExecution(act, e) } 

Hasil


Kami dapat dengan mudah mengelola strategi dengan menulis kode minimal. Pada saat yang sama, kolega kami senang, dan kami bisa melakukan sesuatu seperti ini.

 executor.apply { execute(Completable.timer(2, SECONDS) .toAct("Hello", KillMe)) execute(Completable.timer(2, SECONDS) .toAct("Hello", KillMe)) execute(Completable.timer(2, SECONDS) .toAct("Hello«, KillMe)) } Hello - Act Started Hello - Act Canceled Hello - Act Started Hello - Act Canceled Hello - Act Started Hello - Act Finished 

Kami membuat tugas yang tidak sinkron, melewati strategi, dan setiap kali kami memulai tugas baru, semua yang sebelumnya, dan bukan yang berikutnya, akan dibatalkan.

Masalah nomor 3: strategi tidak cukup


Mari kita beralih ke satu masalah menarik yang saya temui di beberapa proyek. Kami akan memperluas solusi kami untuk menangani kasus yang lebih rumit. Salah satu kasus ini, terutama yang relevan untuk jejaring sosial, adalah "suka / tidak suka" . Ada pos dan kami ingin menyukainya, tetapi sebagai pengembang kami tidak ingin memblokir seluruh UI, dan menampilkan dialog di layar penuh dengan memuat sampai permintaan selesai. Ya, dan pengguna tidak akan senang. Kami ingin menipu pengguna: ia menekan tombol dan, seolah-olah hal seperti itu sudah terjadi - animasi yang indah telah dimulai. Tetapi pada kenyataannya, tidak ada yang seperti - kita menunggu sampai penipuan menjadi kenyataan. Untuk mencegah penipuan, kami harus secara transparan menangani ketidaksukaan bagi pengguna.

Akan menyenangkan untuk menangani ini dengan benar sehingga pengguna mendapatkan hasil yang diinginkan. Tetapi sulit bagi kami, sebagai pengembang, untuk menangani permintaan yang berbeda dan saling eksklusif setiap kali.

Ada terlalu banyak pertanyaan. Bagaimana memahami bahwa kueri terkait? Bagaimana cara menyimpan koneksi ini? Bagaimana menangani skrip yang rumit dan tidak menyalin-menempel? Bagaimana memberi nama komponen baru? Tugasnya kompleks, dan apa yang sudah kami implementasikan tidak cocok untuk solusinya.

Grup dan strategi untuk grup


Buat antarmuka sederhana yang disebut GroupStrategyHolder . Ini sedikit lebih rumit - dua bidang, bukan satu.

 interface GroupStrategyHolder { val groupStrategy: GroupStrategy val groupKey: String } sealed class GroupStrategy object Default : GroupStrategy() object KillGroup : GroupStrategy() 

Selain strategi untuk permintaan tertentu, kami memperkenalkan entitas baru - sekelompok permintaan. Grup ini juga akan memiliki strategi. Kami hanya akan mempertimbangkan opsi paling sederhana dengan dua strategi: Default - strategi default ketika kami tidak melakukan apa pun dengan kueri, dan KillGroup - membunuh semua pertanyaan yang ada dari grup dan meluncurkan yang baru.

 interface Act : StrategyHolder, GroupStrategyHolder { val id: String } class CompletableAct( override val id: String, override val completable: Completable, override val strategy: Strategy = SaveMe, override val groupStrategy: GroupStrategy = Default override val groupKey: String = "" ) : Act 

Kami mengulangi langkah-langkah yang saya bicarakan sebelumnya: kami mengambil antarmuka, memperluas dan menambahkan dua bidang tambahan ke CompletableAct dan SingleAct.

Perbarui implementasi


Kami kembali ke metode Jalankan. Tugas ketiga lebih rumit, tetapi solusinya cukup sederhana: kami memeriksa strategi grup untuk permintaan tertentu dan, jika itu adalah KillGroup, kami membunuh seluruh grup dan menjalankan logika yang biasa.

 MapDisposable -> GroupDisposable ... override fun execute(act: Act, e: (Throwable) -> Unit) { if (act.groupStrategy == KillGroup) groupDisposable.removeGroup(act.groupKey) return when { groupDisposable.contains(act.groupKey, act.id) -> when (act.strategy) { KillMe -> { stop(act.groupKey, act.id) startExecution(act, e) } SaveMe -> log("${act.id} - Act duplicate") } else -> startExecution(act, e) } } 

Masalahnya kompleks, tetapi kami sudah memiliki infrastruktur yang cukup memadai - kami dapat mengembangkannya dan menyelesaikan masalah. Jika Anda melihat hasil kami, apa yang perlu kita lakukan sekarang?

Hasil


 fun act(id: String)= Completable.timer(2, SECONDS).toAct( id = id, groupStrategy = KillGroup, groupKey = "Like-Dislike-PostId-1234" ) executor.apply { execute(act(“Like”)) execute(act(“Dislike”)) execute(act(“Like”)) } Like - Act Started Like - Act Canceled Dislike - Act Started Dislike - Act Canceled Like - Act Started Like - Act Finished 

Jika kami membutuhkan kueri yang rumit, kami menambahkan dua bidang: groupStrategy dan ID grup. ID grup adalah parameter khusus, karena untuk mendukung banyak permintaan suka / tidak suka paralel, Anda perlu membuat grup untuk setiap pasangan permintaan yang termasuk dalam objek yang sama. Dalam hal ini, Anda dapat memberi nama grup Suka-Tidak Suka-PostId dan tambahkan ID posting di sana. Setiap kali kami menyukai posting yang berdekatan, kami akan yakin bahwa semuanya berfungsi dengan baik untuk posting sebelumnya, dan untuk posting berikutnya.

Dalam contoh sintetik kami, kami mencoba menjalankan urutan suka-tidak suka-suka. Ketika kami melakukan tindakan pertama, dan kemudian yang kedua - yang sebelumnya dibatalkan dan yang berikutnya membatalkan ketidaksukaan sebelumnya. Inilah yang saya inginkan.

Dalam contoh terakhir, kami menggunakan parameter bernama untuk membuat Kisah Para Rasul. Ini membantu pembacaan kode yang keren, terutama ketika ada banyak parameter.

Untuk membaca lebih mudah, gunakan parameter bernama.

Arsitektur


Mari kita lihat bagaimana keputusan ini dapat mempengaruhi arsitektur kita. Pada proyek, saya sering melihat bahwa View Model atau Presenter mengambil banyak tanggung jawab, seperti peretasan, untuk menangani situasi dengan suka / tidak suka. Biasanya semua logika ini dalam Model Tampilan: banyak kode duplikat dengan penguncian tombol, penangan LifeCycle, langganan.



Segala sesuatu yang Pelaku kita lakukan sekarang adalah baik dalam Presenter atau View Model. Jika arsitekturnya matang, pengembang dapat membawa logika ini ke beberapa jenis interaksi atau kasus penggunaan, tetapi logika itu digandakan di beberapa tempat.

Setelah kami mengadopsi Pelaksana, Model Tampilan menjadi lebih sederhana dan semua logika disembunyikan dari mereka. Jika Anda pernah membawa ini ke Presenter dan interkom, maka Anda tahu bahwa interkom dan Presenter semakin mudah. Secara umum, saya puas.



Apa lagi yang ingin ditambahkan?


Kelebihan lain dari solusi saat ini adalah bahwa ia dapat diperluas. Apa lagi yang ingin kita tambahkan sebagai pengembang yang bekerja pada aplikasi seluler dan berjuang dengan bug dan banyak permintaan bersamaan setiap hari?

Kemungkinan


Implementasi dari siklus hidup tetap di belakang layar, tetapi sebagai pengembang ponsel, kita semua selalu memikirkan hal ini dan khawatir sehingga tidak ada yang akan mengalir. Saya ingin menyimpan dan mengembalikan permintaan restart aplikasi.

Rantai panggilan. Karena pembungkus rantai RX, dimungkinkan untuk membuat cerita bersambung, karena secara default RX tidak membuat cerita bersambung.

Hanya sedikit orang yang tahu berapa banyak permintaan bersamaan berjalan pada titik waktu tertentu dalam aplikasi mereka. Saya tidak akan mengatakan bahwa ini adalah masalah besar untuk aplikasi kecil dan menengah. Tetapi untuk aplikasi besar yang melakukan banyak pekerjaan di latar belakang, senang memahami penyebab gangguan dan keluhan pengguna. Tanpa infrastruktur tambahan, pengembang tidak memiliki informasi untuk memahami alasannya: mungkin alasannya ada di UI, atau mungkin dalam sejumlah besar permintaan konstan di latar belakang. Kami dapat memperluas solusi kami dan menambahkan beberapa jenis metrik .

Mari kita pertimbangkan kemungkinan secara lebih rinci.

Pemrosesan siklus hidup


 class ActExecutorImpl( lifecycle: Lifecycle ) : ActExecutor { inir { lifecycle.doOnDestroy { cancelAll() } } ... 

Ini adalah contoh implementasi siklus hidup. Dalam kasus yang paling sederhana - dengan Destroy fragmen atau dibatalkan dengan Activity , kami meneruskan lifecycle-handler kepada Pelaksana kami , dan ketika peristiwa onDestroy terjadi, kami menghapus semua permintaan . Ini adalah solusi sederhana yang menghilangkan kebutuhan untuk menyalin-menempelkan kode serupa di View Models. LifeData melakukan hal yang kurang lebih sama.

Menyimpan / Memulihkan


Karena kita memiliki pembungkus, kita dapat membuat kelas terpisah untuk Kisah Para Rasul , di dalamnya akan ada logika untuk membuat tugas-tugas yang tidak sinkron. Selanjutnya, kita dapat menyimpan nama ini ke basis data dan mengembalikannya dari basis data saat startup aplikasi menggunakan metode pabrik atau yang serupa.

Pada saat yang sama, kami akan mendapatkan kesempatan pekerjaan offline dan kami akan memulai kembali permintaan yang dilengkapi dengan kesalahan saat Internet muncul. Dengan tidak adanya Internet atau dengan kesalahan permintaan, kami menyimpannya ke database, dan kemudian mengembalikan dan menjalankannya lagi. Jika Anda dapat melakukan ini dengan RX biasa tanpa pembungkus tambahan, silakan tulis di komentar, itu akan menarik.

Rantai panggilan


Kita juga dapat mengikat Kisah Para Rasul kita . Opsi ekstensi lainnya adalah menjalankan rantai kueri . Misalnya, Anda memiliki satu entitas yang perlu dibuat di server, dan entitas lain, yang tergantung pada yang pertama, harus dibuat tepat pada saat kami yakin bahwa permintaan pertama berhasil. Ini juga bisa dilakukan. Tentu saja, ini tidak sepele, tetapi memiliki kelas yang mengontrol peluncuran semua tugas asinkron dimungkinkan. Menggunakan RX telanjang lebih sulit dilakukan.

Metrik


Sangat menarik untuk melihat berapa banyak permintaan paralel dilakukan rata-rata di latar belakang . Memiliki metrik, Anda dapat memahami penyebab keluhan pengguna tentang kelesuan. , , .

, , , , - - 10% . , .

Kesimpulan


— , . «» . , , , , .

, , . — - , , — . — . , . . .

. Kotlin, . , .

AppsConf 2018, AppsConf 2019 . 38 : , Android, UX, , - , , Kotlin.

, youtube- 22–23 .

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


All Articles