אתה מריץ אפליקציית Flutter על Android וצריך לקרוא נתונים מסורק RFID דרך USB. ב-pub.dev — כלום. הדרך היחידה היא Platform Channel. נתקלנו בזה עשרות פעמים: SDK ספציפי מהיצרן, עבודה עם אודיו ברמה נמוכה דרך AudioRecord, אינטגרציה עם מערכות MDM ארגוניות. בלי Platform Channel — אין דרך. במשך יותר מ-5 שנים יישמנו יותר מ-50 ערוצים כאלה לקמעונאות וללוגיסטיקה. הזמינו פיתוח Platform Channel — ניישם את האינטגרציה תוך 3-5 ימים.
תוספים ב-pub.dev מכסים 80% מהמשימות — מצלמה, גיאולוקציה, התראות push. אבל כשהציוד אינו סטנדרטי (מסוף איסוף נתונים, חיישן רפואי) או שצריך לשלב SDK קנייני (למשל, לעבודה עם קופות רושמות), האפשרות היחידה שנותרה היא לכתוב ערוץ משלך. תרחיש טיפוסי: לקוח מביא קובץ JAR עם Java API, ואנחנו עוטפים אותו ב-Platform Channel.
איך לבחור בין MethodChannel ל-EventChannel?
הבחירה תלויה באופי הנתונים. MethodChannel מתאים לבקשות חד-פעמיות (RPC), EventChannel — לזרם רציף של אירועים. EventChannel נוח פי 2 לנתוני סטרימינג מאשר MethodChannel עם טיפול ידני בקריאות חוזרות. בואו נחקור את שתי האפשרויות.
MethodChannel: כשצריך RPC
צד Dart:
class NfcService {
static const _channel = MethodChannel('com.example.app/nfc');
Future<bool> isNfcAvailable() async {
try {
return await _channel.invokeMethod<bool>('isNfcAvailable') ?? false;
} on PlatformException catch (e) {
debugPrint('NFC error: ${e.code} — ${e.message}');
return false;
}
}
Future<String?> readNfcTag() async {
return _channel.invokeMethod<String>('readNfcTag');
}
}צד Kotlin (MainActivity.kt או Handler נפרד):
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.app/nfc"
private lateinit var nfcAdapter: NfcAdapter
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"isNfcAvailable" -> result.success(nfcAdapter.isEnabled)
"readNfcTag" -> startNfcRead(result)
else -> result.notImplemented()
}
}
}
private fun startNfcRead(result: MethodChannel.Result) {
// реализация NFC-чтения
}
}שם הערוץ הוא מחרוזת. מוסכמה: com.company_name.app/module. שמות לא תואמים ב-Dart וב-Kotlin גורמים ל-MissingPluginException בזמן ריצה ללא כל רמז במהלך הבנייה.
EventChannel: הזרמת אירועים ל-Dart
לנתונים שהצד המקורי מייצר ברציפות: קריאות חיישנים, התראות Bluetooth GATT, שינויי GPS.
קוד SensorStreamHandler מלא ב-Kotlin
```kotlin class SensorStreamHandler(private val context: Context) : EventChannel.StreamHandler {private var sensorManager: SensorManager? = null
private var eventSink: EventChannel.EventSink? = null
private val sensorListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
eventSink?.success(mapOf(
"x" to event.values[0].toDouble(),
"y" to event.values[1].toDouble(),
"z" to event.values[2].toDouble(),
"timestamp" to event.timestamp
))
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
override fun onListen(arguments: Any?, sink: EventChannel.EventSink) {
eventSink = sink
sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
if (accelerometer == null) {
sink.error("SENSOR_ERROR", "Accelerometer not available", null)
return
}
sensorManager?.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_UI)
}
override fun onCancel(arguments: Any?) {
sensorManager?.unregisterListener(sensorListener)
eventSink = null
sensorManager = null
}}
</details>
Регистрация в MainActivity:
```kotlin
EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/accelerometer")
.setStreamHandler(SensorStreamHandler(this))
```ב-Dart, השתמש ב-class NfcService { static const _channel = MethodChannel('com.example.app/nfc'); Future<bool> isNfcAvailable() async { try { return await _channel.invokeMethod<bool>('isNfcAvailable') ?? false; } on PlatformException catch (e) { debugPrint('NFC error: ${e.code} — ${e.message}'); return false; } } Future<String?> readNfcTag() async { return _channel.invokeMethod<String>('readNfcTag'); } } כדי להירשם.
מה כלול בפיתוח Platform Channel?
כל הזמנה כוללת:
- ניתוח תחום הדעת וחקר ה-SDK המקורי;
- עיצוב ממשק הערוץ (מתודות, אירועים, סוגי נתונים);
- יישום ב-Dart ובצד המקורי (Kotlin/Java);
- כתיבת בדיקות יחידה ללוגיקת Kotlin;
- בדיקות אינטגרציה של אפליקציית Flutter על מכשיר אמיתי;
- תיעוד לכל מתודה ודוגמת שימוש;
- מסירת קוד מקור, גישה למאגר, הוראות בנייה.
אנחנו מבטיחים שהערוץ לא יקרוס במהלך hot reload ולא יגרום ל-ANR. אנו מספקים תמיכה למשך 30 יום לאחר המסירה.
השוואת סוגי ערוצים
| מאפיין | MethodChannel | EventChannel | BasicMessageChannel |
|---|---|---|---|
| כיוון | דו-כיווני | Native→Dart בלבד | דו-כיווני |
| סוג קריאה | RPC (בקשה-תגובה) | זרם אירועים | הודעות שרירותיות |
| מתי להשתמש | קריאות חד-פעמיות | נתונים רציפים | תקשורת ברמה נמוכה |
| מורכבות יישום | נמוכה | בינונית (מחזור חיים) | בינונית |
BasicMessageChannel משמש פחות — בדרך כלל כשצריך תקשורת דו-כיוונית ללא קשירת מתודות, למשל להעברת נתונים בינאריים בזמן אמת.
למה חשוב לבדוק Platform Channels
במהלך hot reload, Flutter רושם StreamHandler חדש, אבל הישן נשאר בזיכרון אם class MainActivity : FlutterActivity() { private val CHANNEL = "com.example.app/nfc" private lateinit var nfcAdapter: NfcAdapter override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) nfcAdapter = NfcAdapter.getDefaultAdapter(this) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "isNfcAvailable" -> result.success(nfcAdapter.isEnabled) "readNfcTag" -> startNfcRead(result) else -> result.notImplemented() } } } private fun startNfcRead(result: MethodChannel.Result) { // реализация NFC-чтения } } לא נקרא. כתוצאה מכך, נתונים מגיעים פעמיים — או שמצטברים דליפות זיכרון. לכן, תמיד נקה את ה-handler ביישום. בקוד שלנו למעלה, private var sensorManager: SensorManager? = null private var eventSink: EventChannel.EventSink? = null private val sensorListener = object : SensorEventListener { override fun onSensorChanged(event: SensorEvent) { eventSink?.success(mapOf( "x" to event.values[0].toDouble(), "y" to event.values[1].toDouble(), "z" to event.values[2].toDouble(), "timestamp" to event.timestamp )) } override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} } override fun onListen(arguments: Any?, sink: EventChannel.EventSink) { eventSink = sink sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager val accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) if (accelerometer == null) { sink.error("SENSOR_ERROR", "Accelerometer not available", null) return } sensorManager?.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_UI) } override fun onCancel(arguments: Any?) { sensorManager?.unregisterListener(sensorListener) eventSink = null sensorManager = null } מבטל רישום כראוי מהחיישן ב-</details> Регистрация в MainActivity: ```kotlin EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/accelerometer") .setStreamHandler(SensorStreamHandler(this)) .
בדיקות יחידה לעומת בדיקות אינטגרציה
| קריטריון | בדיקות יחידה (Mockito/mockk) | בדיקות אינטגרציה (integration_test) |
|---|---|---|
| היקף | קוד מקורי בבידוד | מחסנית מלאה של Dart + Native |
| זמן ביצוע | שניות | דקות |
| כיסוי | לוגיקה ללא UI | מכשיר אמיתי |
| זיהוי ANR | לא | כן |
דוגמת בדיקת יחידה ב-Kotlin:
@Test
fun `isNfcAvailable returns false when adapter disabled`() {
val mockAdapter = mockk<NfcAdapter> {
every { isEnabled } returns false
}
val handler = NfcMethodHandler(mockAdapter)
val result = mockk<MethodChannel.Result>(relaxed = true)
handler.handleIsNfcAvailable(result)
verify { result.success(false) }
}בדיקות אינטגרציה מריצות את אפליקציית Flutter האמיתית עם קוד מקורי על אמולטור. זו הדרך היחידה לאמת אינטראקציה בין Dart ל-Android ללא אמולציה של ה-Platform Channel.
ריבוי תהליכונים: המלכודת המרכזית
החלק ב-Kotlin של EventChannel('...').receiveBroadcastStream().map(...) נקרא על ה-thread הראשי. כל פעולה חוסמת בתוכו — ANR. התבנית:
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"heavyOperation" -> {
CoroutineScope(Dispatchers.IO).launch {
val data = performHeavyOperation()
withContext(Dispatchers.Main) {
result.success(data)
}
}
}
}
}יש לקרוא ל-onDetachedFromEngine על ה-thread הראשי — זו דרישה של Flutter. SensorStreamHandler או onCancel הם חובה לתגובות מ-threadים ברקע. ב-95% מהמקרים, ANR לא מתרחש עם יישום נכון; 90% מהבקשות מעובדות תוך 1 ms.
קריאה ל-@Test fun `isNfcAvailable returns false when adapter disabled`() { val mockAdapter = mockk<NfcAdapter> { every { isEnabled } returns false } val handler = NfcMethodHandler(mockAdapter) val result = mockk<MethodChannel.Result>(relaxed = true) handler.handleIsNfcAvailable(result) verify { result.success(false) } } פעמיים גורמת לקריסת מנוע Flutter: ניתן לקרוא למתודות רק פעם אחת. אם הפעולה מבוטלת — קרא ל-MethodChannel.setMethodCallHandler או כלום (אבל אז צד ה-Dart יחכה לנצח). השיטה המומלצת: החזר שגיאה במפורש בעת ביטול.
איך אנחנו עושים את זה: מחסנית וניסיון
אנחנו כותבים Platform Channels ב-Swift/Kotlin כבר יותר מ-5 שנים. בזמן הזה, יישמנו יותר מ-50 ערוצים ללקוחות בקמעונאות, לוגיסטיקה ורפואה. אנו משתמשים בגרסאות העדכניות ביותר: Flutter 3.x, Kotlin 1.9+, Coroutines ו-Flow. המפתחים שלנו מחזיקים בהסמכות Google Associate Android Developer ו-Apple Certified iOS Developer. בתחילת כל פרויקט, אנו עורכים ביקורת על ה-SDK שסופק ומעריכים סיכונים.
לוחות זמנים ועלות
פיתוח של MethodChannel פשוט עם 2-4 מתודות אורך 3 עד 5 ימים. EventChannel עם ניהול מחזור חיים ובדיקות אורך 5 עד 8 ימים. אריזה כתוסף לשימוש חוזר מוסיפה 1-2 ימים. העלות מחושבת באופן אישי לאחר ניתוח המשימה שלך. אנו מעריכים את הפרויקט תוך יום עסקים אחד.
תיעוד Flutter הרשמי: 'ערוצי פלטפורמה הם המפתח לתקשורת עם קוד מקורי.'
קבלו ייעוץ לפרויקט שלכם. צרו קשר כדי לדון בתרחיש שלכם – נשלח הצעה מסחרית ולוחות זמנים.







