BasicMessageChannel: التراسل الثنائي الاتجاه للبيانات العشوائية
BasicMessageChannel: التراسل الثنائي الاتجاه للبيانات العشوائية
BasicMessageChannel هو الأكثر مرونة بين أنواع قنوات المنصة الثلاثة في Flutter. على عكس MethodChannel (الذي يتبع نموذج استدعاء-استجابة RPC) وEventChannel (الذي يبث الأحداث من الكود الأصلي إلى Dart)، يتيح BasicMessageChannel لـ أي من الطرفين — Dart أو الكود الأصلي — بدء رسالة في أي وقت، ويمكن للمستقبل اختيارياً إرسال رد. هذا يجعله مثالياً للتواصل الحر ذي الكمون المنخفض في الاتجاهين حيث لا يكون أي من الطرفين "مستدعياً" أو "مستمعاً" بالمعنى الصارم.
MethodChannel، تستدعي Dart الكود الأصلي وتنتظر نتيجة. مع BasicMessageChannel، يمكن لكل من Dart والكود الأصلي إرسال رسائل لبعضهما بشكل مستقل، وكل رسالة قد تحمل ردًا عشوائياً.متى تختار BasicMessageChannel
استخدم BasicMessageChannel عندما:
- يحتاج الكود الأصلي لدفع بيانات إلى Dart دون أن تستدعي Dart أولاً أي دالة
- تحتاج Dart لإرسال بيانات منظمة عشوائية إلى الكود الأصلي وتلقي رد منظم
- تريد تبادل أنواع غير قياسية (ثنائيات، كائنات مخصصة) باستخدام MessageCodec مخصص
- تحتاج إلى أنبوب خفيف الوزن ثنائي الاتجاه دون التعقيد الزائد لدفق الأحداث الكامل
خيارات MessageCodec
كل BasicMessageChannel مُحدَّد بـ MessageCodec<T> يُسلسل الرسائل إلى بايتات ويُفك تسلسلها على الطرف الآخر. يوفر Flutter أربعة ترميزات مدمجة:
- StandardMessageCodec — الافتراضي؛ يعالج
nullوboolوintوdoubleوStringوUint8ListوInt32ListوInt64ListوFloat64ListوListوMap - JSONMessageCodec — يُشفر ويُفك التشفير إلى/من JSON؛ مريح عندما يتعامل الكود الأصلي مع JSON بالفعل
- BinaryCodec — يمرر
ByteDataالخام بدون أي تشفير إضافي؛ الأسرع للحمولات الثنائية - StringCodec — نصوص UTF-8 عادية؛ الأبسط للبروتوكولات النصية فقط
يمكنك أيضاً تنفيذ MessageCodec<T> مباشرةً لدعم تنسيقات أسلاك مخصصة (مثل protobuf أو MessagePack).
جانب Dart: الإرسال والاستقبال
أعلن عن القناة مرة واحدة، ثم استخدم send() لدفع رسالة (وانتظار الرد الاختياري) وsetMessageHandler() لاستقبال الرسائل التي يبدأها الكود الأصلي.
مثال 1 — Dart ترسل رسالة وتتلقى رداً
import 'dart:async';
import 'package:flutter/services.dart';
// أعلن عن قناة مُحدَّدة النوع باستخدام الترميز القياسي
const _channel = BasicMessageChannel<Object?>(
'com.example.app/settings',
StandardMessageCodec(),
);
class NativeSettingsService {
/// يُستدعى عند بدء التشغيل: اطلب من الكود الأصلي خريطة الإعدادات الحالية.
Future<Map<Object?, Object?>?> fetchSettings() async {
final reply = await _channel.send({'action': 'getSettings'});
if (reply is Map) {
return reply as Map<Object?, Object?>;
}
return null;
}
/// سجّل معالجاً حتى يتمكن الكود الأصلي من دفع الإعدادات المحدّثة إلى Dart.
void listenForUpdates(void Function(Map<Object?, Object?>) onUpdate) {
_channel.setMessageHandler((message) async {
if (message is Map) {
onUpdate(message as Map<Object?, Object?>);
}
// أعد null أو نص إقرار
return 'ack';
});
}
}
send() Future يُحلّ برد الكود الأصلي، أو null إذا لم يرد المعالج الأصلي. تعامل دائماً مع حالة null لتجنب أخطاء LateInitializationError غير المتوقعة.جانب الكود الأصلي: Android (Kotlin)
في Android، كرر إعلان القناة في MainActivity (أو أي حاضن لـ FlutterEngine). استخدم setMessageHandler للاستقبال من Dart، واستدعِ send على مثيل القناة لدفع رسائل غير مطلوبة إلى Dart.
مثال 2 — معالج Kotlin في Android + دفع غير مطلوب
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.StandardMessageCodec
class MainActivity : FlutterActivity() {
private lateinit var settingsChannel: BasicMessageChannel<Any?>
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
settingsChannel = BasicMessageChannel(
flutterEngine.dartExecutor.binaryMessenger,
"com.example.app/settings",
StandardMessageCodec.INSTANCE
)
// معالجة الرسائل القادمة من Dart
settingsChannel.setMessageHandler { message, reply ->
if (message is Map<*, *> && message["action"] == "getSettings") {
val settings = mapOf(
"theme" to "dark",
"fontSize" to 16,
"language" to "en"
)
reply.reply(settings) // أرسل الرد إلى Dart
} else {
reply.reply(null)
}
}
}
// استدعِ هذه الدالة من أي مكان في كود Android لدفع بيانات إلى Dart
fun pushSettingsUpdate(newSettings: Map<String, Any>) {
settingsChannel.send(newSettings) { reply ->
// اختياري: تعامل مع إقرار Dart
println("Dart ack: $reply")
}
}
}
جانب الكود الأصلي: iOS (Swift)
واجهة برمجة Swift متماثلة. سجّل FlutterBasicMessageChannel في AppDelegate واستدعِ sendMessage لبدء رسائل من iOS إلى Dart.
مثال 3 — معالج Swift في iOS
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var settingsChannel: FlutterBasicMessageChannel?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let messenger = controller.binaryMessenger
settingsChannel = FlutterBasicMessageChannel(
name: "com.example.app/settings",
binaryMessenger: messenger,
codec: FlutterStandardMessageCodec.sharedInstance()
)
// استقبال الرسائل من Dart
settingsChannel?.setMessageHandler { message, reply in
if let dict = message as? [String: Any],
dict["action"] as? String == "getSettings" {
reply(["theme": "dark", "fontSize": 16, "language": "en"])
} else {
reply(nil)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// دفع تحديث غير مطلوب من iOS إلى Dart
func pushSettingsUpdate(_ settings: [String: Any]) {
settingsChannel?.sendMessage(settings) { reply in
print("Dart ack: \(reply ?? "none")")
}
}
}
BasicMessageChannel على الخيط الرئيسي للمنصة على الجانب الأصلي، ويجب إعداد القناة بعد تهيئة محرك Flutter بالكامل. استدعاء send قبل أن يكون المحرك جاهزاً يُسقط الرسالة بصمت.مقارنة أنواع القنوات الثلاثة
فهم متى تستخدم كل نوع قناة يمنع الإفراط في التصميم الهندسي:
- MethodChannel — تستدعي Dart دالة مسماة على الكود الأصلي؛ يعيد الكود الأصلي نتيجة أو يطلق استثناءً. الأفضل للعمليات المنفصلة (الحصول على مستوى البطارية، فتح الكاميرا).
- EventChannel — يبث الكود الأصلي أحداثاً مستمرة إلى Dart (بيانات المستشعر، تغييرات الاتصال). لا تستطيع Dart إرسال رسائل عبره.
- BasicMessageChannel — رسائل ثنائية الاتجاه الكاملة. يمكن لأي من الطرفين البدء. يدعم الترميزات المخصصة. الأفضل لتبادل البيانات بين الأقران أو البروتوكولات المخصصة.
الخلاصة
يملأ BasicMessageChannel الفجوة بين نموذج RPC الصارم لـ MethodChannel ودفق EventChannel أحادي الاتجاه. باختيار MessageCodec المناسب — قياسي، أو JSON، أو ثنائي، أو مخصص — يمكنك تبادل أي بيانات منظمة بكفاءة بين Dart والكود الأصلي، مع إمكانية أي من الطرفين بدء التواصل في أي وقت.
BasicMessageChannel مع StandardMessageCodec هو أداتك المثلى. سجّل المعالج مبكراً، احتفظ بأسماء القنوات مُنسوبة (مثل com.yourapp/channelname)، وتعامل دائماً مع الردود الفارغة null بسلاسة.