3. MethodChannel基础:从创建到原生通信

MethodChannel,说白了就是Flutter和原生代码之间的「电话线」。

我刚开始接触Flutter插件开发时,总觉得这玩意儿很神秘。后来做多了才发现,它本质上就是一个消息通道——Flutter端发个消息过去,原生端处理完再回传结果。就这么简单。

3.1 MethodChannel的创建与注册

先看Flutter端。创建MethodChannel只需要两样东西:一个名字,一个BinaryMessenger。

// Flutter端
import 'package:flutter/services.dart';

class BatteryPlugin {
  static const _channel = MethodChannel('com.example.battery');
  
  Future<int> getBatteryLevel() async {
    final result = await _channel.invokeMethod('getBatteryLevel');
    return result;
  }
}

这里有个坑,我踩过好几次——通道名字必须唯一。你想想看,如果两个插件用了同一个名字,消息就会串线。我个人习惯用「包名+功能名」的格式,比如 com.example.battery

注意:通道名在Flutter端和原生端必须完全一致,大小写都不能错。我曾经因为把"Battery"写成了"battery",排查了半天。

3.2 Android端方法实现

Android端要做的,就是实现 MethodChannel.MethodCallHandler 接口。

// Android端 - MainActivity.kt
class MainActivity : FlutterActivity() {
    private val CHANNEL = "com.example.battery"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        
        MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            CHANNEL
        ).setMethodCallHandler { call, result ->
            when (call.method) {
                "getBatteryLevel" -> {
                    val batteryLevel = getBatteryLevel()
                    if (batteryLevel != -1) {
                        result.success(batteryLevel)
                    } else {
                        result.error("UNAVAILABLE", "Battery level not available", null)
                    }
                }
                else -> result.notImplemented()
            }
        }
    }

    private fun getBatteryLevel(): Int {
        val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
        return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    }
}

嗯,这里要注意几个点:

  • result.success() 返回成功结果
  • result.error() 返回错误信息
  • result.notImplemented() 方法未实现

我在项目中遇到过一个问题:如果原生端忘记调用 result 的任何一个方法,Flutter端就会一直等待,直到超时。所以记得每个分支都要处理 result。

3.3 iOS端方法实现

iOS端用Swift实现,逻辑和Android差不多。

// iOS端 - 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 batteryChannel = FlutterMethodChannel(
            name: "com.example.battery",
            binaryMessenger: controller.binaryMessenger
        )
        
        batteryChannel.setMethodCallHandler { [weak self] (call, result) in
            guard call.method == "getBatteryLevel" else {
                result(FlutterMethodNotImplemented)
                return
            }
            
            let batteryLevel = self?.getBatteryLevel() ?? -1
            if batteryLevel >= 0 {
                result(batteryLevel)
            } else {
                result(FlutterError(
                    code: "UNAVAILABLE",
                    message: "Battery level not available",
                    details: nil
                ))
            }
        }
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    private func getBatteryLevel() -> Int {
        UIDevice.current.isBatteryMonitoringEnabled = true
        let level = Int(UIDevice.current.batteryLevel * 100)
        return level
    }
}
小技巧:iOS端记得用 [weak self] 避免循环引用。我刚开始写Swift时没注意这个,结果内存泄漏了。

3.4 数据类型的自动映射

MethodChannel最方便的地方,就是它会自动帮你做类型转换。Flutter的 int 到Android的 Integer,Flutter的 String 到iOS的 NSString——这些都不用你操心。

Flutter类型 Android类型 iOS类型
null null nil
bool Boolean NSNumber(bool)
int Integer NSNumber(int)
double Double NSNumber(double)
String String NSString
List ArrayList NSArray
Map HashMap NSDictionary

为什么会自动映射?因为MethodChannel底层用的是 StandardMessageCodec,它定义了一套标准的数据编码格式。你传什么类型,它自动给你转成对应的原生类型。

核心要点:虽然类型会自动映射,但复杂对象(比如自定义的Model类)不能直接传。你需要先转成Map或List,再通过通道传递。

举个例子,如果你想传一个用户信息:

// Flutter端
final user = {
  'name': '张三',
  'age': 25,
  'isVip': true
};
await _channel.invokeMethod('saveUser', user);

// Android端接收时,自动转成了HashMap
val userMap = call.arguments as HashMap<String, Any>
val name = userMap["name"] as String

说白了,MethodChannel就是一套「约定大于配置」的通信机制。你只要遵守它的规则,数据就能在Flutter和原生之间自由穿梭。

我个人建议,刚开始做插件开发时,先用简单的数据类型(int、String、Map)练手。等熟悉了这套机制,再考虑更复杂的场景。

避坑指南:我曾经在传List<int>时,Android端收到的是ArrayList<Integer>,但iOS端收到的是NSArray。如果你要做跨平台插件,记得两边都要测试。

好了,MethodChannel的基础就这些。下一章我们会讲EventChannel,那是另一种通信方式——适合流式数据,比如传感器数据、位置更新等。