Flutter 与原生通信:MethodChannel 与 Pigeon 完全指南

4 阅读3分钟

Flutter 与原生通信:MethodChannel 与 Pigeon 完全指南

Flutter 作为跨平台框架,需要频繁与原生平台(Android/iOS)进行通信。本文系统讲解 MethodChannelEventChannelBasicMessageChannel 的使用场景和代码实现,并介绍 Pigeon 代码生成方案。


一、Flutter 与原生通信的三种方式

通信方式方向适用场景数据类型
MethodChannelFlutter → 原生(双向)调用原生功能(摄像头、蓝牙等)支持基础类型 + List/Map
EventChannel原生 → Flutter(流式)监听原生事件流(传感器、音乐播放进度)支持基础类型 + List/Map
BasicMessageChannel双向,任意时刻高频通信、自定义协议ByteData(需自行序列化)

二、MethodChannel 实战

2.1 Flutter 端代码

import 'package:flutter/services.dart';

class NativeApi {
  static const _channel = MethodChannel('com.example.app/native');
  
  // 调用原生方法(获取电池电量)
  static Future<int> getBatteryLevel() async {
    try {
      final result = await _channel.invokeMethod<int>('getBatteryLevel');
      return result ?? -1;
    } on PlatformException catch (e) {
      print('调用失败:${e.message}');
      return -1;
    }
  }
  
  // 传递复杂参数
  static Future<String> compressImage(String imagePath, int quality) async {
    try {
      final result = await _channel.invokeMethod<String>(
        'compressImage',
        {
          'path': imagePath,
          'quality': quality,
        },
      );
      return result ?? '';
    } on PlatformException catch (e) {
      print('压缩失败:${e.message}');
      return '';
    }
  }
}

使用:

// 在 Widget 中调用
Future<void> _checkBattery() async {
  final level = await NativeApi.getBatteryLevel();
  setState(() {
    _batteryLevel = level;
  });
}

2.2 Android 端代码(Kotlin)

// MainActivity.kt
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) {
                "getBatteryLevel" -> {
                    val batteryLevel = getBatteryLevel()
                    if (batteryLevel != -1) {
                        result.success(batteryLevel)
                    } else {
                        result.error("UNAVAILABLE", "电池电量不可用", null)
                    }
                }
                "compressImage" -> {
                    val path = call.argument<String>("path")
                    val quality = call.argument<Int>("quality") ?: 80
                    
                    if (path != null) {
                        val compressedPath = compressImage(path, quality)
                        result.success(compressedPath)
                    } else {
                        result.error("INVALID_ARGUMENT", "路径不能为空", null)
                    }
                }
                else -> result.notImplemented()
            }
        }
    }
    
    private fun getBatteryLevel(): Int {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        } else {
            val intent = Context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
            val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
            val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
            if (level == -1 || scale == -1) -1 else (level * 100 / scale.toFloat()).toInt()
        }
    }
}

2.3 iOS 端代码(Swift)

// AppDelegate.swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(
            name: "com.example.app/native",
            binaryMessenger: controller.binaryMessenger
        )
        
        channel.setMethodCallHandler { call, result in
            switch call.method {
            case "getBatteryLevel":
                self.getBatteryLevel(result: result)
            case "compressImage":
                guard let args = call.arguments as? [String: Any],
                      let path = args["path"] as? String,
                      let quality = args["quality"] as? Int else {
                    result(FlutterError(code: "INVALID_ARGUMENT", message: "参数错误", details: nil))
                    return
                }
                self.compressImage(path: path, quality: quality, result: result)
            default:
                result(FlutterMethodNotImplemented)
            }
        }
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    private func getBatteryLevel(result: FlutterResult) {
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true
        let level = Int(device.batteryLevel * 100)
        result(level)
    }
}

三、EventChannel 实战(监听原生事件流)

3.1 Flutter 端代码

class NativeEventApi {
  static const _eventChannel = EventChannel('com.example.app/events');
  
  // 监听网络状态变化
  static Stream<String> get networkStatusStream {
    return _eventChannel
        .receiveBroadcastStream()
        .map((event) => event as String);
  }
}

// 使用
StreamBuilder<String>(
  stream: NativeEventApi.networkStatusStream,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text('网络状态:${snapshot.data}');
    }
    return const CircularProgressIndicator();
  },
)

3.2 Android 端代码

// MainActivity.kt
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "...").setMethodCallHandler { ... }

EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/events")
    .setStreamHandler(object : EventChannel.StreamHandler {
        private var eventSink: EventChannel.EventSink? = null
        
        override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
            eventSink = events
            
            // 监听网络变化(示例)
            val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            
            // 发送事件到 Flutter
            eventSink?.success("WIFI")
        }
        
        override fun onCancel(arguments: Any?) {
            eventSink = null
        }
    })

四、Pigeon:类型安全的通信方案

Pigeon 通过代码生成实现类型安全的 Flutter 与原生通信,避免手动处理参数编码/解码。

4.1 配置 Pigeon

# pubspec.yaml
dependencies:
  pigeon: ^13.0.0

4.2 定义接口(Dart)

// lib/messages.dart
import 'package:pigeon/pigeon.dart';

@HostApi()
abstract class NativeApi {
  @async
  int getBatteryLevel();
  
  String getPlatformVersion();
}

@FlutterApi()
abstract class FlutterApi {
  void onNetworkStatusChanged(String status);
}

4.3 生成代码

flutter pub run pigeon \
  --input lib/messages.dart \
  --dart_out lib/messages.g.dart \
  --objc_header_out ios/Runner/messages.h \
  --objc_source_out ios/Runner/messages.m \
  --java_out android/app/src/main/java/com/example/app/Messages.java \
  --java_package "com.example.app"

4.4 Android 端实现(Pigeon 生成)

// MainActivity.kt
class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        
        val api = object : Messages.NativeApi {
            override fun getBatteryLevel(): Long {
                return getBatteryLevel().toLong()
            }
            
            override fun getPlatformVersion(): String {
                return "Android ${Build.VERSION.RELEASE}"
            }
        }
        
        Messages.NativeApi.setup(flutterEngine.dartExecutor.binaryMessenger, api)
    }
}

4.5 Flutter 端使用(Pigeon 生成)

// 使用生成的代码(类型安全)
Future<void> _getBattery() async {
  final api = Messages.NativeApi();
  try {
    final level = await api.getBatteryLevel();
    print('电池电量:$level');
  } catch (e) {
    print('调用失败:$e');
  }
}

Pigeon 的优势:

  • 类型安全,编译时检查错误
  • 不需要手动处理参数编码/解码
  • 自动生成 Android/iOS/Flutter 三端代码
  • 支持复杂数据类型(ListMap、自定义类)

五、数据类型对照表

Dart 类型Android(Kotlin)类型iOS(Swift)类型
nullnullnil
boolBooleanNSNumber(value: Bool)
intInt / LongNSNumber(value: Int64)
doubleDoubleNSNumber(value: Double)
StringStringString
Uint8ListByteArrayFlutterStandardTypedData(bytes: Data)
Int32ListIntArrayFlutterStandardTypedData(int32s: Data)
List<?>List<Any?>Array<Any>
Map<String, ?>Map<String, Any?>Dictionary<String, Any>

六、常见问题 FAQ

Q:MethodChannel 调用时应用崩溃,如何排查?

A:检查以下几点:

  1. Channel 名称在 Flutter 和原生端是否完全一致
  2. 原生端是否正确注册了 MethodCallHandler
  3. 参数类型是否匹配(Dart 的 int 在 Android 端用 Int,不要用 Double
  4. 是否在主线程调用(Android 端 UI 操作需要在主线程)

Q:如何在原生端主动调用 Flutter 方法?

A:使用 MethodChannel(双向通信)

// Flutter 端注册方法供原生调用
static const _channel = MethodChannel('com.example.app/native');

void initState() {
  super.initState();
  _channel.setMethodCallHandler((call) async {
    if (call.method == 'onNativeEvent') {
      final data = call.arguments as String;
      print('收到原生事件:$data');
    }
  });
}

Q:Pigeon 支持自定义类作为参数的吗?

A:支持,需要在 Dart 端定义 @AllFields 注解的类:

import 'package:pigeon/pigeon.dart';

class User {
  final String name;
  final int age;
  User(this.name, this.age);
}

@HostApi()
abstract class NativeApi {
  String getUserInfo(User user);
}

七、参考资源


如果本文对你有帮助,欢迎点赞收藏。如有疑问,欢迎在评论区交流。