Method Channel to mechanizm dwukierunkowej komunikacji między kodem Dart a natywną stroną iOS i Android w Flutter. Według Flutter Documentation, 2026, Method Channel zapewnia przesyłanie typowanych wiadomości między Dart a platformą-hostem. Bez tego mechanizmu niemożliwe jest uzyskanie dostępu do możliwości sprzętowych urządzenia, natywnych SDK i wywołań systemowych z kodu aplikacji.
Najważniejsze
Method Channel to centralny komponent platformy Flutter, przez który izolaty Dart wymieniają się wiadomościami z aplikacją hosts na iOS lub Android. Głównym zadaniem kanału jest ukrycie różnic w protokołach przesyłania danych między dwiema platformami i zapewnienie jednolitego API dla programisty.
Gdy aplikacja Flutter wymaga dostępu do aparatu, Bluetooth, czujników lub innego natywnego API, bezpośrednie wywołanie z Dart jest niemożliwe. Flutter działa w silniku opartym na C++ i nie ma dostępu do frameworków UIKit ani Android SDK. Method Channel rozwiązuje ten problem, tworząc most między światem Dart a światem kodu natywnego.
Według Google I/O 2024, ponad 80% aplikacji Flutter w produkcji używa co najmniej jednego Method Channel do integracji z usługami platformowymi. Potwierdza to krytyczną rolę kanału w architekturze nowoczesnych projektów.
Dla programisty Method Channel wygląda jak wywołanie zwykłej funkcji asynchronicznej. Pod maską odbywa się serializacja wiadomości, przesłanie jej przez bufor silnika i wykonanie kodu natywnego na głównym wątku platformy.
Interakcja przez Method Channel rozpoczyna się od wysłania przez stronę Dart wiadomości zawierającej nazwę metody i argumenty. Flutter Engine odbiera tę wiadomość, przekształca ją w standardowy format StandardMethodCodec i przekazuje na stronę natywną przez BinaryMessenger.
Strona natywna zawiera handler — MethodCallHandler, który otrzymuje zdeserializowane wywołanie i wykonuje odpowiednią logikę. Wynik jest zwracany z powrotem do Dart w postaci Response, zawierającej albo pomyślny wynik, albo błąd z kodem i komunikatem.
Cały cykl wywołania przez Method Channel można podzielić na sześć etapów. Izolat Dart tworzy instancję kanału z unikalną nazwą dla identyfikacji połączenia. Przy wywołaniu invokeMethod platformowy kod Dart serializuje nazwę metody i argumenty za pomocą MethodCodec, który przekształca je w bufor binarny przez StandardMessageCodec.
Flutter Engine przekazuje ten bufor przez gniazdo na stronę natywną. Natywny BinaryMessenger odczytuje wiadomość, określa kanał po nazwie i wywołuje zarejestrowany handler, przekazując mu obiekt FlutterMethodCall z przetworzonymi danymi. Handler wykonuje odpowiedni kod i zwraca wynik, który przechodzi odwrotną ścieżkę serializacji i trafia do Dart jako Future.
Architektura Method Channel składa się z kilku powiązanych ze sobą jednostek, z których każda odpowiada za swój etap przesyłania danych. Dart API udostępnia klasę MethodChannel, która ukrywa przed programistą szczegóły niskiego poziomu serializacji i routingu.
BinaryMessenger to niskopoziomowy interfejs Flutter Engine do wysyłania i odbierania wiadomości binarnych między Dart a platformą-hostem. Każdy MethodChannel jest powiązany z konkretnym BinaryMessenger, który zapewnia routing według nazwy kanału. Po stronie Dart używana jest klasa BinaryMessenger, na Android — BinaryMessenger z pakietu io.flutter.embedding.engine, na iOS — protokół FlutterBinaryMessenger.
MethodCodec to koder, który przekształca wywołania metod i wartości zwracane na format binarny. Flutter jest dostarczany z dwiema wbudowanymi implementacjami: StandardMethodCodec (domyślnie) i JSONMethodCodec (dla łańcuchów JSON). StandardMethodCodec używa pod maską StandardMessageCodec, który serializuje dane z obsługą wszystkich typów podstawowych Dart.
StandardMessageCodec obsługuje ograniczony zestaw typów danych, aby zapewnić zgodność między Dart, Kotlin i Swift. W skład zestawu wchodzą: null, bool, int, double, String, Uint8List, Int32List, Int64List, Float64List, List oraz Map z kluczami-cIągami znaków.
Wszystkie inne typy — DateTime, obiekty DTO lub klasy niestandardowe — muszą zostać przekształcone na jeden z wymienionych formatów. Najpopularniejszym podejściem jest serializacja złożonych obiektów do Map z polami i odtworzenie struktury po stronie odbiorcy z słownika pól.
Do przesyłania dużych danych binarnych, takich jak obrazy z aparatu, Flutter zaleca używanie BasicMessageChannel z Uint8List, aby uniknąć pełnego kopiowania bufora przy każdym wywołaniu przez MethodChannel.
| Dart typ | Kotlin typ | Swift typ |
|---|---|---|
| null | null | nil |
| bool | Boolean | NSNumber |
| int | Int | NSNumber |
| double | Double | NSNumber |
| String | String | NSString |
| Uint8List | ByteArray | FlutterStandardTypedData |
| List | List | Array |
| Map | HashMap | Dictionary |
Konfiguracja Method Channel po stronie Android odbywa się w klasie implementującej FlutterPlugin lub bezpośrednio w MainActivity. Pierwsze podejście jest zalecane, ponieważ zapewnia prawidłowe zarządzanie cyklem życia wtyczki i zgodność ze scenariuszami add-to-app.
Po utworzeniu instancji kanału z tą samą nazwą co po stronie Dart należy zarejestrować MethodCallHandler przez setMethodCallHandler. Wewnątrz handlera programista sprawdza nazwę przychodzącej metody przez when i zwraca wynik przez result.success albo błąd przez result.error z kodem i komunikatem.
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()
}
}
}
}
W tym przykładzie kanał o nazwie samples.flutter.dev/battery przetwarza wywołanie getBatteryLevel, pobiera poziom naładowania przez Android BatteryManager i zwraca go do kodu Dart. Nazwa kanału musi być taka sama po obu stronach, w przeciwnym razie wiadomość nie dotrze do handlera.
W kodzie produkcyjnym zaleca się wydzielenie logiki Method Channel do osobnej klasy implementującej FlutterPlugin. Pozwala to na ponowne użycie wtyczki między projektami i gwarantuje poprawne oczyszczenie zasobów przy wywołaniu onDetachedFromEngine. Wtyczka rejestruje się przez registerWith i może być testowana niezależnie od Activity.
Method Channel na iOS konfiguruje się w klasie implementującej protokół FlutterPlugin lub w AppDelegate. Zalecanym sposobem jest utworzenie osobnej klasy wtyczki, która rejestruje się przez FlutterPluginRegistrar i jest zarządzana przez Flutter Engine.
Strona Dart wysyła wywołanie, natywny handler otrzymuje obiekt FlutterMethodCall z nazwą metody i argumentami. Programista określa wywoływaną metodę przez switch według call.method i zwraca wynik przez zamknięcie result. Do dostępu do API iOS używane są UIKit i inne frameworki systemowe.
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)
}
}
}
Podejście FlutterPlugin gwarantuje prawidłową rejestrację i wyłączenie wtyczki przy zniszczeniu Flutter Engine. W handlerze Swift używa się switch według call.method, każdy case zwraca wynik przez zamknięcie result. Argumenty są dostępne przez call.arguments z rzutowaniem na odpowiedni typ.
Podczas pracy z Method Channel ważne jest przestrzeganie kilku kluczowych zasad, aby zapewnić wydajność i stabilność aplikacji. Główne zalecenie to minimalizacja liczby i objętości przesyłanych danych, szczególnie przy wywołaniach w pętlach animacji lub z wysoką częstotliwością.
Po stronie natywnej zawsze należy obsługiwać wyjątki i zwracać błąd przez result.error z czytelnym komunikatem. Po stronie Dart każde wywołanie invokeMethod powinno być owinięte w try-catch w celu przechwycenia PlatformException. Ignorowanie błędów może doprowadzić do nieoczekiwanego crasha aplikacji bez zrozumiałej przyczyny.
Domyślnie Method Channel wykonuje kod natywny na głównym wątku platformy. Jeśli handler wykonuje ciężką operację, należy przenieść wykonanie do wątku tła za pomocą Kotlin Coroutines na Android lub Grand Central Dispatch na iOS. Zwracanie wyniku przez result powinno nastąpić dopiero po zakończeniu pracy na głównym wątku.
Wybieraj unikalne nazwy dla kanałów, używając odwrotnej notacji domenowej — na przykład com.example.app/feature. Krótkie nazwy mogą kolidować z innymi wtyczkami. Flutter rejestruje kanały globalnie, więc identyczne nazwy w różnych wtyczkach prowadzą do nadpisania handlera i niedziałających wywołań.
Często zadawane pytania
MethodChannel służy do wywoływania metod w schemacie wywołanie-odpowiedź z kodowaniem przez MethodCodec. BasicMessageChannel przesyła dowolne wiadomości bez formatu metody i argumentów, co jest wygodne dla danych strumieniowych i zdarzeń z platformy.
Bezpośrednio — nie. StandardMessageCodec obsługuje tylko typy podstawowe: prymitywy, String, Uint8List, List i Map. Obiekty niestandardowe należy ręcznie serializować do Map przed wysłaniem i odtwarzać po stronie odbiorcy ze słownika pól.
Po stronie natywnej używaj result.error z kodem błędu i komunikatem. Po stronie Dart owiń invokeMethod w try-catch i łap PlatformException. Jeśli metoda nie jest zaimplementowana na platformie, zwróć result.notImplemented.
Każde wywołanie wykonuje serializację i kopiowanie danych między izolatami a platformami. W przypadku rzadkich wywołań narzut jest nieznaczny. Przy przesyłaniu megabajtów danych na klatkę mogą wystąpić opóźnienia i spadek FPS. Do danych strumieniowych używaj platformowych widoków lub teksturowanych obiektów renderowania.
Użyj EventChannel — służy on do strumieniowania zdarzeń ze strony natywnej do Dart. Platforma inicjuje wysyłanie przez EventSink, a Dart subskrybuje strumień za pomocą receiveBroadcastStream. Method Channel nie nadaje się do tego scenariusza.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.