aidoc.uz

4-qism · Sifat va ishlab chiqarish·12-bob

Native integratsiya — platform kanallar va FFI

Flutter va native (Android/iOS) kod orasidagi ko'prik qanday ishlaydi: MethodChannel, EventChannel, plugin arxitekturasi va Dart FFI — kodli misollar va intervyu savollari bilan.

Flutter — o'z rendering dvigateliga ega, lekin ba'zi imkoniyatlar (Bluetooth, sensorlar, native SDK'lar) faqat platforma kodida mavjud. Senior Flutter dasturchi bu chegarani qanday kesib o'tishni — va bunda nima uchun ba'zi yondashuvlar boshqalaridan tezroq/xavfsizroq ekanini tushuntira olishi kerak.

1. Nega umuman "ko'prik" kerak#

Flutter ilovasi Dart kodini o'z dvigateli (Skia/Impeller) orqali to'g'ridan- to'g'ri ekranga chizadi — u native UI komponentlaridan (UIView, View) foydalanmaydi. Lekin GPS, Bluetooth, fayl tizimi, kamera kabi imkoniyatlar operatsion tizim API'lari orqali ishlaydi — bularga faqat native (Kotlin/Java Android uchun, Swift/Objective-C iOS uchun) kod orqali murojaat qilish mumkin. Shuning uchun Flutter platform kanallari (platform channels) mexanizmini taqdim etadi — Dart va native kod orasida xabar almashish uchun ko'prik.

Dart kodi (Flutter)
MethodChannel orqali so'rov yuboradi va javob kutadi
Platform kanali (binary messenger)
Xabarni standart kodek (masalan, StandardMessageCodec) bilan serializatsiya qiladi
Native kod (Kotlin/Swift)
Xabarni qabul qiladi, platforma API'sini chaqiradi, natijani qaytaradi

2. MethodChannel — so'rov-javob modeli#

MethodChannel — eng ko'p ishlatiladigan kanal turi: Dart tomonidan bitta metod chaqiriladi, native tomon uni bajaradi va natija (yoki xato) qaytaradi. Bu asinxron, chunki native tomonda ishlov berish vaqt olishi mumkin.

Dart tomoni:

import 'package:flutter/services.dart';
 
class BatteryService {
  static const _channel = MethodChannel('com.aidoc.app/battery');
 
  Future<int> getBatteryLevel() async {
    try {
      final int level = await _channel.invokeMethod('getBatteryLevel');
      return level;
    } on PlatformException catch (e) {
      throw Exception('Batareya darajasini olib bo\'lmadi: ${e.message}');
    }
  }
}

Android (Kotlin) tomoni:

class MainActivity : FlutterActivity() {
    private val CHANNEL = "com.aidoc.app/battery"
 
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                if (call.method == "getBatteryLevel") {
                    val level = getBatteryLevelInternal()
                    if (level != -1) {
                        result.success(level)
                    } else {
                        result.error("UNAVAILABLE", "Batareya darajasi mavjud emas", null)
                    }
                } else {
                    result.notImplemented()
                }
            }
    }
}

iOS (Swift) tomoni:

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller = window?.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(name: "com.aidoc.app/battery", binaryMessenger: controller.binaryMessenger)
 
    channel.setMethodCallHandler { call, result in
      if call.method == "getBatteryLevel" {
        result(self.getBatteryLevel())
      } else {
        result(FlutterMethodNotImplemented)
      }
    }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

3. EventChannel — uzluksiz oqim (stream) uchun#

MethodChannel bitta so'rov-bitta javob modeli. Agar native tomondan vaqti-vaqti bilan, cheklanmagan sonda ma'lumot kelishi kerak bo'lsa (masalan, sensor o'qishlari, joylashuv yangilanishi), EventChannel ishlatiladi — u Dart tomonida Stream sifatida ko'rinadi.

class LocationService {
  static const _eventChannel = EventChannel('com.aidoc.app/location_stream');
 
  Stream<Position> get positionStream =>
      _eventChannel.receiveBroadcastStream().map(
        (event) => Position.fromMap(event as Map),
      );
}

Native tomonda StreamHandler interfeysi amalga oshiriladi — onListen (tinglash boshlanganda) va onCancel (tinglash to'xtaganda) metodlari orqali resurslarni (masalan, GPS sensorini) ochish/yopish boshqariladi.

4. BasicMessageChannel — eng oddiy, xom xabar almashish#

BasicMessageChannel — ikki tomon orasida ixtiyoriy kodek bilan xabar almashishning eng past darajadagi varianti. Kamdan-kam to'g'ridan-to'g'ri ishlatiladi — ko'proq boshqa ikkita kanal turi ustida qurilgan.

5. Plugin arxitekturasi — federated plugins#

Rasmiy Flutter plaginlari (masalan, url_launcher, shared_preferences) federated plugin naqshidan foydalanadi — bu bir nechta paketga bo'lingan arxitektura:

app_facing package
url_launcher
Ilova ishlatadigan, platformadan mustaqil ochiq API.
platform interface
url_launcher_platform_interface
Abstrakt klass — har bir platforma implementatsiyasi shu shartnomaga rioya qiladi.
platform implementations
url_launcher_android, url_launcher_ios, url_launcher_web
Har bir platforma uchun alohida paket, o'z native kodini o'z ichiga oladi.

Bu naqsh nima uchun kerak: yangi platforma (masalan, Linux) qo'shilganda, asosiy paketni o'zgartirmasdan, faqat yangi implementatsiya paketini qo'shish kifoya. Bu — ochiq-yopiq prinsipining (open-closed principle) amaliy ko'rinishi.

6. Dart FFI — C kutubxonalarga to'g'ridan-to'g'ri murojaat#

Platform kanallari asinxron va har bir chaqiruv uchun serializatsiya xarajatiga ega. Agar performance juda muhim bo'lgan, tez-tez chaqiriladigan native funksiya (masalan, rasm/video qayta ishlash, kriptografik hisob-kitob) kerak bo'lsa, Dart FFI (Foreign Function Interface) C kutubxonalarga to'g'ridan-to'g'ri, sinxron murojaat qilish imkonini beradi — platform kanali orqali emas.

import 'dart:ffi';
import 'package:ffi/ffi.dart';
 
// C funksiyasi: int add(int a, int b);
typedef AddNative = Int32 Function(Int32 a, Int32 b);
typedef AddDart = int Function(int a, int b);
 
final DynamicLibrary nativeLib = DynamicLibrary.open('libmath.so');
final AddDart add = nativeLib
    .lookup<NativeFunction<AddNative>>('add')
    .asFunction();
 
void main() {
  print(add(3, 4));   // to'g'ridan-to'g'ri, sinxron C chaqiruvi
}

FFI va platform kanal orasidagi tanlov:

MezonMethodChannelDart FFI
Chaqiruv turiAsinxronSinxron (bloklaydigan)
XarajatHar chaqiruvda serializatsiyaDeyarli xarajatsiz, to'g'ridan-to'g'ri
Platforma UI/SDK'ga kirishHa (Activity, ViewController va h.k.)Yo'q — faqat C ABI darajasidagi funksiyalar
Tipik ishlatilishKamera, GPS, bildirishnoma, tizim UIRasm/video kodek, kriptografiya, mavjud C/C++ kutubxona

7. Pigeon — turdagi xavfsiz kanal kodini generatsiya qilish#

Qo'lda MethodChannel yozishda kanal nomi va argument turlarida yozuv xatolari runtime'gacha bilinmaydi. Pigeon — Dart interfeys ta'rifidan Dart va native (Kotlin/Swift) tomonlar uchun avtomatik, turdagi xavfsiz kod generatsiya qiladigan rasmiy vosita. Katta jamoalarda, ko'p sonli platform chaqiruvi bo'lgan loyihalarda qo'lda yozishga qaraganda ishonchliroq va kamroq xatoga moyil.

8. Keng tarqalgan tuzoqlar#

  • Asosiy oqim (main thread) talabi — ko'p platforma API'lari (masalan, UI bilan bog'liq native chaqiruvlar) faqat asosiy oqimda ishlashi kerak. Agar native tomonda background thread'dan result.success() chaqirilsa, ba'zi hollarda kutilmagan xatti-harakat yoki crash yuz berishi mumkin.
  • Katta ma'lumot uzatish — platform kanali orqali juda katta obyekt (masalan, xom rasm baytlari) uzatish sekin va xotira uchun qimmat; bunday hollarda fayl orqali uzatish yoki FFI ko'proq mos keladi.
  • Kanal nomi ziddiyati — bir xil nomdagi ikkita kanal turli plaginlar tomonidan ishlatilsa, ular bir-birini "eshitib" qolishi mumkin; nomlarni paket domeni bilan (masalan, com.aidoc.app/...) noyob qilish shart.

Ko'p uchraydigan xato javoblar#

Savol/vaziyatJunior darajadagi javobSenior darajadagi javob
"Native funksiyaga qanday murojaat qilasiz?""Plagin qidiraman, bo'lmasa iloji yo'q deyman""Avval pub.dev'dan mavjud plaginni tekshiraman, bo'lmasa o'zim MethodChannel yozaman yoki performance kritik bo'lsa FFI ko'rib chiqaman"
Native chaqiruv sekin"Flutter shunaqa sekin-da""Platform kanalining har chaqiruvda serializatsiya xarajati borligini tekshiraman — tez-tez chaqiriladigan, kichik funksiya bo'lsa FFI'ga o'tishni ko'rib chiqaman"
iOS'da ishlayapti, Androidda ishlamayapti"iOS bilan ishlayman, Android muammosi boshqa jamoada""Ikkala platformada ham bir xil kanal nomi va argument formatini tekshiraman — odatda nom yoki tip mosligi buziladi"

Tez-tez so'raladigan intervyu savollari#

Savol: MethodChannel va EventChannel orasidagi farqni tushuntiring.

Javob: MethodChannel — bitta so'rov, bitta javob modeli: Dart bir metodni chaqiradi, native tomon bir marta javob qaytaradi (yoki xato). Bu oddiy, bitta martalik operatsiyalar (masalan, batareya darajasini olish) uchun mos. EventChannel esa vaqt davomida bir nechta qiymat kelishi kerak bo'lgan hollar uchun — u Dart tomonida Stream sifatida ko'rinadi va native tomon onListen/onCancel orqali oqimni boshqaradi (masalan, sensor o'qishlari yoki joylashuv yangilanishlari).

Savol: Platform kanali chaqiruvi nega har doim asinxron?

Javob: Chunki native tomon, umuman olganda, darhol javob bera olmaydi — u operatsion tizim API'siga murojaat qilishi, foydalanuvchi ruxsatini so'rashi yoki tarmoq/sensor javobini kutishi mumkin. Agar bu chaqiruv sinxron bo'lganida, u Dart'ning UI thread'ini bloklab qo'yar edi. Shuning uchun invokeMethod har doim Future qaytaradi.

Savol: Nega ba'zi hollarda MethodChannel o'rniga Dart FFI ishlatiladi?

Javob: FFI to'g'ridan-to'g'ri, sinxron C ABI chaqiruvi bo'lgani uchun serializatsiya va platform kanali xarajatidan xoli — bu tez-tez chaqiriladigan, performance-kritik funksiyalar (masalan, rasm qayta ishlash, kriptografiya) uchun muhim farq qiladi. Lekin FFI faqat C ABI darajasidagi funksiyalarga kira oladi — u Android Activity yoki iOS UIViewController kabi platforma UI/SDK obyektlariga kira olmaydi, bu uchun baribir platform kanali kerak.

Savol: Federated plugin arxitekturasi nima va u nega yaratilgan?

Javob: Federated plugin — bitta plaginni bir nechta paketga bo'lish: platformadan mustaqil "app-facing" paket, abstrakt "platform interface" paketi va har bir platforma uchun alohida implementatsiya paketi. Bu arxitektura yangi platforma (masalan, Linux yoki Windows) qo'shilganda asosiy paketni o'zgartirmasdan, faqat yangi implementatsiya qo'shish imkonini beradi — ochiq-yopiq prinsipiga mos.

Savol: MissingPluginException odatda nimadan kelib chiqadi?

Javob: Bu xato odatda Dart va native tomondagi kanal nomi mos kelmaganda (yozuv xatosi, farqli string) yoki native tomonda handler ro'yxatdan o'tkazilmagan/noto'g'ri joyda ro'yxatdan o'tkazilganda yuz beradi. Kanal nomi compile vaqtida tekshirilmaydi — bu runtime xatosi, shuning uchun qo'lda yozilgan kanallarda nomlarni konstantalar orqali belgilash va ikkala tomonda ham bir xil ishlatish tavsiya etiladi.

Savol: Pigeon nima muammoni hal qiladi?

Javob: Qo'lda MethodChannel yozishda kanal nomi, metod nomi va argument turlari string/Map darajasida yoziladi — bu yozuv xatolariga moyil va faqat runtime'da bilinadi. Pigeon Dart interfeys ta'rifidan Dart va native tomonlar uchun turdagi xavfsiz kod generatsiya qiladi — nomlash va tip xatolari compile vaqtida ushlanadi, bu katta, ko'p platform chaqiruvli loyihalarda ishonchlilikni oshiradi.

Savol: Platform kanali orqali katta hajmdagi ma'lumot (masalan, video fayl) uzatish yaxshi amaliyotmi?

Javob: Yo'q. Platform kanali xabarlari serializatsiya qilinadi va xotirada nusxalanadi — katta hajm uchun bu sekin va xotira uchun qimmat. Katta ma'lumotlar uchun fayl yo'li (path) orqali uzatish (native tomon faylni yozadi, Dart tomon yo'lni oladi) yoki, juda yuqori performance kerak bo'lsa, FFI orqali xotira ko'rsatkichi (pointer) bilan ishlash ma'qulroq.

Savol: Nega ba'zi native chaqiruvlar faqat asosiy (main) thread'da ishlashi kerak?

Javob: Ko'p platforma UI freymvorklari (Android View system, iOS UIKit) thread-xavfsiz emas — ular faqat o'zlarini yaratgan (odatda asosiy) thread'dan boshqarilishi kutiladi. Agar background thread'dan UI bilan bog'liq native chaqiruv qilinsa, bu aniqlanmagan xatti-harakat yoki crashga olib kelishi mumkin. Shuning uchun ko'p native plagin implementatsiyalarida og'ir ishni background thread'da bajarib, natijani asosiy thread'ga qaytarish naqshi ishlatiladi.

Savol: BasicMessageChannel qachon ishlatiladi?

Javob: U eng past darajadagi kanal — ixtiyoriy kodek bilan ikki tomon orasida xom xabar almashish imkonini beradi. Amalda kamdan-kam to'g'ridan- to'g'ri ishlatiladi, chunki MethodChannel va EventChannel aslida shu mexanizm ustiga qurilgan, aniqroq semantikaga (so'rov-javob yoki oqim) ega qulay qobiqlar. BasicMessageChannel maxsus, nostandart xabar almashish protokoli kerak bo'lgan noyob hollarda ishlatiladi.

Savol: Yangi plagin yozishda platform interface paketining vazifasi nima?

Javob: Platform interface — abstrakt Dart klass sifatida, barcha platforma implementatsiyalari rioya qilishi shart bo'lgan "shartnoma"ni belgilaydi. Bu app-facing paketni har bir platforma implementatsiyasidan ajratadi — app-facing paket faqat shu abstraksiya bilan ishlaydi, qaysi platformada qanday implementatsiya ishlatilayotgani haqida bilishi shart emas. Bu testlash uchun ham foydali — testlarda soxta (fake) implementatsiya almashtirilishi mumkin.

Amaliy topshiriq

Oddiy MethodChannel yozish

Yangi Flutter loyihasida (yoki mavjudida) qurilma nomini native tomondan (Android: Build.MODEL, iOS: UIDevice.current.model) MethodChannel orqali olib, Dart tomonida ekranga chiqaradigan minimal misol yozing. Ikkala platforma uchun ham kod yozib, kanal nomi mos kelmasa qanday xato chiqishini (MissingPluginException) qo'lda sinab ko'ring.

Maslahatni ko'rsatish

Eng oddiy holatdan boshlang — parametrsiz, faqat bitta qiymat qaytaradigan metod (masalan, qurilma nomini olish). Ikkala platformada ham xuddi shu kanal nomini ishlatishga alohida e'tibor bering.