Method Channel مکانیزم ارتباط دوطرفه بین کد Dart و طرف بومی iOS و Android در Flutter است. بر اساس Flutter Documentation, 2026، Method Channel انتقال پیامهای تایپشده بین Dart و پلتفرم میزبان را فراهم میکند. بدون این مکانیزم، دسترسی به قابلیتهای سختافزاری دستگاه، SDKهای بومی و فراخوانیهای سیستمی از کد برنامه غیرممکن است.
نکات اصلی
Method Channel — مؤلفه مرکزی لایه پلتفرمی Flutter است که از طریق آن ایزولههای Dart با برنامه میزبان در iOS یا Android تبادل پیام میکنند. وظیفه اصلی کانال پنهان کردن تفاوتهای پروتکلهای انتقال داده بین دو پلتفرم و ارائه یک API واحد برای توسعهدهنده است.
وقتی برنامه Flutter به دوربین، بلوتوث، سنسورها یا هر API بومی دیگری نیاز دارد، فراخوانی مستقیم از Dart غیرممکن است. Flutter در موتور مبتنی بر C++ اجرا میشود و به فریمورکهای UIKit یا Android SDK دسترسی ندارد. Method Channel این مشکل را با ایجاد پلی بین دنیای Dart و دنیای کد بومی حل میکند.
بر اساس Google I/O 2024، بیش از 80% برنامههای Flutter در تولید حداقل از یک Method Channel برای ادغام با سرویسهای پلتفرمی استفاده میکنند. این نقش حیاتی کانال را در معماری پروژههای مدرن تأیید میکند.
برای توسعهدهنده، Method Channel مانند فراخوانی یک تابع ناهمزمان معمولی به نظر میرسد. در پشت صحنه، سریالسازی پیام، انتقال آن از طریق بافر موتور و اجرای کد بومی روی رشته اصلی پلتفرم انجام میشود.
تعامل از طریق Method Channel با ارسال پیامی از طرف Dart که شامل نام متد و آرگومانها است آغاز میشود. Flutter Engine این پیام را دریافت کرده، آن را به فرمت استاندارد StandardMethodCodec تبدیل کرده و از طریق BinaryMessenger به طرف بومی منتقل میکند.
طرف بومی شامل یک handler به نام MethodCallHandler است که فراخوانی Deserialized را دریافت کرده و منطق مربوطه را اجرا میکند. نتیجه به صورت Response شامل یا نتیجه موفق یا خطا با کد و پیام به Dart بازگردانده میشود.
کل چرخه فراخوانی از طریق Method Channel را میتوان به شش مرحله تقسیم کرد. ایزوله Dart یک نمونه کانال با نام یکتا برای شناسایی اتصال ایجاد میکند. هنگام فراخوانی invokeMethod، کد پلتفرمی Dart نام متد و آرگومانها را با MethodCodec سریالسازی میکند که آنها را از طریق StandardMessageCodec به بافر باینری تبدیل میکند.
Flutter Engine این بافر را از طریق سوکت به طرف بومی منتقل میکند. BinaryMessenger بومی پیام را میخواند، کانال را بر اساس نام شناسایی کرده و handler ثبتشده را فراخوانی میکند و شیء FlutterMethodCall با دادههای تجزیهشده به آن منتقل میکند. Handler کد مورد نظر را اجرا کرده و نتیجه را برمیگرداند که مسیر معکوس سریالسازی را طی کرده و به صورت Future وارد Dart میشود.
معماری Method Channel از چندین موجودیت مرتبط تشکیل شده است که هر کدام مسئول مرحله خود از انتقال داده هستند. Dart API کلاس MethodChannel را ارائه میدهد که جزئیات سطح پایین سریالسازی و مسیریابی را از توسعهدهنده پنهان میکند.
BinaryMessenger — رابط سطح پایین Flutter Engine برای ارسال و دریافت پیامهای باینری بین Dart و پلتفرم میزبان. هر MethodChannel به یک BinaryMessenger خاص متصل میشود که مسیریابی بر اساس نام کانال را تضمین میکند. در طرف Dart از کلاس BinaryMessenger، در Android از BinaryMessenger بسته io.flutter.embedding.engine و در iOS از پروتکل FlutterBinaryMessenger استفاده میشود.
MethodCodec — کدکنندهای است که فراخوانیهای متد و مقادیر بازگشتی را به فرمت باینری تبدیل میکند. Flutter با دو پیادهسازی داخلی ارائه میشود: StandardMethodCodec (پیشفرض) و JSONMethodCodec (برای رشتههای JSON). StandardMethodCodec در پشت صحنه از StandardMessageCodec استفاده میکند که دادهها را با پشتیبانی از تمام انواع پایه Dart سریالسازی میکند.
StandardMessageCodec از مجموعه محدودی از انواع داده برای اطمینان از سازگاری بین Dart، Kotlin و Swift پشتیبانی میکند. این مجموعه شامل: null، bool، int، double، String، Uint8List، Int32List، Int64List، Float64List، List و Map با کلیدهای رشتهای است.
تمام انواع دیگر — DateTime، اشیاء DTO یا کلاسهای سفارشی — باید به یکی از فرمتهای ذکر شده تبدیل شوند. رایجترین رویکرد سریالسازی اشیاء پیچیده به Map با فیلدها و بازسازی ساختار در طرف گیرنده از دیکشنری فیلدها است.
برای انتقال دادههای باینری حجیم مانند تصاویر دوربین، Flutter استفاده از BasicMessageChannel با Uint8List را توصیه میکند تا از کپی کامل بافر در هر فراخوانی از طریق MethodChannel جلوگیری شود.
| نوع Dart | نوع Kotlin | نوع Swift |
|---|---|---|
| null | null | nil |
| bool | Boolean | NSNumber |
| int | Int | NSNumber |
| double | Double | NSNumber |
| String | String | NSString |
| Uint8List | ByteArray | FlutterStandardTypedData |
| List | List | Array |
| Map | HashMap | Dictionary |
تنظیم Method Channel در طرف Android در کلاسی که FlutterPlugin را پیادهسازی میکند یا مستقیماً در MainActivity انجام میشود. رویکرد اول توصیه میشود زیرا مدیریت صحیح چرخه حیات پلاگین و سازگاری با سناریوهای add-to-app را تضمین میکند.
پس از ایجاد نمونه کانال با همان نام طرف Dart، باید MethodCallHandler را از طریق setMethodCallHandler ثبت کرد. در داخل handler، توسعهدهنده نام متد ورودی را از طریق when بررسی کرده و نتیجه را از طریق result.success یا خطا را از طریق result.error با کد و پیام برمیگرداند.
package com.example.app
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
channel.setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryLevel = getBatteryLevel()
if (batteryLevel != null) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery not available", null)
}
}
else -> result.notImplemented()
}
}
}
}
در این مثال، کانال با نام samples.flutter.dev/battery فراخوانی getBatteryLevel را پردازش کرده، سطح باتری را از طریق Android BatteryManager دریافت کرده و به کد Dart برمیگرداند. نام کانال باید در هر دو طرف یکسان باشد، در غیر این صورت پیام به handler نمیرسد.
برای کد تولید، توصیه میشود منطق Method Channel را در یک کلاس جداگانه که FlutterPlugin را پیادهسازی میکند قرار دهید. این امکان استفاده مجدد از پلاگین بین پروژهها را فراهم کرده و پاکسازی صحیح منابع را هنگام فراخوانی onDetachedFromEngine تضمین میکند. پلاگین از طریق registerWith ثبت شده و میتواند به صورت جداگانه از Activity آزمایش شود.
Method Channel در iOS در کلاسی که پروتکل FlutterPlugin را پیادهسازی میکند یا در AppDelegate تنظیم میشود. روش توصیهشده ایجاد یک کلاس پلاگین جداگانه است که از طریق FlutterPluginRegistrar ثبت شده و توسط Flutter Engine مدیریت میشود.
طرف Dart فراخوانی را ارسال میکند، handler بومی شیء FlutterMethodCall را با نام متد و آرگومانها دریافت میکند. توسعهدهنده متد فراخوانیشده را از طریق switch بر اساس call.method تعیین کرده و نتیجه را از طریق closure result برمیگرداند. برای دسترسی به API iOS از UIKit و سایر فریمورکهای سیستمی استفاده میشود.
import Flutter
import UIKit
public class BatteryPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(
name: "samples.flutter.dev/battery",
binaryMessenger: registrar.messenger())
let instance = BatteryPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getBatteryLevel":
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
let level = Int(device.batteryLevel * 100)
result(level)
default:
result(FlutterMethodNotImplemented)
}
}
}
رویکرد FlutterPlugin ثبت صحیح و غیرفعالسازی پلاگین را هنگام نابودی Flutter Engine تضمین میکند. در handler Swift از switch بر اساس call.method استفاده میشود، هر case نتیجه را از طریق closure result برمیگرداند. آرگومانها از طریق call.arguments با تبدیل به نوع مربوطه در دسترس هستند.
هنگام کار با Method Channel رعایت چند قانون کلیدی برای تضمین عملکرد و پایداری برنامه مهم است. توصیه اصلی به حداقل رساندن تعداد و حجم دادههای منتقلشده، بهویژه در فراخوانیهای درون حلقههای انیمیشن یا با فرکانس بالا است.
در طرف بومی همیشه باید استثناها را مدیریت کرده و خطا را از طریق result.error با پیام قابل خواندن برای انسان برگردانید. در طرف Dart هر فراخوانی invokeMethod باید برای catch PlatformException در try-catch پیچیده شود. نادیده گرفتن خطاها میتواند منجر به crash غیرمنتظره برنامه بدون دلیل مشخص شود.
به طور پیشفرض، Method Channel کد بومی را روی رشته اصلی پلتفرم اجرا میکند. اگر handler عملیات سنگینی انجام میدهد، باید اجرا را با Kotlin Coroutines در Android یا Grand Central Dispatch در iOS به رشته پسزمینه منتقل کرد. نتیجه از طریق result فقط پس از اتمام کار روی رشته اصلی برگردانده شود.
نامهای یکتا برای کانالها با استفاده از نمادگذاری دامنه معکوس انتخاب کنید — به عنوان مثال com.example.app/feature. نامهای کوتاه ممکن است با سایر پلاگینها تداخل داشته باشند. Flutter کانالها را به صورت سراسری ثبت میکند، بنابراین نامهای یکسان در پلاگینهای مختلف منجر به بازنویسی handler و عدم کارکرد فراخوانیها میشود.
سوالات متداول
MethodChannel برای فراخوانی متدها در طرح فراخوانی-پاسخ با کدگذاری از طریق MethodCodec طراحی شده است. BasicMessageChannel پیامهای دلخواه را بدون فرمت متد و آرگومان منتقل میکند که برای دادههای جریانی و رویدادهای پلتفرم مناسب است.
مستقیماً — خیر. StandardMessageCodec فقط از انواع پایه پشتیبانی میکند: انواع اولیه، String، Uint8List، List و Map. اشیاء سفارشی باید قبل از ارسال به صورت دستی به Map سریالسازی شده و در طرف گیرنده از دیکشنری فیلدها بازسازی شوند.
در طرف بومی از result.error با کد خطا و پیام استفاده کنید. در طرف Dart invokeMethod را در try-catch بپیچید و PlatformException را بگیرید. اگر متد در پلتفرم پیادهسازی نشده است، result.notImplemented را برگردانید.
هر فراخوانی سریالسازی و کپی دادهها را بین ایزولهها و پلتفرمها انجام میدهد. برای فراخوانیهای نادر، سربار ناچیز است. هنگام انتقال مگابایت داده در هر فریم، تأخیر و کاهش FPS ممکن است رخ دهد. برای دادههای جریانی از viewهای پلتفرمی یا اشیاء رندر بافتدار استفاده کنید.
از EventChannel استفاده کنید — این کانال برای جریان رویدادها از طرف بومی به Dart طراحی شده است. پلتفرم ارسال را از طریق EventSink آغاز میکند و Dart با receiveBroadcastStream در جریان مشترک میشود. Method Channel برای این سناریو مناسب نیست.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.