> Как во Flutter взаимодействовать с нативным кодом платформы для обмена данными (Flutter, Android)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: PashaPay

Стек: Flutter, Android

> Пример ответа

Короткий ответ

Во Flutter взаимодействие с нативным кодом реализуется через platform channels: MethodChannel для вызова методов, EventChannel для потоковых данных и BasicMessageChannel для двустороннего обмена сообщениями. На Android стороне используется MainActivity или отдельный класс, реализующий MethodCallHandler. Данные сериализуются через стандартный StandardMessageCodec, поддерживающий примитивы, списки и map. Для асинхронных операций - Future на Dart и Result на Kotlin/Java.

Подробное объяснение

Platform channels - это мост между Dart-кодом и нативным кодом Android/iOS. Архитектура состоит из трёх частей:

  • Dart-сторона: MethodChannel с именем канала, вызов invokeMethod.
  • Нативная сторона: MethodChannel с тем же именем, обработчик onMethodCall.
  • Кодек: StandardMessageCodec по умолчанию, сериализует данные в бинарный формат.

Типы данных, которые можно передавать: null, bool, int, double, String, Uint8List, Int32List, Int64List, Float32List, Float64List, List, Map. Кастомные объекты нужно вручную преобразовывать в map.

Для Android важно, что вызовы происходят на main thread, но тяжёлые операции нужно выполнять в фоновом потоке, а результат возвращать через Result.success() или Result.error().

EventChannel работает по подписке: нативная сторона отправляет события через EventSink, Dart получает их через Stream. Это подходит для сенсоров, навигации, системных событий.

BasicMessageChannel - низкоуровневый вариант для двусторонней передачи сообщений, редко используется, но полезен для кастомных протоколов.

На практике

Для senior-позиции важно не просто показать, как вызвать метод, а объяснить:

  • Жизненный цикл: канал создаётся один раз, обычно в initState или в конструкторе сервиса. На нативной стороне - в configureFlutterEngine.
  • Обработка ошибок: PlatformException на Dart, Result.error на Android. Нужно передавать код и message.
  • Потоки: если нативный код долго выполняется, использовать Handler или корутины, не блокируя main thread.
  • Тестирование: мокать MethodChannel через TestDefaultBinaryMessengerBinding.
  • Альтернативы: Pigeon для генерации типизированного кода, FFI для C/C++ библиотек, platform views для нативных UI-элементов.

Для Android 12+ нужно учитывать exported в манифесте, если используется Activity для нативного кода. Также стоит помнить про FlutterEngineGroup для нескольких движков.

Пример кода

Dart-сторона:

class NativeBridge {
  static const _channel = MethodChannel('com.example.app/native');

  Future<String> getDeviceInfo() async {
    try {
      final result = await _channel.invokeMethod('getDeviceInfo');
      return result as String;
    } on PlatformException catch (e) {
      throw Exception('Native error: ${e.code} ${e.message}');
    }
  }
}

Android-сторона (Kotlin):

class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/native")
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "getDeviceInfo" -> {
                        // Выполняем в фоновом потоке
                        Thread {
                            val info = getDeviceInfoBlocking()
                            runOnUiThread { result.success(info) }
                        }.start()
                    }
                    else -> result.notImplemented()
                }
            }
    }
}

EventChannel для потока данных:

final _eventChannel = EventChannel('com.example.app/battery');
Stream<int> get batteryLevel => _eventChannel.receiveBroadcastStream().cast<int>();

Как отвечать на собеседовании

Начни с краткого определения platform channels, затем перечисли три типа каналов и их назначение. Обязательно упомяни кодеки и ограничения по типам данных. Расскажи про обработку ошибок и асинхронность. Если спросят про производительность - отметь, что сериализация через StandardMessageCodec быстрее, чем JSON, но для больших данных лучше использовать FFI или передачу файлов. Покажи понимание альтернатив: Pigeon для типизации, platform views для UI.

Что проверяет интервьюер

  • Понимание архитектуры Flutter и нативного моста.
  • Умение работать с асинхронностью и потоками.
  • Знание ограничений и edge cases: main thread, сериализация, ошибки.
  • Опыт с реальными кейсами: батарея, камера, файловая система.
  • Способность выбрать правильный инструмент: MethodChannel vs EventChannel vs FFI.

Типичные ошибки

  • Вызов invokeMethod без обработки PlatformException.
  • Блокировка main thread на нативной стороне.
  • Использование одного канала для всех методов - лучше разбивать по доменам.
  • Неправильная сериализация: передача DateTime или Uint8List без преобразования.
  • Забывают про result.notImplemented() для неизвестных методов.
  • Создание канала каждый раз заново вместо одного экземпляра.
  • Игнорирование EventChannel для стримов, пытаясь использовать MethodChannel с поллингом.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью