为什么需要原生插件
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 端会收到不可预知的崩溃