المُرمِّزات وتسلسل البيانات عبر القناة
المُرمِّزات وتسلسل البيانات عبر القناة
كل رسالة يتبادلها Flutter مع الكود الأصلي يجب أن تُسلسَل إلى بايتات قبل أن تعبر حدود المنصة، ثم تُفكَّك تسلسلها على الطرف الآخر. المكوّن المسؤول عن هذا التحويل يُسمى المُرمِّز (Codec). يأتي Flutter مزوَّدًا بأربعة مُرمِّزات مدمجة، كل منها مُحسَّن لحالة استخدام مختلفة. اختيار المُرمِّز الصحيح يؤثر مباشرةً على كلٍّ من الأداء ودقة الأنواع.
المُرمِّزات الأربعة المدمجة
توفر مكتبة services في Flutter هذه المُرمِّزات جاهزةً للاستخدام:
- StandardMessageCodec — المُرمِّز الافتراضي، يدعم أنواع Dart الغنية (int، double، bool، String، Uint8List، List، Map) بترميز ثنائي فعّال
- JSONMessageCodec — يُرمِّز الرسائل كسلاسل JSON بترميز UTF-8؛ مناسب للتشغيل البيني، لكنه يفقد دقة الأنواع (تصبح جميع الأرقام doubles)
- BinaryCodec — يمرّر
ByteDataالخام دون أي تغيير؛ لا يُضيف أي تكلفة ترميز، لكن تقع عليك مسؤولية كل منطق التسلسل - StringCodec — يُرمِّز
Stringواحدة من Dart كـ UTF-8؛ مفيد للبروتوكولات النصية البسيطة
MethodChannel أو BasicMessageChannel أو EventChannel. فقط BasicMessageChannel يسمح باستبدال المُرمِّز الافتراضي؛ أما MethodChannel وEventChannel فمقيَّدان بـ StandardMessageCodec.StandardMessageCodec بالتفصيل
يُعدّ StandardMessageCodec حصان العمل في قنوات المنصة. يُرمِّز كل قيمة بوسم نوع من بايت واحد يعقبه الحمولة. يكون تعيين الأنواع بين Dart وJava/Kotlin/Swift/ObjC محدَّدًا بوضوح:
null← nullbool← Booleanint(32-bit) ← Integer؛ (64-bit) ← Longdouble← DoubleString← String (UTF-8)Uint8List/Int32List/Int64List/Float64List← مصفوفات بايت مكتوبةList← ArrayList (Java) / Array (Swift)Map← HashMap (Java) / Dictionary (Swift)
BasicMessageChannel مع StandardMessageCodec (الافتراضي)
import 'package:flutter/services.dart';
// StandardMessageCodec هو الافتراضي — يمكن حذفه
const _channel = BasicMessageChannel<dynamic>(
'com.example.app/settings',
StandardMessageCodec(),
);
Future<void> sendPreferences() async {
// جميع أنواع Dart هذه تبقى سليمة بعد الرحلة ذهابًا وإيابًا
final reply = await _channel.send({
'userId': 42, // int → Integer على Android
'score': 3.14, // double → Double
'enabled': true, // bool → Boolean
'tags': ['a', 'b'], // List → ArrayList
'meta': <String, dynamic>{'key': 'value'}, // Map → HashMap
});
print('رد الكود الأصلي: $reply');
}
JSONMessageCodec
يُسلسِل JSONMessageCodec رسالتك إلى سلسلة JSON (عبر jsonEncode) قبل تسليمها للطبقة الأصلية، ويُفكِّك تسلسلها في طريق العودة. هذا يجعله قابلًا للتشغيل البيني بسهولة مع أي منصة قادرة على تحليل JSON، لكن ثمة مقايضة مهمة: تنهار جميع أنواع الأرقام إلى double عند فك التسلسل لأن JSON يمتلك نوعًا رقميًا واحدًا. ستصل الأعداد الصحيحة كـ doubles ما لم تُحوِّلها صراحةً.
BasicMessageChannel مع JSONMessageCodec
import 'package:flutter/services.dart';
const _jsonChannel = BasicMessageChannel<dynamic>(
'com.example.app/json',
JSONMessageCodec(),
);
Future<void> fetchConfig() async {
// الإرسال: Map من Dart تُسلسَل تلقائيًا إلى JSON
final result = await _jsonChannel.send({'action': 'getConfig'});
// النتيجة مُفككة من JSON — القيم الرقمية هي doubles
if (result is Map) {
// تحذير: 'version' كان int على الجانب الأصلي، تصل كـ double
final version = (result['version'] as double).toInt();
print('إصدار الإعداد: $version');
}
}
BinaryCodec
لا يُجري BinaryCodec أي ترميز — فهو يمرّر كائن ByteData مباشرةً إلى الجانب الأصلي كبايتات خام. هذا هو الخيار الأعلى أداءً: لا تكلفة إضافية من وسم الأنواع أو تحويل JSON. استخدمه عندما تُطبِّق بروتوكولًا ثنائيًا خاصًا بك (مثل Protocol Buffers أو MessagePack أو FlatBuffers) أو عند بث حمولات ثنائية ضخمة كإطارات الكاميرا أو مخازن الصوت.
BinaryCodec لنقل البايتات الخام
import 'dart:typed_data';
import 'package:flutter/services.dart';
const _binaryChannel = BasicMessageChannel<ByteData?>(
'com.example.app/binary',
BinaryCodec(),
);
Future<void> sendRawFrame(Uint8List pixels) async {
// تغليف Uint8List في عرض ByteData — نسخ صفري
final byteData = pixels.buffer.asByteData();
final response = await _binaryChannel.send(byteData);
if (response != null) {
final ack = response.getUint8(0);
print('تأكيد الكود الأصلي: $ack');
}
}
StringCodec
يُرمِّز StringCodec سلسلة String واحدة كـ UTF-8 بايتات. إنه أبسط المُرمِّزات وخيار جيد للقنوات النصية فقط — مثلًا إرسال رسائل سجل إلى الجانب الأصلي أو استقبال سلاسل اللغة من نظام التشغيل. يحمل تكلفة زهيدة لكنه لا يعالج إلا النوع String؛ تمرير أي نوع آخر يُطلق خطأ تأكيد في وضع التطوير.
StringCodec عندما تحمل القناة أوامر أو تسميات بسيطة، وادمجه مع اتفاقية تحليل خفيفة (مثل القيم المفصولة بأنبوب) بدلًا من تحمّل تكلفة ترميز JSON الكاملة لحمولة صغيرة.اختيار المُرمِّز المناسب
استخدم الدليل التالي عند تصميم قناة جديدة:
- تحتاج أنواعًا غنية (int، List، Map، مصفوفات مكتوبة) مع دقة ذهاب وإياب؟ ←
StandardMessageCodec(الخيار الافتراضي) - التكامل مع مكتبة أصلية تتحدث JSON؟ ←
JSONMessageCodec(انتبه لتحويل int→double) - بث بايتات خام أو استخدام بروتوكول ثنائي مخصص؟ ←
BinaryCodec - القناة تحمل سلسلة نصية فقط؟ ←
StringCodec
JSONMessageCodec عندما يعتمد بروتوكولك على دقة الأعداد الصحيحة (مثل معرِّفات 64-bit أو طوابع Unix الزمنية بالميلي ثانية). لا يُميِّز JSON بين الأعداد الصحيحة وأعداد الفاصلة العائمة، وستفقد الدقة بصمت للأعداد الصحيحة الكبيرة التي تتجاوز 2^53.المُرمِّزات المخصصة
يمكنك تطبيق مُرمِّز مخصص بتوسيع MessageCodec<T> وتجاوز encodeMessage وdecodeMessage. هذا هو المسار الصحيح لـ Protocol Buffers أو أي تنسيق تسلسل مُحكَم بمخطط. يجب أن يُطبِّق النظير الأصلي المُرمِّز المقابل على جانب Android (MessageCodec<T>) أو iOS (FlutterMessageCodec).
ملخص
تغطي المُرمِّزات الأربعة المدمجة في Flutter الطيف الكامل من الترميز الثنائي الغني بالأنواع (StandardMessageCodec) إلى تمرير البايتات الخام (BinaryCodec). يُعدّ StandardMessageCodec الخيار الافتراضي الصحيح لمعظم القنوات لأنه يحافظ على أنواع Dart الأصلية عبر الحدود. انتقل إلى JSONMessageCodec فقط عندما تكون التشغيل البيني مع JSON متطلبًا صارمًا، ودائمًا ضع في الاعتبار تحويل int إلى double الذي يُحدثه. الجأ إلى BinaryCodec عندما يكون الأداء حاسمًا وتملك تنسيق التسلسل من البداية إلى النهاية.