Flutter: Platform Channels 与原生交互
Flutter 不是孤岛——Platform Channels 是连接原生大陆的桥梁。
📋 前置知识:需要先掌握以下内容
1. 你将学到
- Platform Channel 三种类型:MethodChannel、EventChannel、BasicMessageChannel
- Kotlin/Java (Android) 与 Swift (iOS) 端 Handler 实现
- Pigeon 代码生成:类型安全的双端通信
- Plugin 开发流程与 flutter-plugin 脚手架
- ShopApp:调用原生支付 SDK(Apple Pay / Google Pay 集成)
2. 一个支付集成的真实故事
(1) 痛点:Flutter 无法直接调用原生支付 SDK
Bob 的 ShopApp 需要集成 Apple Pay 和 Google Pay,但这两个 SDK 只有原生 API(Swift/Kotlin),Flutter 层无法直接调用。如果不用原生支付,转化率低 40%——用户不愿意在移动端手动输入信用卡号。
(2) MethodChannel 的解法
MethodChannel 是 Flutter ↔ 原生的消息通道,Flutter 端调用方法,原生端处理并返回结果。
DART
import 'package:flutter/services.dart';
// Flutter side: invoke native payment
final channel = MethodChannel('com.shopapp/payment');
final result = await channel.invokeMethod<bool>('startPayment', {
'amount': 99.99,
'currency': 'USD',
'merchantId': 'shopapp_inc',
});
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
(3) 收益:原生支付转化率提升
Bob 通过 MethodChannel 集成原生支付后,支付转化率从 55% 提升到 92%,因为 Apple Pay/Google Pay 一键完成无需输入。
3. Platform Channel 通信机制
sequenceDiagram
participant Flutter
participant Channel as MethodChannel
participant Android as Android(Kotlin)
participant iOS as iOS(Swift)
Flutter->>Channel: invokeMethod('pay', amount: 99.99)
Channel->>Android: onMethodCall()
Channel->>iOS: onMethodCall()
Android-->>Channel: PaymentResult
iOS-->>Channel: PaymentResult
Channel-->>Flutter: result
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
(1) Channel 类型对比
| 类型 | 通信模式 | 数据类型 | 适用场景 |
|---|---|---|---|
MethodChannel |
请求-响应 | 标准类型 | 一次性调用(支付/相机) |
EventChannel |
流式推送 | 标准类型 | 持续事件(传感器/GPS) |
BasicMessageChannel |
双向消息 | 自定义编解码 | 高频双向通信 |
(2) 数据类型映射
| Dart | Android (Kotlin) | iOS (Swift) |
|---|---|---|
bool |
Boolean |
NSNumber(value: Bool) |
int |
Int |
NSNumber(value: Int) |
double |
Double |
NSNumber(value: Double) |
String |
String |
String |
List |
ArrayList |
NSArray |
Map |
HashMap |
NSDictionary |
4. MethodChannel 实现
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:Flutter 端 MethodChannel
DART
import 'package:flutter/services.dart';
// 自定义异常类定义(需自行添加到项目中):
/*
class PaymentCancelledException implements Exception {}
class PaymentFailedException implements Exception {
final String message;
PaymentFailedException(this.message);
}
class PaymentNotSupportedException implements Exception {}
class PaymentException implements Exception {
final String message;
PaymentException(this.message);
}
*/
class NativePaymentService {
static const _channel = MethodChannel('com.shopapp/payment');
static Future<bool> startPayment({
required double amount,
required String currency,
required String merchantId,
}) async {
try {
final result = await _channel.invokeMethod<bool>('startPayment', {
'amount': amount,
'currency': currency,
'merchant_id': merchantId,
});
return result ?? false;
} on PlatformException catch (e) {
switch (e.code) {
case 'PAYMENT_CANCELLED':
throw PaymentCancelledException();
case 'PAYMENT_FAILED':
throw PaymentFailedException(e.message ?? 'Payment failed');
case 'NOT_SUPPORTED':
throw PaymentNotSupportedException();
default:
throw PaymentException(e.message ?? 'Unknown error');
}
}
}
static Future<bool> isPaymentAvailable() async {
return await _channel.invokeMethod<bool>('isPaymentAvailable') ?? false;
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:Android 端 Kotlin Handler
KOTLIN
// android/app/src/main/kotlin/com/shopapp/MainActivity.kt
class MainActivity : FlutterActivity() {
private lateinit var channel: MethodChannel
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.shopapp/payment")
channel.setMethodCallHandler { call, result ->
when (call.method) {
"startPayment" -> {
val amount = call.argument<Double>("amount") ?: 0.0
val currency = call.argument<String>("currency") ?: "USD"
val merchantId = call.argument<String>("merchant_id") ?: ""
startGooglePay(amount, currency, merchantId, result)
}
"isPaymentAvailable" -> {
val available = isGooglePayAvailable()
result.success(available)
}
else -> result.notImplemented()
}
}
}
private fun startGooglePay(amount: Double, currency: String, merchantId: String, result: MethodChannel.Result) {
// Google Pay API integration
val request = PaymentDataRequest.newBuilder()
.setTransactionInfo(TransactionInfo.newBuilder()
.setTotalPriceStatus(WalletConstants.TOTAL_PRICE_STATUS_FINAL)
.setTotalPrice(amount.toString())
.setCurrencyCode(currency)
.build())
.build()
// Launch payment activity and return result
}
private fun isGooglePayAvailable(): Boolean {
val isReadyToPayRequest = IsReadyToPayRequest.newBuilder().build()
// Check Google Pay availability
return true
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:iOS 端 Swift Handler
SWIFT
// ios/Runner/AppDelegate.swift
import Flutter
import PassKit
@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.shopapp/payment",
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { (call, result) in
switch call.method {
case "startPayment":
guard let args = call.arguments as? [String: Any],
let amount = args["amount"] as? Double,
let currency = args["currency"] as? String,
let merchantId = args["merchant_id"] as? String else {
result(FlutterError(code: "INVALID_ARGS", message: "Invalid arguments", details: nil))
return
}
self.startApplePay(amount: amount, currency: currency,
merchantId: merchantId, result: result)
case "isPaymentAvailable":
result(PKPaymentAuthorizationViewController.canMakePayments())
default:
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func startApplePay(amount: Double, currency: String,
merchantId: String, result: @escaping FlutterResult) {
let paymentRequest = PKPaymentRequest()
paymentRequest.merchantIdentifier = merchantId
paymentRequest.supportedNetworks = [.visa, .masterCard, .amex]
paymentRequest.merchantCapabilities = .capability3DS
paymentRequest.countryCode = "US"
paymentRequest.currencyCode = currency
paymentRequest.paymentSummaryItems = [
PKPaymentSummaryItem(label: "ShopApp", amount: NSDecimalNumber(value: amount))
]
guard let vc = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) else {
result(FlutterError(code: "NOT_SUPPORTED", message: "Apple Pay not available", details: nil))
return
}
vc.delegate = self
// Present payment sheet
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
5. EventChannel 流式通信
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:电池状态监听
DART
import 'package:flutter/services.dart';
// Flutter side
class BatteryService {
static const _channel = EventChannel('com.shopapp/battery');
static Stream<double> get batteryLevel {
return _channel.receiveBroadcastStream().map((event) => event as double);
}
}
// Usage
StreamBuilder<double>(
stream: BatteryService.batteryLevel,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('Battery: ${snapshot.data!.toStringAsFixed(0)}%');
}
return const CircularProgressIndicator();
},
)
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
6. Pigeon 类型安全通信
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:Pigeon 定义与生成
DART
// pigeons/payment_api.dart
import 'package:pigeon/pigeon.dart';
// ⚙️ **开发依赖**:flutter pub add --dev pigeon
class PaymentRequest {
double? amount;
String? currency;
String? merchantId;
}
class PaymentResult {
bool? success;
String? transactionId;
String? errorMessage;
}
@HostApi()
abstract class PaymentApi {
@async
PaymentResult startPayment(PaymentRequest request);
bool isPaymentAvailable();
}
// Run: dart run pigeon --input pigeons/payment_api.dart
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
| Pigeon 特性 | 说明 |
|---|---|
| 类型安全 | 编译期检查参数类型 |
| 双端生成 | 同时生成 Dart + Kotlin + Swift 代码 |
| 空安全 | 生成的代码支持 null safety |
| async 支持 | @async 注解自动处理异步回调 |
7. Plugin 开发
(1) Plugin 项目结构
TEXT
📖 仅展示
shopapp_payment/
├── lib/
│ └── shopapp_payment.dart # Dart API
├── android/
│ └── src/main/kotlin/ # Android implementation
├── ios/
│ └── Classes/ # iOS implementation
├── pubspec.yaml
└── example/ # Example app
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:创建 Plugin
BASH
# Create plugin project
flutter create --template=plugin --platforms=android,ios shopapp_payment
# Plugin pubspec.yaml
name: shopapp_payment
description: Native payment integration for ShopApp
version: 1.0.0
flutter:
plugin:
platforms:
android:
package: com.shopapp.payment
pluginClass: ShopAppPaymentPlugin
ios:
pluginClass: ShopAppPaymentPlugin
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
8. 完整示例:ShopApp 支付集成 UI
DART
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'dart:io';
// ⚙️ **安装依赖**:flutter pub add flutter_riverpod go_router
// 自定义类定义来源:
// - NativePaymentService: 见本课第4节 Flutter 端 MethodChannel
// - PaymentCancelledException/PaymentFailedException: 见本课第4节
// - cartProvider: 见第12课 CartNotifier
class PaymentPage extends ConsumerWidget {
final double total;
const PaymentPage({super.key, required this.total});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Payment')),
body: FutureBuilder<bool>(
future: NativePaymentService.isPaymentAvailable(),
builder: (context, snapshot) {
final nativeAvailable = snapshot.data ?? false;
return ListView(children: [
// Native payment options
if (nativeAvailable) ...[
ListTile(
leading: const Icon(Icons.phone_iphone),
title: Text(Platform.isIOS ? 'Apple Pay' : 'Google Pay'),
subtitle: const Text('Fast & secure'),
trailing: const Icon(Icons.chevron_right),
onTap: () => _payNative(context, ref),
),
const Divider(),
],
// Credit card option
ListTile(
leading: const Icon(Icons.credit_card),
title: const Text('Credit Card'),
subtitle: const Text('Visa, Mastercard, Amex'),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push('/checkout/card'),
),
// PayPal option
ListTile(
leading: const Icon(Icons.account_balance_wallet),
title: const Text('PayPal'),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
const SizedBox(height: 32),
Padding(padding: const EdgeInsets.all(16),
child: Text('Total: \$${total.toStringAsFixed(2)}',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold))),
]);
},
),
);
}
Future<void> _payNative(BuildContext context, WidgetRef ref) async {
try {
final success = await NativePaymentService.startPayment(
amount: total, currency: 'USD', merchantId: 'shopapp_inc',
);
if (success && context.mounted) {
ref.read(cartProvider.notifier).clear();
context.go('/order/confirmed');
}
} on PaymentCancelledException {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment cancelled')));
} on PaymentFailedException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.message)));
}
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
❓ 常见问题
Q MethodChannel 的通道名有规范吗?
A 推荐用域名反写:
com.shopapp/payment。通道名必须 Flutter 和原生两端一致。Q MethodChannel 调用是异步的吗?
A 是的,
invokeMethod 返回 Future。原生端处理在主线程,耗时操作应开子线程。Q PlatformException 怎么处理?
A 用 try-catch 捕获 PlatformException,根据 code 字段区分错误类型,展示友好提示。
Q Pigeon 和手写 MethodChannel 怎么选?
A 新项目推荐 Pigeon(类型安全、编译期检查);简单一次性通信可用 MethodChannel。
Q Plugin 和直接写 Platform Channel 有什么区别?
A Plugin 是可复用的包,发布到 pub.dev;直接写 Platform Channel 是项目内专用。
Q Web 端支持 Platform Channel 吗?
A 不支持。Web 端用 JS interop(dart:js_interop)调用浏览器 API。
📖 小节
- MethodChannel 请求-响应模式,适合一次性调用(支付/相机)
- EventChannel 流式推送,适合持续事件(传感器/位置)
- 原生端 Kotlin/Swift 通过 MethodCallHandler 响应 Flutter 调用
- Pigeon 代码生成实现类型安全的双端通信
- Plugin 打包为可复用包,发布到 pub.dev
📝 作业
- 基础题(难度⭐):创建 MethodChannel,Flutter 端调用原生获取设备型号,原生端返回 String。
- 进阶题(难度⭐⭐):实现 EventChannel 监听原生电池状态,Flutter 端用 StreamBuilder 实时展示。
- 挑战题(难度⭐⭐⭐):用 Pigeon 生成类型安全的支付 API,Flutter 端调用原生支付(模拟),处理成功/取消/失败三种结果。