Untuk merilis aplikasi hanya untuk satu platform seluler tidak relevan dan Anda perlu mengembangkan dua versi sekaligus, untuk iOS dan Android. Dan di sini Anda dapat memilih dua cara: bekerja dalam bahasa pemrograman "asli" untuk setiap sistem operasi atau menggunakan kerangka kerja lintas platform.
Ketika mengembangkan salah satu proyek di DD Planet, saya mengandalkan opsi terakhir. Dan dalam artikel ini saya akan berbicara tentang pengalaman mengembangkan aplikasi lintas platform, masalah yang kami temui, dan solusi yang ditemukan.
Tiga pendekatan untuk mengembangkan aplikasi seluler lintas platform
Untuk memulai, pertimbangkan pendekatan apa yang digunakan ketika Anda perlu mendapatkan dua aplikasi sekaligus: untuk iOS dan Android.
Yang pertama adalah yang paling mahal, baik dalam waktu maupun sumber daya: mengembangkan aplikasi terpisah untuk setiap platform. Kompleksitas dari pendekatan ini terletak pada kenyataan bahwa setiap sistem operasi memerlukan pendekatan sendiri: ini dinyatakan baik dalam bahasa di mana pengembangan sedang dilakukan (untuk Android - Java atau Kotlin, untuk iOS - Objective-C atau Swift), dan metode untuk menggambarkan bagian UI aplikasi (file axml dan xib atau storyboard, masing-masing).
Fakta ini sendiri membawa kita pada fakta bahwa untuk pendekatan ini perlu membentuk dua tim pengembangan. Selain itu, Anda harus menduplikasi logika untuk setiap platform: interaksi dengan api dan logika bisnis.
Tetapi bagaimana jika jumlah API yang digunakan bertambah?
Ini menimbulkan pertanyaan: bagaimana cara mengurangi jumlah sumber daya manusia yang dibutuhkan? Singkirkan kebutuhan untuk menggandakan kode untuk setiap platform. Ada cukup banyak kerangka kerja dan teknologi yang memecahkan masalah ini.
Menggunakan kerangka kerja lintas platform (Xamarin.Forms, misalnya) memungkinkan untuk menulis kode dalam satu bahasa pemrograman dan menjelaskan logika data dan logika UI satu kali, di satu tempat. Karena itu, kebutuhan untuk menggunakan dua tim pengembangan menghilang. Dan sebagai hasil kompilasi proyek, kami mendapatkan dua aplikasi asli pada output. Dan ini adalah pendekatan kedua.
Saya pikir, banyak yang tahu apa itu Xamarin, atau paling tidak pernah mendengarnya, tetapi bagaimana cara kerjanya? Xamarin didasarkan pada implementasi open-source platform .NET - Mono. Mono menyertakan kompiler C # sendiri, runtime, serta sejumlah pustaka, termasuk implementasi WinForms dan ASP.Net.
Tujuan dari proyek ini adalah untuk memungkinkan program yang ditulis dalam C # dijalankan pada sistem operasi selain sistem Windows - Unix, Mac OS, dan lainnya. Kerangka kerja Xamarin sendiri, pada dasarnya, adalah pustaka kelas yang memberi para pengembang akses ke SDK platform dan kompiler untuk ini. Xamarin.Forms, pada gilirannya, memungkinkan Anda untuk tidak hanya menulis untuk kedua platform dalam bahasa yang sama, tetapi juga mendesain layar menggunakan markup XAML, yang akrab bagi mereka yang sudah memiliki pengalaman dengan aplikasi WPF. Sebagai hasil dari perakitan proyek, kami mendapatkan tampilan yang hampir identik pada semua platform, karena pada tahap kompilasi semua kontrol XF dikonversi ke asli untuk setiap platform.

Pengembang dipaksa untuk menulis kode untuk setiap platform hanya jika akses ke fitur platform apa saja diperlukan (misalnya, pemindai sidik jari atau tingkat baterai) atau perlu untuk menyempurnakan perilaku kontrol. Dalam beberapa kasus, ketika mengembangkan aplikasi, mungkin perlu untuk menulis kode yang tergantung platform, tetapi bahkan dalam kasus ini, tidak ada yang melarang mengambil fungsi platform ke antarmuka dan berinteraksi dengannya dari proyek umum.
Satu bahasa pemrograman, kode kecil, dan sebagainya. Semuanya terdengar indah tapi, Xamarin. Badai bukanlah peluru perak, dan semua keindahannya pecah menjadi batu kenyataan. Segera setelah situasi muncul ketika kontrol XF bawaan tidak lagi memenuhi persyaratan untuk mereka, struktur layar dan kontrol menjadi semakin rumit. Untuk memastikan pekerjaan yang nyaman dengan layar dari proyek umum, Anda harus menulis semakin banyak render khusus.
Ini akan beralih ke pendekatan ketiga, yang kami gunakan saat mengembangkan aplikasi.
Kami telah menemukan bahwa menggunakan Formulir Xamarin dapat menyulitkan pekerjaan, daripada menyederhanakannya. Oleh karena itu, untuk mengimplementasikan layar yang kompleks secara arsitektur, elemen desain dan kontrol yang secara fundamental berbeda dari yang asli, kompromi dan kemungkinan menggabungkan pendekatan pertama dan kedua ditemukan.
Kami memiliki semua tiga proyek yang sama: proyek PCL biasa, tetapi tanpa Formulir Xamarin, dan dua proyek Xamarin Android dan Xamarin iOS. Masih ada kesempatan untuk menulis semuanya dalam satu bahasa, logika umum antara dua proyek, tetapi tidak ada batasan satu markup XAML. Komponen UI dikendalikan oleh setiap platform dan menggunakan alat asli, pada Android - AXML asli, pada file iOS - XIB. Setiap platform memiliki kemampuan untuk mematuhi pedomannya, karena koneksi antara Core dan proyek platform diatur hanya pada tingkat data.
Untuk mengatur hubungan seperti itu, Anda dapat menggunakan pola desain MVVM dan implementasinya yang cukup populer untuk Xamarin - MVVMCross. Penggunaannya memungkinkan Anda menyimpan ViewModel yang umum untuk setiap layar, yang menggambarkan seluruh "logika bisnis" dari pekerjaan, dan percayakan renderingnya ke platform. Ini juga memungkinkan dua pengembang untuk bekerja dengan layar yang sama (satu dengan logika - yang lain dengan UI) dan tidak saling mengganggu. Selain penerapan pola, kami mendapatkan sejumlah alat untuk bekerja: penerapan DI dan IoC. Untuk meningkatkan interaksi dengan platform ke tingkat kode umum, pengembang hanya perlu mendeklarasikan antarmuka dan mengimplementasikannya pada platform. Untuk hal-hal yang khas, MvvmCross sudah menyediakan satu set plugin sendiri. Dalam tim, kami menggunakan plugin messenger untuk bertukar pesan antara platform dan kode umum dan plugin untuk bekerja dengan file (memilih gambar dari galeri, dll.).
Kami memecahkan masalah desain kompleks dan navigasi multi-level
Seperti yang disebutkan sebelumnya, ketika menggunakan representasi kompleks di layar, kerangka kerja dapat menyulitkan kehidupan daripada membuatnya lebih mudah. Tetapi apa yang disebut elemen kompleks? Karena saya terutama terlibat dalam pengembangan iOS, contoh platform ini akan dipertimbangkan. Misalnya, hal sepele seperti bidang input dapat memiliki beberapa status dan logika yang cukup untuk beralih dan visualisasi.
Selama bekerja dengan input pengguna, kontrol input seperti itu dikembangkan di sini. Dia dapat menaikkan namanya di atas bidang input, bekerja dengan topeng, mengatur awalan, postfix, memberi tahu ketika CapsLock ditekan, memvalidasi informasi dalam dua mode: larangan input dan output informasi kesalahan. Logika di dalam kontrol membutuhkan sekitar ~ 1000 baris. Dan, tampaknya: apa yang bisa rumit dalam desain bidang input?
Contoh sederhana dari kontrol kompleks yang kami lihat. Bagaimana dengan layar?
Untuk mulai dengan, saya akan mengklarifikasi bahwa dalam kebanyakan kasus satu layar aplikasi adalah satu kelas - UIViewController, menjelaskan perilakunya. Selama pengembangan, penciptaan navigasi multi-level diperlukan. Konsep aplikasi yang dikembangkan datang ke mengelola real estat Anda dan berinteraksi dengan tetangga dan organisasi kota. Oleh karena itu, dibangun tiga tingkat navigasi: properti, tingkat presentasi (rumah, kota, wilayah) dan jenis konten. Semua switching dilakukan dalam satu layar.
Ini dilakukan agar pengguna, di mana pun dia berada, memahami jenis konten apa yang dia lihat. Untuk mengatur navigasi seperti itu, layar utama aplikasi tidak hanya terdiri dari satu pengontrol. Secara visual, ini dapat dibagi menjadi 3 bagian, tetapi adakah yang bisa menebak berapa banyak pengontrol yang digunakan di sini?
Lima belas pengendali utama. Dan ini hanya untuk konten.
Di sini monster seperti itu hidup di layar utama dan terasa cukup baik. Lima belas pengendali untuk satu layar, tentu saja, sangat banyak. Ini memengaruhi kecepatan seluruh aplikasi, dan Anda perlu mengoptimalkannya entah bagaimana.
Kami menolak inisialisasi sinkron: semua model tampilan diinisialisasi di latar belakang dan hanya jika perlu. Untuk mengurangi waktu rendering, kami juga meninggalkan file xib untuk layar ini: penentuan posisi absolut dan matematika selalu lebih cepat daripada menghitung dependensi antar elemen.
Untuk melacak begitu banyak pengontrol yang perlu Anda pahami:
- Dalam kondisi apa mereka masing-masing;
- Di mana pengguna;
- Apa yang dia harapkan untuk dilihat saat pindah ke controller lain.
Untuk melakukan ini, saya menulis prosesor navigasi terpisah yang menyimpan informasi tentang lokasi pengguna, jenis konten yang dia lihat, riwayat navigasi, dll. Ia mengontrol urutan dan kebutuhan inisialisasi.
Karena setiap tab adalah pengontrol slider (untuk membuat transisi gesek pada mereka), Anda perlu memahami: masing-masing bisa dalam keadaan sendiri (misalnya, "Berita" terbuka di satu, dan "Voting" di yang lain). Ini diikuti oleh prosesor navigasi yang sama. Bahkan mengubah tingkat presentasi dari rumah ke wilayah, kami akan tetap pada jenis konten yang sama.
Kami mengontrol aliran data secara real time
Bekerja dengan begitu banyak data dalam aplikasi, Anda perlu mengatur pengiriman informasi yang relevan di semua bagian secara real time. Untuk mengatasi masalah ini, 3 metode dapat dibedakan:
- Akses API Dengan pengatur waktu atau pemicu dan minta kembali konten yang relevan di layar;
- Memiliki koneksi permanen ke server dan menerima perubahan secara real time;
- Terima dorongan dengan perubahan konten.
Setiap pendekatan memiliki pro dan kontra, jadi lebih baik menggunakan ketiganya, hanya memilih kekuatan masing-masing. Kami membagi konten di dalam aplikasi menjadi beberapa jenis: panas, reguler, dan layanan. Ini dilakukan untuk menentukan waktu yang dapat diterima antara acara dan pemberitahuan pengguna. Misalnya, kami ingin melihat pesan obrolan segera setelah mereka mengirimkannya kepada kami - ini adalah konten yang panas. Pilihan lain: jajak pendapat dari tetangga. Tidak ada bedanya ketika kita melihatnya, sekarang atau dalam satu menit, karena ini adalah konten biasa. Notifikasi kecil di dalam aplikasi (pesan yang belum dibaca, perintah, dll.) Adalah konten layanan yang perlu pengiriman segera, tetapi tidak memakan banyak data.
Ternyata:
- Konten panas - koneksi permanen dengan API;
- Konten normal - http-permintaan ke API;
- Konten sistem - pemberitahuan push.
Yang paling menarik adalah menjaga koneksi yang konstan. Menulis klien Anda sendiri untuk bekerja dengan soket web adalah langkah di lubang kelinci, jadi Anda perlu mencari solusi lain. Akibatnya, kami berhenti di perpustakaan SignalR. Mari kita lihat apa itu.
ASP.Net SignalR adalah perpustakaan dari Microsoft yang menyederhanakan interaksi client-server secara real time, menyediakan komunikasi dua arah antara klien dan server. Server mencakup API lengkap untuk mengelola koneksi, peristiwa koneksi-pemutusan, mekanisme untuk menggabungkan klien yang terhubung ke dalam grup, dan otorisasi.
SignalR dapat menggunakan soket web, LongPolling, dan permintaan http sebagai transportasi. Anda dapat menentukan jenis transportasi secara paksa atau mempercayai perpustakaan: jika websocket dapat digunakan, itu akan bekerja melalui websocket, jika ini tidak mungkin, maka ia akan turun sampai menemukan transportasi yang dapat diterima. Fakta ini ternyata sangat praktis, mengingat fakta bahwa ia direncanakan untuk menggunakannya pada perangkat seluler.
Total, manfaat apa yang kita dapatkan:
- Kemampuan untuk bertukar pesan dalam jenis apa pun antara klien dan server;
- Mekanisme untuk beralih secara otomatis antara soket web, Long Pooling, dan permintaan Http;
- Informasi tentang status koneksi saat ini;
- Kesempatan untuk menyatukan klien dalam kelompok;
- Metode praktis untuk memanipulasi logika pengiriman pesan dalam grup;
- Kemampuan untuk skala server.
Ini, tentu saja, tidak memuaskan semua kebutuhan, tetapi terasa membuat hidup lebih mudah.
Di dalam proyek, pembungkus digunakan di perpustakaan SignalR, yang selanjutnya menyederhanakan bekerja dengannya, yaitu:
- Monitor status koneksi, sambungkan kembali sesuai dengan kondisi yang ditentukan dan dalam hal terjadi putus;
- Dapat dengan cepat mengganti atau membuka kembali koneksi, membunuh yang lama secara tidak serempak dan memberikannya ke pemulung untuk merobek - ternyata, metode koneksi bekerja sepuluh kali lebih cepat daripada metode penutupan (Buang atau Berhenti), dan ini adalah satu-satunya cara untuk menutupnya;
- Mengatur antrian untuk mengirim pesan sehingga koneksi ulang atau pembukaan kembali koneksi tidak mengganggu pengiriman;
- Kontrol transfer ke delegasi yang sesuai jika terjadi kesalahan yang tidak terduga.
Masing-masing pembungkus ini (kami menyebutnya klien) bekerja bersama dengan sistem caching, dan, jika terjadi pemutusan, hanya dapat meminta data yang mungkin terlewatkan selama waktu ini. "Masing-masing" karena beberapa senyawa aktif ditahan secara bersamaan. Di dalam aplikasi ada messenger lengkap, dan klien terpisah digunakan untuk melayaninya.
Klien kedua bertanggung jawab untuk menerima pemberitahuan. Seperti yang sudah saya katakan, jenis konten yang biasa diperoleh melalui permintaan http, di masa depan pembaruannya jatuh pada klien ini, yang melaporkan semua perubahan penting di dalamnya (misalnya, pemungutan suara telah berpindah dari satu status ke status lainnya, publikasi berita baru).
Visualisasikan data dalam aplikasi
Mendapatkan data adalah satu hal, menunjukkan hal lain. Pembaruan data waktu nyata memiliki kesulitannya sendiri. Minimal, Anda harus memutuskan bagaimana menyajikan pembaruan ini kepada pengguna. Dalam aplikasi kami menggunakan tiga jenis pemberitahuan:
- Pemberitahuan konten yang belum dibaca;
- Pembaruan otomatis data di layar;
- Penawaran konten.
Cara yang paling akrab dan biasa untuk menunjukkan bahwa di suatu tempat ada konten baru adalah dengan menyorot ikon bagian. Dengan demikian, hampir semua ikon memiliki kemampuan untuk menampilkan notifier konten yang belum dibaca sebagai titik merah. Yang lebih menarik adalah dengan pembaruan otomatis.
Secara otomatis memperbarui data hanya mungkin ketika konten baru tidak mengatur ulang layar dan tidak mengubah ukuran kontrol. Misalnya, pada layar survei: informasi tentang suara hanya akan mengubah nilai bilah kemajuan dan persentase. Perubahan seperti itu tidak akan menyebabkan perubahan ukuran, mereka dapat diterapkan secara instan tanpa masalah.
Kesulitan muncul ketika Anda perlu menambahkan konten baru ke daftar. Semua daftar dalam aplikasi, sebenarnya, adalah ScrollView dan memiliki beberapa karakteristik: ukuran jendela, ukuran konten, dan posisi gulir. Semuanya memiliki awal yang statis (di atas layar dengan koordinat 0; 0) dan dapat meluas ke bawah. Tambahkan konten baru di daftar, pada akhirnya, tidak ada masalah, daftar akan bertahan. Tetapi konten baru akan muncul di bagian atas, dan ini gambarnya:

Berada di 3 elemen, kita akan berada di 2 - gulir akan memantul. Dan karena konten baru dapat tiba secara konstan, pengguna tidak akan dapat menggulir secara normal. Anda mungkin berkata: mengapa tidak menghitung ukuran konten baru dan menggeser gulir ke bawah ke nilai ini? Ya, itu bisa dilakukan. Tetapi kemudian Anda harus mengontrol posisi gulir secara manual, dan jika pada saat itu pengguna menggulir ke arah mana pun, tindakannya akan terganggu. Itulah sebabnya layar seperti itu tidak dapat diperbarui secara waktu nyata tanpa persetujuan pengguna.
Solusi terbaik dalam situasi ini adalah memberi tahu pengguna bahwa ketika ia menggulir umpan, seseorang memposting konten baru. Dalam desain kami, itu terlihat seperti lingkaran merah di sudut layar. Dengan mengkliknya, pengguna memberikan persetujuan bersyarat kepada kami untuk mengembalikannya ke bagian atas layar dan menampilkan konten segar.
Dengan pendekatan ini, kami, tentu saja, menghindari masalah "juggling" konten, tetapi mereka masih harus dipecahkan. Yaitu, di layar obrolan, karena selama komunikasi dan interaksi dengan layar, konten baru harus ditampilkan di tempat yang berbeda.
Perbedaan antara obrolan dan daftar reguler adalah bahwa konten baru ada di bagian bawah layar. Karena ini adalah "ekor", Anda dapat menambahkan konten di sana tanpa banyak kesulitan. Pengguna menghabiskan 90% dari waktu di sini, yang berarti Anda harus terus menjaga posisi gulir dan menggesernya ke bawah saat menerima dan mengirim pesan. Dalam percakapan langsung, tindakan seperti itu harus dilakukan cukup sering.
Poin kedua: memuat riwayat saat menggulir ke atas. Tepat ketika memuat cerita, kita menemukan diri kita dalam situasi di mana perlu untuk menempatkan pesan di atas tingkat ulasan (yang akan memerlukan bias), sehingga gulir itu lancar dan berkelanjutan. Dan seperti yang sudah kita ketahui, agar tidak mengganggu pengguna, tidak mungkin mengontrol posisi gulir secara manual.
Dan kami menemukan solusi: kami membaliknya. Balik layar memecahkan dua masalah sekaligus:
- Ekor daftar ada di bagian atas, jadi kami dapat menambahkan cerita tanpa hambatan dengan mengganggu gulungan pengguna;
- Pesan terakhir selalu di bagian atas daftar dan kami tidak perlu menggulir layar sebelumnya.
Solusi ini juga membantu mempercepat rendering, menghilangkan operasi yang tidak perlu dengan kontrol gulir.
Berbicara tentang kinerja. Di versi pertama layar, drawdown yang terlihat terdeteksi saat menggulir pesan.
Karena konten dalam "uang" adalah beragam - teks, file, foto - Anda harus terus menghitung ulang ukuran sel, menambah dan menghapus elemen dalam uang. Oleh karena itu, optimasi gelembung diperlukan. Kami melakukan hal yang sama seperti pada layar utama, merender sebagian adonan dengan posisi absolut.Saat bekerja dengan daftar di iOS, sebelum menggambar sel, Anda harus mengetahui tingginya. Karena itu, sebelum menambahkan pesan baru ke daftar, Anda perlu menyiapkan semua informasi yang diperlukan untuk ditampilkan dalam aliran terpisah, menghitung ketinggian sel, memproses data pengguna, dan hanya setelah kami mengetahui dan menyimpan semua yang diperlukan, tambahkan sel ke daftar.Hasilnya, kami mendapatkan gulir yang lancar dan tidak aliran UI yang kelebihan beban.Untuk meringkas:
- Pengembangan lintas platform menghemat waktu dan uang;
- , , ;
- , ;
- ;
- SignalR – - ;
- ;
- , , ;
- , SignalR-, , , , .