← 返回首页

Flutter 调用原生插件:Android 与 iOS 双端实践

深入 MethodChannel 机制,从零编写一个同时支持 Android(Kotlin)和 iOS(Swift)的原生插件。

为什么需要原生插件

Flutter 的自渲染引擎覆盖了绝大多数 UI 需求,但有些功能必须调用平台 API:蓝牙、NFC、人脸识别、后台服务、推送通知、相机高级功能等。这时就需要通过 Platform Channel 在 Dart 和 Native 之间建立通信桥梁。

MethodChannel 原理

Platform Channel 本质是一个命名管道,消息以标准格式(通常是 Map)序列化后异步传递。Flutter 提供三种 Channel:

  • MethodChannel:单次请求-响应,最常用
  • EventChannel:Native 持续向 Dart 推流(如传感器数据)
  • BasicMessageChannel:双向消息通道,自定义编解码

Dart 端实现

我们以「获取设备电量」为例:

import 'package:flutter/services.dart';

class BatteryPlugin {
  static const _channel = MethodChannel('com.myassets.demo/battery');

  static Future<int> getBatteryLevel() async {
    try {
      final int level = await _channel.invokeMethod('getBatteryLevel');
      return level;
    } on PlatformException catch (e) {
      throw Exception('获取电量失败: ${e.message}');
    }
  }
}

Channel 名称建议使用反向域名格式,避免与其他插件冲突。

Android 端实现(Kotlin)

// MainActivity.kt
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    private val CHANNEL = "com.myassets.demo/battery"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "getBatteryLevel" -> {
                        val level = getBatteryLevel()
                        if (level != -1) result.success(level)
                        else result.error("UNAVAILABLE", "无法获取电量", null)
                    }
                    else -> result.notImplemented()
                }
            }
    }

    private fun getBatteryLevel(): Int {
        val intent = 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
        return if (level == -1 || scale == -1) -1
        else (level * 100 / scale.toFloat()).toInt()
    }
}

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.myassets.demo/battery",
            binaryMessenger: controller.binaryMessenger
        )

        channel.setMethodCallHandler { [weak self] call, result in
            guard call.method == "getBatteryLevel" else {
                result(FlutterMethodNotImplemented)
                return
            }
            self?.receiveBatteryLevel(result: result)
        }

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    private func receiveBatteryLevel(result: FlutterResult) {
        UIDevice.current.isBatteryMonitoringEnabled = true
        let level = UIDevice.current.batteryLevel
        guard level >= 0 else {
            result(FlutterError(code: "UNAVAILABLE",
                message: "无法获取电量(模拟器不支持)",
                details: nil))
            return
        }
        result(Int(level * 100))
    }
}

EventChannel 推流

如果需要 Native 持续向 Flutter 推送数据(如陀螺仪、心率),使用 EventChannel:

// Dart 端订阅
static const _eventChannel = EventChannel('com.myassets.demo/sensor');

Stream<double> get sensorStream {
  return _eventChannel.receiveBroadcastStream()
    .map((event) => event as double);
}

踩坑总结

  • Channel 名称两端必须完全一致,包括大小写
  • MethodChannel 调用必须在主线程(UI Thread),不要在后台线程回调 result
  • iOS 模拟器不支持部分硬件 API(相机、蓝牙、电量监控),需真机测试
  • 传递复杂对象时,使用 Map<String, dynamic> 而非自定义类,避免序列化问题
  • 错误处理要用 result.error() 而非抛出 Native 异常,否则 Flutter 端会收到不可预知的崩溃