flutter_platform_channels
Flutter - 平台通道
Section titled “Flutter - 平台通道”虽然 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 线程。
平台通道的类型
Section titled “平台通道的类型”MethodChannel(方法通道): 用于调用单个方法(类似于函数调用)并接收单个结果。这是最常见的类型。EventChannel(事件通道): 用于将数据从原生平台流式传输到 Dart(例如,传感器事件、位置更新、连接状态变化)。BasicMessageChannel(基本消息通道): 用于基本、异步的消息传递,如果需要可使用自定义编解码器(不如MethodChannel常用)。
使用 MethodChannel(示例:获取电池电量)
Section titled “使用 MethodChannel(示例:获取电池电量)”让我们创建一个简单的示例,使用原生 API 获取设备的当前电池电量。
1. Dart(客户端)侧
Section titled “1. Dart(客户端)侧”在你的 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()));2. Android(宿主)侧 - Kotlin
Section titled “2. Android(宿主)侧 - Kotlin”在 Android Studio 中打开项目的 Android 部分(android/ 文件夹),或编辑 android/app/src/main/kotlin/.../MainActivity.kt:
package com.example.myapp // 替换为你的包名
import androidx.annotation.NonNullimport io.flutter.embedding.android.FlutterActivityimport io.flutter.embedding.engine.FlutterEngineimport io.flutter.plugin.common.MethodChannel
// 电池电量所需的导入import android.content.Contextimport android.content.ContextWrapperimport android.content.Intentimport android.content.IntentFilterimport android.os.BatteryManagerimport android.os.Build.VERSIONimport 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 }}3. iOS(宿主)侧 - Swift
Section titled “3. iOS(宿主)侧 - Swift”在 Xcode 中打开项目的 iOS 部分(ios/ 文件夹)(ios/Runner.xcworkspace),并编辑 ios/Runner/AppDelegate.swift:
import UIKitimport 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中编写代码。
何时使用平台通道
Section titled “何时使用平台通道”- 访问平台特定硬件(传感器、蓝牙、NFC 等)。
- 使用现有插件未提供的平台特定 API(例如,特定操作系统功能、后台执行模式)。
- 集成现有原生代码或 SDK。
- 在 Flutter 应用中显示原生 UI 组件(使用基于平台通道构建的 Platform Views)。
平台通道是扩展 Flutter 功能的强大特性,但通常情况下,pub.dev 上已有的插件可以提供你需要的功能,而无需直接进行原生开发。