Flutter рисует интерфейс сам, но камера, биометрия, платежи и датчики живут в нативном коде Android (Kotlin/Java) и iOS (Swift/Objective-C). Связь с ними строится через платформенные каналы, генератор Pigeon или прямой вызов C через FFI.
MethodChannel: вызов метода
Асинхронный вызов «запрос-ответ» по имени канала.
const _channel = MethodChannel('app.example/battery');
Future<int?> batteryLevel() async {
try {
return await _channel.invokeMethod<int>('getLevel');
} on PlatformException catch (e) {
log('ошибка платформы: ${e.code} ${e.message}');
return null;
} on MissingPluginException {
return null; // нет реализации на этой платформе
}
}
На Android в MainActivity (или в плагине) регистрируется обработчик с тем же именем канала, он вызывает result.success(value), result.error(code, message, details) или result.notImplemented(). На iOS то же через FlutterMethodChannel в AppDelegate.
Что важно знать:
- Вызовы асинхронные и сериализуются стандартным кодеком (
StandardMessageCodec):null,bool, числа,String,Uint8Listи типизированные списки,List,Map. Свои классы надо переводить в эти типы. - Обработчики на нативной стороне по умолчанию выполняются в главном потоке платформы. Долгая работа там тормозит UI приложения: её уводят в фоновый поток, а
resultвызывают, когда работа готова. Сообщения во Flutter отправляют с главного потока платформы. Есть и встроенный вариант: обработчик канала можно привязать к фоновой очереди задач (TaskQueue). - Имена каналов и методов это строки: опечатка проявится только в рантайме как
MissingPluginExceptionилиnotImplemented.
EventChannel и BasicMessageChannel
EventChannelдаётStreamсобытий с платформы: геолокация, датчики, состояние сети. Подписка на стороне Dart вызываетonListenу нативногоStreamHandler, отмена вызываетonCancel, где нужно отписаться от источника.BasicMessageChannelшлёт сообщения в обе стороны со своим кодеком, когда модель «метод и результат» не подходит.
Нативная сторона тоже может вызвать метод в Dart через тот же MethodChannel (setMethodCallHandler в Dart).
Pigeon: типобезопасные каналы
Pigeon генерирует код каналов для Dart, Kotlin, Swift по описанию API на Dart:
@HostApi()
abstract class BatteryApi {
int getLevel();
}
Получаются интерфейсы и классы данных с обеих сторон, без строковых имён и ручной сериализации. Ошибки контракта ловятся на этапе компиляции. Команда Flutter использует Pigeon в своих плагинах, и для нового кода это рекомендуемый путь.
FFI
dart:ffi вызывает функции C напрямую, без сериализации и синхронно. Подходит для библиотек на C, C++ и Rust: криптография, обработка изображений, SQLite. Привязки генерирует ffigen. Для Java/Kotlin и Objective-C/Swift есть генераторы jnigen и ffigen с поддержкой Objective-C, но это более новые и менее устоявшиеся инструменты.
Плагины и платформенные виджеты
Код с каналами упаковывают в плагин: Dart-API плюс реализации под платформы. Federated plugins разносят платформы по отдельным пакетам. Нативный экран (карта, веб-вью) встраивается через Platform Views (AndroidView, UiKitView): это дорого по производительности, используйте их точечно.
Частые ошибки
- Тяжёлая работа в обработчике на главном потоке платформы.
- Не обработан
PlatformExceptionиMissingPluginException. - Не отписались от нативного источника в
onCancel: утечка и расход батареи. - Передача своих объектов без перевода в поддерживаемые кодеком типы.
Что спросят на собесе
- Как вызвать нативный код из Flutter, какие бывают каналы.
- Какие типы можно передать через канал.
- Зачем Pigeon и чем он лучше ручных каналов.
- Когда выбирать FFI.
- Что такое Platform Views и почему они дорогие.