一、为什么需要与原生平台交互?
尽管Flutter提供了强大的跨平台能力,但在实际开发中,我们不可避免需要调用原生平台功能:
- 🔒 访问设备特有功能(如指纹识别、NFC、蓝牙)
- 📱 使用尚未有Flutter插件的原生SDK
- ⚡ 提升特定场景性能(如视频处理)
- 📦 集成企业级原生模块(如金融级安全SDK)
- 🌐 与遗留原生代码库集成
https://img-blog.csdnimg.cn/202406/flutter_native_interaction_scenarios.png
💡2024调查数据:87%的生产级Flutter应用都需要至少一种原生功能集成(来源:Flutter企业应用白皮书)
本文将带你:
- ✅ 掌握Platform Channels核心原理
- ✅ 实现MethodChannel与EventChannel完整案例
- ✅ 避免90%开发者踩过的原生交互陷阱
- ✅ 获取Android/iOS双平台完整代码模板
二、Platform Channels核心原理
2.1 三种Channel类型对比
| Channel类型 | 通信方向 | 适用场景 | 同步/异步 |
|---|---|---|---|
| MethodChannel | Flutter ↔ Native | 调用原生方法、获取结果 | 异步 |
| EventChannel | Native → Flutter | 监听原生事件流 | 异步流 |
| BasicMessageChannel | Flutter ↔ Native | 简单消息传递 | 异步 |
https://img-blog.csdnimg.cn/202406/flutter_platform_channels_architecture.png
📌关键点:
- 所有通信必须异步(避免阻塞UI线程)
- 数据通过标准消息编解码器序列化
- 通信基于二进制消息通道(BinaryMessenger)
2.2 消息编解码器选择
| 编解码器 | 支持数据类型 | 性能 | 使用场景 |
|---|---|---|---|
| StandardMethodCodec | 基本类型、List、Map | ⭐⭐⭐ | 默认选择 |
| JSONMethodCodec | JSON兼容数据 | ⭐⭐ | 跨语言通信 |
| StringCodec | 字符串 | ⭐⭐⭐⭐ | 简单文本 |
| BinaryCodec | 二进制数据 | ⭐⭐⭐⭐⭐ | 图像/文件 |
✅推荐:90%场景使用默认的
StandardMethodCodec
三、MethodChannel实战:调用原生功能
3.1 实现电池状态查询功能
步骤1:Dart端定义Channel
// lib/battery_service.dart import 'package:flutter/services.dart'; class BatteryService { static const MethodChannel _channel = MethodChannel('com.example/battery'); // 获取电池电量(0-100) Future<int> getBatteryLevel() async { try { final int result = await _channel.invokeMethod('getBatteryLevel'); return result; } on PlatformException catch (e) { throw Exception("获取电量失败: ${e.message}"); } } }步骤2:Android端实现(Kotlin)
// android/app/src/main/java/com/example/myapp/MainActivity.kt import io.flutter.embedding.android.FlutterActivity import io.flutter.plugin.common.MethodChannel import android.content.Context import android.content.IntentFilter import android.os.BatteryManager class MainActivity: FlutterActivity() { private val CHANNEL = "com.example/battery" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "getBatteryLevel" -> { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "电池信息不可用", null) } } else -> result.notImplemented() } } } private fun getBatteryLevel(): Int { val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) val level = batteryIntent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1 val scale = batteryIntent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1 return (level * 100 / scale).toInt() } }步骤3:iOS端实现(Swift)
// ios/Runner/AppDelegate.swift import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let batteryChannel = FlutterMethodChannel( name: "com.example/battery", binaryMessenger: controller.binaryMessenger ) batteryChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in if call.method == "getBatteryLevel" { self.receiveBatteryLevel(result: result) } else { result(FlutterMethodNotImplemented) } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func receiveBatteryLevel(result: @escaping FlutterResult) { let device = UIDevice.current device.isBatteryMonitoringEnabled = true if device.batteryState == .unknown { result(FlutterError(code: "UNAVAILABLE", message: "电池信息不可用", details: nil)) } else { let batteryLevel = Int(device.batteryLevel * 100) result(batteryLevel) } } }步骤4:UI中使用
// lib/main.dart class BatteryScreen extends StatefulWidget { const BatteryScreen({super.key}); @override State<BatteryScreen> createState() => _BatteryScreenState(); } class _BatteryScreenState extends State<BatteryScreen> { final BatteryService _batteryService = BatteryService(); int _batteryLevel = -1; bool _isLoading = false; Future<void> _getBatteryLevel() async { setState(() => _isLoading = true); try { final level = await _batteryService.getBatteryLevel(); setState(() { _batteryLevel = level; _isLoading = false; }); } catch (e) { setState(() => _isLoading = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("错误: $e")), ); } } @override void initState() { super.initState(); _getBatteryLevel(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("电池状态查询")), body: Center( child: _isLoading ? const CircularProgressIndicator() : Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( _batteryLevel >= 0 ? '当前电量: $_batteryLevel%' : '电量信息不可用', style: const TextStyle(fontSize: 24), ), const SizedBox(height: 20), ElevatedButton( onPressed: _getBatteryLevel, child: const Text("刷新电量"), ) ], ), ), ); } }3.2 运行效果演示
https://img-blog.csdnimg.cn/202406/flutter_battery_demo.png
✅成功实现:Flutter调用原生API获取实时电池状态
四、EventChannel实战:监听原生事件流
4.1 实现位置更新监听功能
步骤1:Dart端定义EventChannel
// lib/location_service.dart import 'package:flutter/services.dart'; class Location { final double latitude; final double longitude; Location(this.latitude, this.longitude); Map<String, double> toMap() { return {'latitude': latitude, 'longitude': longitude}; } } class LocationService { static const EventChannel _channel = EventChannel('com.example/location'); // 获取位置流 Stream<Location> get locationStream { return _channel .receiveBroadcastStream() .map((dynamic event) => _parseLocation(event)); } Location _parseLocation(dynamic event) { if (event is Map) { return Location( event['latitude']?.toDouble() ?? 0.0, event['longitude']?.toDouble() ?? 0.0, ); } throw Exception('无效的位置数据格式'); } }步骤2:Android端实现(Kotlin)
// android/app/src/main/java/com/example/myapp/LocationStreamHandler.kt import io.flutter.plugin.common.EventChannel import android.location.Location import android.location.LocationListener import android.location.LocationManager import android.content.Context class LocationStreamHandler( private val context: Context ) : EventChannel.StreamHandler { private var eventSink: EventChannel.EventSink? = null private lateinit var locationManager: LocationManager private val locationListener = object : LocationListener { override fun onLocationChanged(location: Location) { eventSink?.success(mapOf( "latitude" to location.latitude, "longitude" to location.longitude )) } } override fun onListen(arguments: Any?, events: EventChannel.EventSink) { eventSink = events startLocationUpdates() } override fun onCancel(arguments: Any?) { eventSink = null stopLocationUpdates() } private fun startLocationUpdates() { locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager try { locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 5000, // 5秒更新一次 10f, // 10米移动距离 locationListener ) } catch (e: SecurityException) { eventSink?.error("PERMISSION_DENIED", "位置权限被拒绝", null) } } private fun stopLocationUpdates() { locationManager.removeUpdates(locationListener) } }步骤3:注册EventChannel
// MainActivity.kt override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // 注册MethodChannel(电池示例) // ... // 注册EventChannel EventChannel( flutterEngine.dartExecutor.binaryMessenger, "com.example/location" ).setStreamHandler(LocationStreamHandler(this)) }步骤4:iOS端实现(Swift)
// ios/Runner/LocationStreamHandler.swift import Flutter import CoreLocation class LocationStreamHandler: NSObject, FlutterStreamHandler, CLLocationManagerDelegate { private var locationManager: CLLocationManager? private var eventSink: FlutterEventSink? func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { eventSink = events locationManager = CLLocationManager() locationManager?.delegate = self locationManager?.desiredAccuracy = kCLLocationAccuracyBest locationManager?.requestWhenInUseAuthorization() return nil } func onCancel(withArguments arguments: Any?) -> FlutterError? { locationManager?.stopUpdatingLocation() locationManager = nil eventSink = nil return nil } // MARK: - CLLocationManagerDelegate func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let location = locations.last else { return } let data: [String: Double] = [ "latitude": location.coordinate.latitude, "longitude": location.coordinate.longitude ] eventSink?(data) } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { eventSink?(FlutterError( code: "LOCATION_ERROR", message: error.localizedDescription, details: nil )) } }步骤5:注册EventChannel(AppDelegate.swift)
// AppDelegate.swift override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // ... 其他Channel注册 let locationChannel = FlutterEventChannel( name: "com.example/location", binaryMessenger: controller.binaryMessenger ) locationChannel.setStreamHandler(LocationStreamHandler()) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }步骤6:UI中使用位置流
// lib/location_screen.dart class LocationScreen extends StatefulWidget { const LocationScreen({super.key}); @override State<LocationScreen> createState() => _LocationScreenState(); } class _LocationScreenState extends State<LocationScreen> { final LocationService _locationService = LocationService(); StreamSubscription<Location>? _locationSubscription; Location? _currentLocation; String _error = ''; @override void initState() { super.initState(); _startLocationUpdates(); } void _startLocationUpdates() { _locationSubscription = _locationService.locationStream.listen( (location) { setState(() { _currentLocation = location; _error = ''; }); }, onError: (err) { setState(() { _error = err.toString(); }); }, ); } @override void dispose() { _locationSubscription?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("实时位置追踪")), body: Center( child: _error.isNotEmpty ? Text("错误: $_error", style: const TextStyle(color: Colors.red)) : (_currentLocation != null ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.location_on, size: 60, color: Colors.blue), const SizedBox(height: 20), Text( '纬度: ${_currentLocation!.latitude.toStringAsFixed(6)}', style: const TextStyle(fontSize: 18), ), Text( '经度: ${_currentLocation!.longitude.toStringAsFixed(6)}', style: const TextStyle(fontSize: 18), ), const SizedBox(height: 30), const Text( "位置数据每5秒更新一次", style: TextStyle(color: Colors.grey), ) ], ) : const CircularProgressIndicator()), ), ); } }4.2 运行效果演示
https://img-blog.csdnimg.cn/202406/flutter_location_demo.gif
✅成功实现:Flutter实时接收原生平台的位置更新事件
五、高级技巧:复杂数据类型传递
5.1 传递自定义对象(User类)
Dart端:
// lib/models/user.dart class User { final String id; final String name; final int age; User(this.id, this.name, this.age); // 转换为Map用于序列化 Map<String, dynamic> toMap() { return { 'id': id, 'name': name, 'age': age, }; } // 从Map重建对象 static User fromMap(Map<String, dynamic> map) { return User( map['id'], map['name'], map['age'], ); } } // lib/user_service.dart class UserService { static const MethodChannel _channel = MethodChannel('com.example/user'); Future<User> getUser(String userId) async { final result = await _channel.invokeMethod('getUser', {'userId': userId}); return User.fromMap(result); } }Android端(Kotlin):
// User.kt data class User(val id: String, val name: String, val age: Int) // MainActivity.kt when (call.method) { "getUser" -> { val userId = call.argument<String>("userId") val user = fetchUserFromDatabase(userId) result.success(mapOf( "id" to user.id, "name" to user.name, "age" to user.age )) } } private fun fetchUserFromDatabase(userId: String?): User { // 模拟数据库查询 return User(userId ?: "default", "张三", 25) }iOS端(Swift):
// AppDelegate.swift if call.method == "getUser" { if let userId = call.arguments as? [String: Any]?["userId"] as? String { let user = fetchUserFromDatabase(userId: userId) result([ "id": user.id, "name": user.name, "age": user.age ]) } else { result(FlutterError(code: "INVALID_ARGS", message: "缺少userId参数", details: nil)) } } private func fetchUserFromDatabase(userId: String) -> (id: String, name: String, age: Int) { return (id: userId, name: "张三", age: 25) }5.2 传递二进制数据(图片处理)
Dart端:
// lib/image_processor.dart class ImageProcessor { static const MethodChannel _channel = MethodChannel('com.example/image_processor'); Future<Uint8List> applyFilter(Uint8List imageBytes) async { final result = await _channel.invokeMethod( 'applyFilter', {'image': imageBytes} ); return result; } }Android端(Kotlin):
when (call.method) { "applyFilter" -> { val imageBytes = call.argument<ByteArray>("image") val filteredImage = applyFilterToImage(imageBytes) result.success(filteredImage) } } private fun applyFilterToImage(imageBytes: ByteArray?): ByteArray { // 实际图像处理逻辑 return imageBytes ?: ByteArray(0) }iOS端(Swift):
if call.method == "applyFilter" { if let imageData = call.arguments as? [String: Any]?["image"] as? FlutterStandardTypedData { let filteredImage = applyFilterToImage(imageData: imageData.data) result(filteredImage) } } private func applyFilterToImage(imageData: Data) -> FlutterStandardTypedData { // 实际图像处理逻辑 return FlutterStandardTypedData(bytes: imageData) }六、最佳实践与避坑指南
6.1 必须遵循的5大原则
线程安全:原生代码必须在主线程执行UI操作
// Android正确做法 Handler(Looper.getMainLooper()).post { // UI操作 }// iOS正确做法 DispatchQueue.main.async { // UI操作 }错误处理:必须处理所有可能的异常情况
// Dart端 try { final result = await channel.invokeMethod('someMethod'); } on PlatformException catch (e) { // 处理原生异常 } catch (e) { // 处理其他异常 }资源管理:及时释放EventChannel资源
@override void dispose() { _subscription?.cancel(); // 取消流订阅 super.dispose(); }权限处理:提前检查并请求必要权限
// Dart端检查权限 if (await Permission.location.isGranted) { // 可以调用位置相关方法 } else { // 请求权限 await Permission.location.request(); }版本兼容:考虑不同平台版本的API差异
// Android检查API级别 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 使用新API } else { // 使用旧API }
6.2 常见问题解决方案
❓ 问题1:MethodChannel调用无响应
原因:原生端未正确注册Channel或方法名不匹配
解决方案:
- 检查Channel名称是否完全一致(包括大小写)
- 确保在
configureFlutterEngine(Android)或application:didFinishLaunchingWithOptions(iOS)中注册 - 使用日志确认原生方法是否被调用
// Android添加日志 Log.d("FlutterChannel", "Method called: ${call.method}")// iOS添加日志 print("FlutterChannel: Method called \(call.method)")❓ 问题2:EventChannel监听不到事件
原因:EventSink未正确设置或未启动事件流
解决方案:
- 确认
onListen方法被调用 - 检查EventSink是否为null
- 验证原生端是否真正发送了事件
// Android检查EventSink override fun onListen(arguments: Any?, events: EventChannel.EventSink) { Log.d("Location", "监听开始") eventSink = events // 确保启动了位置更新 startLocationUpdates() }❓ 问题3:数据类型转换错误
原因:Dart与原生平台数据类型不匹配
解决方案:
- 使用标准数据类型(int, double, String, List, Map)
- 复杂对象转换为Map进行传递
- 避免使用平台特有类型
| Dart类型 | Android对应类型 | iOS对应类型 |
|---|---|---|
| int | Long | NSNumber (integer) |
| double | Double | NSNumber (double) |
| String | String | NSString |
| bool | Boolean | NSNumber (bool) |
| List | ArrayList | NSArray |
| Map | HashMap | NSDictionary |
七、安全性考虑
7.1 安全风险与防护
| 风险 | 防护措施 |
|---|---|
| 敏感数据泄露 | 避免通过Channel传递敏感信息(如token) |
| 未授权调用 | 验证调用来源(如检查包签名) |
| 恶意输入 | 严格验证输入参数 |
| DoS攻击 | 限制调用频率和参数大小 |
安全防护代码示例(Android):
// 验证调用来源 if (!isCallerValid(flutterEngine.dartExecutor)) { result.error("SECURITY_ERROR", "非法调用", null) return } private fun isCallerValid(executor: DartExecutor): Boolean { val callingPackage = executor.flutterLoader.applicationContext.packageName return callingPackage == "com.example.myapp" // 验证包名 }安全防护代码示例(iOS):
// 验证调用来源 if Bundle.main.bundleIdentifier != "com.example.myapp" { result(FlutterError(code: "SECURITY_ERROR", message: "非法调用", details: nil)) return }7.2 敏感操作保护
// Dart端添加安全检查 Future<void> sensitiveOperation() async { // 检查是否在安全环境 if (!await isSecureEnvironment()) { throw Exception("非安全环境"); } // 执行敏感操作 await _channel.invokeMethod('sensitiveOperation'); } Future<bool> isSecureEnvironment() async { // 检查是否 rooted/jailbroken final isRooted = await _channel.invokeMethod<bool>('isDeviceRooted'); // 检查调试状态 final isDebugging = await _channel.invokeMethod<bool>('isDebugging'); return !(isRooted ?? false) && !(isDebugging ?? false); }八、性能优化技巧
8.1 减少跨平台调用次数
❌ 低效写法:
// 每次循环都调用原生方法 for (var i = 0; i < 100; i++) { await channel.invokeMethod('processData', i); }✅ 高效写法:
// 批量处理数据 final result = await channel.invokeMethod( 'processBatch', {'data': List.generate(100, (i) => i)} );8.2 避免在动画帧中调用原生方法
❌ 严重卡顿:
// 在动画回调中频繁调用 AnimationController( vsync: this, duration: const Duration(seconds: 1), )..addListener(() { channel.invokeMethod('updatePosition', _controller.value); });✅ 优化方案:
// 使用防抖限制调用频率 final _debouncer = Debouncer(const Duration(milliseconds: 100)); AnimationController( vsync: this, duration: const Duration(seconds: 1), )..addListener(() { _debouncer.run(() { channel.invokeMethod('updatePosition', _controller.value); }); }); // 防抖工具类 class Debouncer { final Duration delay; Timer? _timer; Debouncer(this.delay); void run(VoidCallback action) { _timer?.cancel(); _timer = Timer(delay, action); } }8.3 大数据传输优化
传输1MB图片的优化:
// 未优化:直接传输原始图片 channel.invokeMethod('processImage', imageBytes); // 优化1:压缩后再传输 final compressed = await compressImage(imageBytes, quality: 75); channel.invokeMethod('processImage', compressed); // 优化2:使用二进制传输(BasicMessageChannel) final binaryChannel = BasicMessageChannel<Uint8List>( 'com.example/image', const BinaryCodec(), ); await binaryChannel.send(imageBytes);九、替代方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Platform Channels | 官方支持、灵活、功能全面 | 需要编写原生代码 | 通用场景 |
| Flutter Plugins | 社区支持、开箱即用 | 可能不满足定制需求 | 常见功能 |
| FFI (Dart FFI) | 高性能、直接调用C代码 | 仅限C/C++、复杂 | 高性能计算 |
| Pigeon | 类型安全、减少样板代码 | 需要额外配置 | 大型项目 |
| WebViews | 快速集成Web内容 | 性能较差 | Web内容集成 |
Pigeon实战示例(类型安全的Channel)
// lib/messages.dart import 'package:pigeon/pigeon.dart'; @HostApi() abstract class Api { Location getCurrentLocation(); User getUser(String id); } class Location { double? latitude; double? longitude; } class User { String? id; String? name; int? age; }生成代码:
flutter pub run pigeon \ --input lib/messages.dart \ --dart_out lib/messages.dart \ --objc_header_out ios/Runner/messages.h \ --objc_source_out ios/Runner/messages.m \ --java_out android/app/src/main/java/com/example/messages.java \ --java_package "com.example"使用示例:
// 自动生成的Dart代码 final api = Api(); final location = await api.getCurrentLocation();✅优势:编译时类型检查,减少运行时错误
十、完整项目结构建议
10.1 推荐项目结构
my_app/ ├── lib/ │ ├── channels/ # Platform Channels相关 │ │ ├── battery_channel.dart │ │ ├── location_channel.dart │ │ └── user_channel.dart │ ├── models/ # 数据模型 │ │ ├── battery.dart │ │ ├── location.dart │ │ └── user.dart │ ├── services/ # 业务服务 │ │ ├── battery_service.dart │ │ ├── location_service.dart │ │ └── user_service.dart │ └── main.dart ├── android/ # Android原生代码 │ └── app/ │ └── src/ │ └── main/ │ ├── java/com/example/ │ │ ├── BatteryChannel.kt │ │ ├── LocationChannel.kt │ │ └── UserChannel.kt │ └── AndroidManifest.xml └── ios/ # iOS原生代码 └── Runner/ ├── AppDelegate.swift ├── BatteryChannel.swift ├── LocationChannel.swift └── UserChannel.swift10.2 代码复用技巧
共享Channel名称常量:
// lib/constants/channel_names.dart class ChannelNames { static const battery = 'com.example/battery'; static const location = 'com.example/location'; static const user = 'com.example/user'; }Android与iOS共用测试用例:
// test/channel_integration_test.dart void main() { group('Battery Channel', () { test('should get battery level', () async { final batteryService = BatteryService(); final level = await batteryService.getBatteryLevel(); expect(level, isNonNegative); expect(level, lessThanOrEqualTo(100)); }); }); group('Location Channel', () { test('should receive location updates', () async { final locationService = LocationService(); final location = await locationService.locationStream.first; expect(location.latitude, isNonNegative); expect(location.longitude, isNonNegative); }); }); }十一、总结与学习路径
🏆 核心要点回顾
- Platform Channels是Flutter与原生交互的基石
- MethodChannel用于方法调用,EventChannel用于事件流
- 必须处理线程、错误、资源释放等关键问题
- 安全性和性能是生产环境必须考虑的因素
- Pigeon等工具可提升开发体验和代码质量
📚 学习路径建议
| 阶段 | 学习内容 | 目标 |
|---|---|---|
| 入门 | 基础Channel使用 | 实现简单功能调用 |
| 进阶 | 复杂数据传递、错误处理 | 构建稳定可靠的交互 |
| 专家 | 性能优化、安全防护 | 生产级应用集成 |
| 大师 | FFI、Pigeon、自定义引擎 | 深度定制与优化 |
🔮 未来趋势
- 更安全的通信机制:Flutter 3.0+加强了Channel安全性
- 更高效的跨平台通信:减少序列化开销
- 更好的工具支持:IDE自动补全、类型检查
- Pigeon成为官方推荐:逐步替代手动Channel管理
十二、资源推荐
📚 官方文档
- Flutter Platform Channels
- Pigeon文档
- Dart FFI
💻 实用工具
- Flutter Channel Tester
- Pigeon Generator
- Flutter Native Integration Template
📦 示例代码
- GitHub仓库:https://github.com/flutter-expert/flutter-native-integration
- 包含完整Android/iOS双平台实现
- 5个实用场景示例(电池、位置、摄像头、蓝牙、NFC)
- 安全防护与性能优化技巧