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.
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_launcherIlova ishlatadigan, platformadan mustaqil ochiq API.
- platform interface
- url_launcher_platform_interfaceAbstrakt klass — har bir platforma implementatsiyasi shu shartnomaga rioya qiladi.
- platform implementations
- url_launcher_android, url_launcher_ios, url_launcher_webHar 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:
| Mezon | MethodChannel | Dart FFI |
|---|---|---|
| Chaqiruv turi | Asinxron | Sinxron (bloklaydigan) |
| Xarajat | Har chaqiruvda serializatsiya | Deyarli xarajatsiz, to'g'ridan-to'g'ri |
| Platforma UI/SDK'ga kirish | Ha (Activity, ViewController va h.k.) | Yo'q — faqat C ABI darajasidagi funksiyalar |
| Tipik ishlatilish | Kamera, GPS, bildirishnoma, tizim UI | Rasm/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/vaziyat | Junior darajadagi javob | Senior 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.
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.