Skip to content

flutter_platform_channels

虽然 Flutter 允许你从单一的 Dart 代码库构建精美的 UI 和应用逻辑,但有时你需要访问只有通过原生 Android (Kotlin/Java) 或 iOS (Swift/Objective-C) 代码才能获得的平台特定 API 或服务。例如,使用设备传感器、访问平台特定硬件(如相机,超出基本插件支持)、集成现有原生代码库或利用平台特定 UI 元素。

Flutter 提供 平台通道 (Platform Channels) 作为 Dart 代码与原生平台代码之间的通信机制。

平台通道促进异步消息传递:

  • 客户端 (Dart): 你的 Flutter 应用代码通过指定名称的通道发送消息(带有可选参数的方法调用)。
  • 宿主 (平台原生): 应用的原生 Android 或 iOS 部分监听同一指定名称的通道。当消息到达时,宿主执行请求的原生代码,并可以选择向客户端发送响应。
  • 通道名称 (Channel Name): 一个唯一的字符串标识通道(例如,com.yourcompany.yourapp/channel_name)。
  • 消息编解码器 (Message Codec): 决定了消息(参数和结果)在 Dart 和原生平台之间如何序列化和反序列化。标准编解码器 (StandardMethodCodec) 支持基本类型,如 null、布尔值、数字、字符串、字节数组、列表和 Map。

通信是异步的,以防止阻塞任何一侧的 UI 线程。

  • MethodChannel(方法通道): 用于调用单个方法(类似于函数调用)并接收单个结果。这是最常见的类型。
  • EventChannel(事件通道): 用于将数据从原生平台流式传输到 Dart(例如,传感器事件、位置更新、连接状态变化)。
  • BasicMessageChannel(基本消息通道): 用于基本、异步的消息传递,如果需要可使用自定义编解码器(不如 MethodChannel 常用)。

使用 MethodChannel(示例:获取电池电量)

Section titled “使用 MethodChannel(示例:获取电池电量)”

让我们创建一个简单的示例,使用原生 API 获取设备的当前电池电量。

在你的 Flutter 部件的状态或服务类中:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // Platform Channels 所需
import 'dart:async';
class BatteryInfo extends StatefulWidget {
const BatteryInfo({Key? key}) : super(key: key);
@override
_BatteryInfoState createState() => _BatteryInfoState();
}
class _BatteryInfoState extends State<BatteryInfo> {
// 定义通道。名称必须与宿主侧匹配。
static const platform = MethodChannel('com.example.myapp/battery');
String _batteryLevel = 'Unknown battery level.';
// 调用平台特定代码的方法
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
// 在通道上调用 'getBatteryLevel' 方法。
// 结果期望是一个整数。
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
// 处理平台调用期间可能发生的错误
batteryLevel = "Failed to get battery level: '${e.message}'.";
} on MissingPluginException catch (e) {
// 处理原生侧未实现该方法的情况
batteryLevel = "Battery level method not implemented: '${e.message}'.";
}
// 更新 UI
setState(() {
_batteryLevel = batteryLevel;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Platform Channel Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _getBatteryLevel,
child: const Text('Get Battery Level'),
),
const SizedBox(height: 16),
Text(_batteryLevel),
],
),
),
);
}
}
// --- 用法示例 ---
// void main() => runApp(MaterialApp(home: BatteryInfo()));

在 Android Studio 中打开项目的 Android 部分(android/ 文件夹),或编辑 android/app/src/main/kotlin/.../MainActivity.kt:

package com.example.myapp // 替换为你的包名
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
// 电池电量所需的导入
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
class MainActivity: FlutterActivity() {
// 定义通道名称(必须与 Dart 侧匹配)
private val CHANNEL = "com.example.myapp/battery"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 设置 MethodChannel
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// 注意:此方法在主线程上调用。
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel) // 将结果发送回 Dart
} else {
// 向 Dart 发送错误
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
// 表示方法调用未实现
result.notImplemented()
}
}
}
// 使用 Android SDK 获取电池电量的私有函数
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}

在 Xcode 中打开项目的 iOS 部分(ios/ 文件夹)(ios/Runner.xcworkspace),并编辑 ios/Runner/AppDelegate.swift:

import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 获取 FlutterViewController
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
// 定义通道名称(必须与 Dart 侧匹配)
let batteryChannel = FlutterMethodChannel(name: "com.example.myapp/battery",
binaryMessenger: controller.binaryMessenger)
// 设置 MethodCallHandler
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// 注意:此方法在 UI 线程上调用。
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented) // 表示方法未实现
return
}
// 调用获取电池电量的私有函数
self?.receiveBatteryLevel(result: result)
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// 使用 iOS SDK 获取电池电量的私有函数
private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true // 启用监听
if device.batteryState == UIDevice.BatteryState.unknown {
// 如果状态未知,则向 Dart 发送错误
result(FlutterError(code: "UNAVAILABLE",
message: "Battery level not available.",
details: nil))
} else {
// 电池电量是 float 0.0-1.0,转换为整数 0-100
result(Int(device.batteryLevel * 100))
}
}
}
  • 错误处理: 始终在 Dart 中处理潜在的 PlatformException,并从原生侧发送适当的错误(result.error(...) 或 FlutterError)。
  • 线程: 原生方法处理程序通常在平台的主 UI 线程上运行。将长时间运行的任务放在后台线程上,以避免阻塞 UI。
  • 类型安全: 确保发送和接收的数据类型在 Dart 和原生代码之间匹配。标准编解码器处理基本类型;对于复杂对象,序列化/反序列化为 Map/List。
  • 插件结构: 对于可重用的平台特定功能,创建一个 Flutter 插件包,而不是直接在应用的 MainActivity/AppDelegate 中编写代码。
  • 访问平台特定硬件(传感器、蓝牙、NFC 等)。
  • 使用现有插件未提供的平台特定 API(例如,特定操作系统功能、后台执行模式)。
  • 集成现有原生代码或 SDK。
  • 在 Flutter 应用中显示原生 UI 组件(使用基于平台通道构建的 Platform Views)。

平台通道是扩展 Flutter 功能的强大特性,但通常情况下,pub.dev 上已有的插件可以提供你需要的功能,而无需直接进行原生开发。