Flutter: Platform Channels 与原生交互

Flutter 不是孤岛——Platform Channels 是连接原生大陆的桥梁。

📋 前置知识:需要先掌握以下内容

1. 你将学到


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 通信机制

100%
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。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建 MethodChannel,Flutter 端调用原生获取设备型号,原生端返回 String。
  2. 进阶题(难度⭐⭐):实现 EventChannel 监听原生电池状态,Flutter 端用 StreamBuilder 实时展示。
  3. 挑战题(难度⭐⭐⭐):用 Pigeon 生成类型安全的支付 API,Flutter 端调用原生支付(模拟),处理成功/取消/失败三种结果。

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏