引言
很多人把Flutter插件开发想得太复杂,其实它的本质就是把Dart语言转换成Java/Swift能识别的语言,再把原生平台的回应翻译回Dart。今天,我们将Flutter与原生平台交互的工作机制彻底搞明白。
为什么需要原生交互?
在正式开始之前,我们先思考一个问题:Flutter作为一个跨平台框架,它的跨平台能力边界在哪里?
实际上,Flutter的跨平台能力主要体现在UI渲染层。Dart代码编译后可以在Android和iOS上运行,Flutter引擎负责将Widget树渲染成对应的平台视图。但当我们想要访问平台某些特定功能时,比如:调用系统相机/相册、使用蓝牙或NFC等功能,这些情况下,我们就需要桥接原生代码。
一、Platform Channel:Flutter与原生交互的桥梁
1.1 核心架构
很多人以为Platform Channel就是一个简单的管道,其实它是三层协议栈 :Dart层,引擎层,平台层;让我们先通过一张架构图来加深理解Platform Channel的整体结构:
备注:
- MethodChannel:最常用的通道,用于方法调用
- EventChannel:用于从原生到Dart的事件流
- BasicMessageChannel:用于简单的消息传递,较少使用
- BinaryMessenger:底层消息传递机制,所有Channel都基于它
1.2 交互原理
具体步骤:
- Dart代码:你把需求用Dart语言写好,MethodChannel会将其转换成二进制数据格式
- Engine:通过Flutter引擎传递到对应平台
- Platform:Android/iOS接收到数据后进行拆解并理解其内容
- Native:Android/iOS端完成需求
- 返回结果:反向再来一遍上述流程
我们简单画个流程图,方便理解:
把方法名+参数编码成二进制 C->>B: 发送二进制消息 B->>E: 跨边界传输 Note over E: 步骤2:数据传递
通过JNI/FFI传递 E->>P: 分发到对应平台 P->>N: 调用原生方法 Note over N: 步骤3:本地执行
Java/Swift实际处理 N-->>P: 返回结果 P-->>E: 编码结果 E-->>B: 返回二进制 B-->>C: 接收响应 Note over C: 步骤4:解析
解码二进制为Dart对象 C-->>D: Future完成,返回结果
代码层面的流程:
dart
// Dart侧发起调用
Future<String> result = channel.invokeMethod('getBatteryLevel');
// 这行代码背后发生了什么?
// 1. invokeMethod将方法名和参数序列化为二进制消息
// 2. 通过BinaryMessenger.send()发送
// 3. 引擎收到消息,路由到对应平台
// 4. 平台侧处理消息,执行原生代码
// 5. 结果序列化后返回
// 6. Dart侧反序列化得到结果
1.3 数据编解码
如何跨语言传递复杂数据?不同编程语言有不同的数据类型系统,如何让Dart的List<Map<String, dynamic>>在Java和Swift中也能被理解?
Flutter使用标准化的消息编解码器:
- StandardMessageCodec:默认编解码器,支持基础类型和列表、字典
- JSONMessageCodec:使用JSON格式编码
- StringCodec:只处理字符串
- BinaryCodec:原始二进制数据
编码规则表:
| Dart类型 | Java/Kotlin类型 | Swift/OC类型 |
|---|---|---|
| null | null | nil |
| bool | java.lang.Boolean | NSNumber(numberWithBool:) |
| int | java.lang.Integer | NSNumber(numberWithInt:) |
| double | java.lang.Double | NSNumber(numberWithDouble:) |
| String | java.lang.String | NSString |
| Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
| Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
| Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
| Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
| List | java.util.ArrayList | NSArray |
| Map | java.util.HashMap | NSDictionary |
主要限制:
- 不支持自定义类的直接传递
- 复杂对象需要先转换为Map/List等基本类型组合
- 大数据量传输需要考虑性能问题
二、与Android原生交互
2.1 以获取电池电量为例
让我们从一个最简单的例子开始,了解完整的通信流程。
Dart侧代码
dart
import 'package:flutter/services.dart';
class BatteryPlugin {
// 1. 创建MethodChannel实例
// 约定好通道名称(必须与原生侧完全一致)
static const MethodChannel _channel =
MethodChannel('com.xxxx/battery');
// 2. 公开的Dart方法
static Future<int> getBatteryLevel() async {
try {
// 3. 调用原生方法
// invokeMethod的第一个参数是方法名(必须与原生侧对应)
final int level = await _channel.invokeMethod('getBatteryLevel');
return level;
} on PlatformException catch (e) {
// 4. 异常处理
print("获取电量失败: ${e.message}");
return -1;
}
}
}
// 使用
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ElevatedButton(
child: Text('获取电量'),
onPressed: () async {
// 调用插件方法
int level = await BatteryPlugin.getBatteryLevel();
if (level != -1) {
print('当前电量: $level%');
}
},
),
),
),
);
}
}
Android侧代码(Kotlin)
kotlin
package com.example.myapp
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
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
// 1. 定义通道名称(必须与Dart侧一致)
companion object {
private const val CHANNEL = "com.xxxx/battery"
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 2. 创建MethodChannel
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
// 3. 处理方法调用
when (call.method) {
"getBatteryLevel" -> {
// 4. 执行实际的原生代码
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
// 5. 返回成功结果
result.success(batteryLevel)
} else {
// 6. 返回错误结果
result.error(
"UNAVAILABLE",
"无法获取电量信息",
null
)
}
}
else -> {
// 7. 处理未实现的方法
result.notImplemented()
}
}
}
}
// 获取电量的实际实现
private fun getBatteryLevel(): Int {
return if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
// 旧版本Android的兼容处理
val intent = ContextWrapper(applicationContext)
.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
if (level == -1 || scale == -1) {
-1
} else {
(level * 100 / scale.toFloat()).toInt()
}
}
}
}
2.2 复杂参数传递:从Dart传递数据到Android
实际开发中,我们经常需要传递复杂参数。看看如何传递一个用户对象:
dart
// Dart侧
Future<void> saveUser(User user) async {
try {
// 将User对象转换为Map
Map<String, dynamic> userMap = {
'name': user.name,
'age': user.age,
'email': user.email,
'isVip': user.isVip,
};
await _channel.invokeMethod('saveUser', userMap);
} on PlatformException catch (e) {
print('保存用户失败: ${e.message}');
}
}
kotlin
// Android侧(Kotlin)
.setMethodCallHandler { call, result ->
when (call.method) {
"saveUser" -> {
// 获取参数
val arguments = call.arguments as? Map<*, *>
if (arguments != null) {
val name = arguments["name"] as? String
val age = arguments["age"] as? Int
val email = arguments["email"] as? String
val isVip = arguments["isVip"] as? Boolean
// 保存
val success = saveUserToStorage(name, age, email, isVip)
if (success) {
result.success(null)
} else {
result.error("SAVE_FAILED", "保存用户失败", null)
}
} else {
result.error("INVALID_ARGUMENTS", "参数格式错误", null)
}
}
}
}
2.3 异步操作处理:长时间运行的任务
有些原生操作可能耗时较长,比如下载文件、处理图像等,我们需要正确处理异步:
kotlin
// Android侧:使用协程处理异步任务
.setMethodCallHandler { call, result ->
when (call.method) {
"processImage" -> {
val imagePath = call.arguments as? String
// 启动协程处理耗时操作
CoroutineScope(Dispatchers.IO).launch {
try {
val processedPath = processImageHeavy(imagePath)
// 切回主线程返回结果
withContext(Dispatchers.Main) {
result.success(processedPath)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
result.error("PROCESS_FAILED", e.message, null)
}
}
}
// 注意:这里没有立即调用result方法,异步操作完成后才调用
}
}
}
dart
// Dart侧:添加超时处理
Future<String> processImage(String imagePath) async {
try {
// 设置超时时间
final result = await _channel.invokeMethod('processImage', imagePath)
.timeout(Duration(seconds: 10));
return result as String;
} on TimeoutException {
print('图片处理超时');
throw Exception('处理超时');
} on PlatformException catch (e) {
print('图片处理失败: ${e.message}');
throw Exception(e.message);
}
}
2.4 以Toast消息插件为例
让我们创建一个完整的Android Toast插件:
dart
// flutter_toast.dart
import 'package:flutter/services.dart';
class FlutterToast {
static const MethodChannel _channel =
MethodChannel('com.xxxx/toast');
// 显示短时Toast
static Future<void> showShort(String message) async {
try {
await _channel.invokeMethod('showShortToast', message);
} on PlatformException catch (e) {
print('显示Toast失败: ${e.message}');
}
}
// 显示长时Toast
static Future<void> showLong(String message) async {
try {
await _channel.invokeMethod('showLongToast', message);
} on PlatformException catch (e) {
print('显示Toast失败: ${e.message}');
}
}
// 显示自定义时长Toast
static Future<void> showCustom(String message, int duration) async {
try {
await _channel.invokeMethod('showCustomToast', {
'message': message,
'duration': duration,
});
} on PlatformException catch (e) {
print('显示Toast失败: ${e.message}');
}
}
}
kotlin
// Android侧 - MainActivity.kt
package com.example.myapp
import android.widget.Toast
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
companion object {
private const val CHANNEL = "com.xxxx/toast"
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
// 主线程执行UI操作
runOnUiThread {
when (call.method) {
"showShortToast" -> {
val message = call.arguments as? String
Toast.makeText(
this,
message ?: "",
Toast.LENGTH_SHORT
).show()
result.success(null)
}
"showLongToast" -> {
val message = call.arguments as? String
Toast.makeText(
this,
message ?: "",
Toast.LENGTH_LONG
).show()
result.success(null)
}
"showCustomToast" -> {
val arguments = call.arguments as? Map<*, *>
val message = arguments?.get("message") as? String
val duration = arguments?.get("duration") as? Int ?: 2000
// 自定义Toast
val toast = Toast.makeText(this, message ?: "", Toast.LENGTH_SHORT)
// Android的Toast不支持直接设置毫秒数,这是一个简单的案例明白如何用即可
// 实际项目中需要自定义View
toast.duration = if (duration > 2000) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
toast.show()
result.success(null)
}
else -> result.notImplemented()
}
}
}
}
}
三、与iOS原生交互
3.1 iOS与Android的差异
在开始iOS开发前,需要了解一些iOS与Android两端的差异:
| 维度 | Android (Kotlin/Java) | iOS (Swift/OC) |
|---|---|---|
| 项目结构 | MainActivity.kt | AppDelegate.swift |
| 管道注册 | configureFlutterEngine | application didFinishLaunchingWithOptions |
| 内存管理 | JVM自动垃圾回收 | ARC自动引用计数 |
| 平台特性 | Context对象 | UIViewController |
3.2 以获取iOS设备信息为例
iOS侧代码(Swift)
swift
// AppDelegate.swift
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 1. 获取FlutterViewController
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
// 2. 创建MethodChannel
let batteryChannel = FlutterMethodChannel(
name: "com.xxxx/battery",
binaryMessenger: controller.binaryMessenger
)
// 3. 设置方法处理器
batteryChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
// 4. 根据方法名分派处理
if call.method == "getBatteryLevel" {
self?.receiveBatteryLevel(result: result)
} else if call.method == "getDeviceInfo" {
self?.getDeviceInfo(result: result)
} else {
// 5. 未实现的方法
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// 获取电池电量
private func receiveBatteryLevel(result: FlutterResult) {
// 6. 获取设备对象
let device = UIDevice.current
// 7. 开启电池监控
device.isBatteryMonitoringEnabled = true
// 8. 获取电池状态
if device.batteryState == .unknown {
// 9. 无法获取电量
result(FlutterError(
code: "UNAVAILABLE",
message: "电池信息不可用",
details: nil
))
} else {
// 10. 计算电量百分比
let batteryLevel = Int(device.batteryLevel * 100)
result(batteryLevel)
}
}
// 获取设备信息
private func getDeviceInfo(result: FlutterResult) {
let device = UIDevice.current
// 11. 设备信息字典
let deviceInfo: [String: Any] = [
"name": device.name,
"model": device.model,
"systemName": device.systemName,
"systemVersion": device.systemVersion,
"identifierForVendor": device.identifierForVendor?.uuidString ?? "",
"batteryLevel": device.batteryLevel,
"batteryState": "\(device.batteryState)",
"isBatteryMonitoringEnabled": device.isBatteryMonitoringEnabled,
"proximityState": device.proximityState,
"isProximityMonitoringEnabled": device.isProximityMonitoringEnabled,
"orientation": "\(device.orientation)",
"isMultitaskingSupported": device.isMultitaskingSupported,
"userInterfaceIdiom": "\(device.userInterfaceIdiom)"
]
// 12. 返回结果
result(deviceInfo)
}
}
Dart侧代码
dart
// ios_device_info.dart
import 'package:flutter/services.dart';
class IOSDeviceInfo {
static const MethodChannel _channel =
MethodChannel('com.xxxx/battery');
// 获取iOS设备信息
static Future<Map<String, dynamic>> getDeviceInfo() async {
try {
final Map<dynamic, dynamic> info =
await _channel.invokeMethod('getDeviceInfo');
// 转换为明确的类型
return Map<String, dynamic>.from(info);
} on PlatformException catch (e) {
print('获取设备信息失败: ${e.message}');
return {};
}
}
// 获取电池电量
static Future<int> getBatteryLevel() async {
try {
final int level = await _channel.invokeMethod('getBatteryLevel');
return level;
} on PlatformException catch (e) {
print('获取电量失败: ${e.message}');
return -1;
}
}
}
3.3 iOS特有功能:使用Face ID/Touch ID
iOS的生物识别是一个很好的平台特定功能示例:
swift
// AppDelegate.swift 中添加
import LocalAuthentication
extension AppDelegate {
// 生物识别验证
private func authenticateWithBiometrics(result: @escaping FlutterResult) {
let context = LAContext()
var error: NSError?
// 1. 检查设备是否支持生物识别
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "请进行生物识别以继续"
// 2. 执行验证
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
localizedReason: reason) { success, authenticationError in
// 3. 主线程返回结果
DispatchQueue.main.async {
if success {
// 4. 验证成功
result(["success": true, "message": "验证成功"])
} else {
// 5. 验证失败
let errorMessage: String
if let laError = authenticationError as? LAError {
switch laError.code {
case .userCancel:
errorMessage = "用户取消"
case .userFallback:
errorMessage = "用户选择使用密码"
case .authenticationFailed:
errorMessage = "验证失败"
case .passcodeNotSet:
errorMessage = "未设置密码"
case .systemCancel:
errorMessage = "系统取消"
case .biometryNotAvailable:
errorMessage = "生物识别不可用"
case .biometryNotEnrolled:
errorMessage = "未录入生物信息"
case .biometryLockout:
errorMessage = "生物识别被锁定"
default:
errorMessage = "未知错误"
}
} else {
errorMessage = "未知错误"
}
result(FlutterError(
code: "AUTH_FAILED",
message: errorMessage,
details: nil
))
}
}
}
} else {
// 6. 设备不支持生物识别
let errorMessage = error?.localizedDescription ?? "设备不支持生物识别"
result(FlutterError(
code: "NOT_SUPPORTED",
message: errorMessage,
details: nil
))
}
}
// 在setMethodCallHandler中添加
// batteryChannel.setMethodCallHandler { [weak self] (call, result) in
// if call.method == "authenticateWithBiometrics" {
// self?.authenticateWithBiometrics(result: result)
// }
// }
}
dart
// dart侧
class BiometricAuth {
static const MethodChannel _channel =
MethodChannel('com.xxxx/battery');
static Future<Map<String, dynamic>> authenticate() async {
try {
final result = await _channel.invokeMethod('authenticateWithBiometrics');
return Map<String, dynamic>.from(result);
} on PlatformException catch (e) {
return {
'success': false,
'message': e.message ?? '验证失败',
'errorCode': e.code,
};
}
}
}
3.4 iOS与Android的兼容性处理
在实际插件开发中,我们经常需要处理平台之间差异:
dart
// 工具类
class PlatformUtils {
static bool get isAndroid => Platform.isAndroid;
static bool get isIOS => Platform.isIOS;
static bool get isMobile => Platform.isAndroid || Platform.isIOS;
// 平台特定的提示
static Future<void> showPlatformToast(String message) async {
if (isAndroid) {
await AndroidToast.showShort(message);
} else if (isIOS) {
// iOS没有Toast,使用其他方式
await _showIOSToastLike(message);
} else {
// Web或桌面端
print('Toast: $message');
}
}
static Future<void> _showIOSToastLike(String message) async {
// iOS上可以使用其他方式模拟Toast
// 比如使用SnackBar或自定义View
}
}
四、EventChannel
如何实现从原生到Dart的持续数据流?
传感器/位置/GPS] --> B[EventChannel.EventSink] B --> C[BinaryMessenger发送] end subgraph "Dart侧" C --> D[StreamController] D --> E[Stream] E --> F[StreamBuilder/widget] end style B fill:#9f9 style E fill:#f99
关键组件:
- EventSink:原生侧的数据发射器
- Stream:Dart侧的响应式数据流
- StreamHandler:连接两者的桥梁
4.1 EventChannel与MethodChannel的区别
| 维度 | MethodChannel | EventChannel |
|---|---|---|
| 通信方向 | 双向 | 主要是原生→Dart |
| 通信模式 | 请求-响应 | 事件流/数据流 |
| 使用场景 | 方法调用 | 实时数据 |
| 连接状态 | 短连接 | 长连接 |
| 性能影响 | 每次调用都建立连接 | 一次建立,多次传输 |
4.2 以监听传感器数据为例
Android侧(Kotlin)
kotlin
// SensorEventPlugin.kt
class SensorEventPlugin(private val context: Context) :
StreamHandler {
private var sensorManager: SensorManager? = null
private var accelerometerSensor: Sensor? = null
private var eventSink: EventChannel.EventSink? = null
companion object {
fun registerWith(registrar: Registrar) {
val channel = EventChannel(
registrar.messenger(),
"com.xxxx/sensor"
)
val plugin = SensorEventPlugin(registrar.context())
channel.setStreamHandler(plugin)
}
}
// 1. 监听
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
// 2. 初始化传感器
sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
accelerometerSensor = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
// 3. 注册传感器监听器
sensorManager?.registerListener(
sensorListener,
accelerometerSensor,
SensorManager.SENSOR_DELAY_NORMAL
)
}
// 4. 监听停止
override fun onCancel(arguments: Any?) {
// 5. 取消传感器监听
sensorManager?.unregisterListener(sensorListener)
sensorManager = null
accelerometerSensor = null
eventSink = null
}
// 6. 传感器监听器
private val sensorListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
// 7. 获取加速度数据
val x = it.values[0]
val y = it.values[1]
val z = it.values[2]
// 8. 发送到Dart
eventSink?.success(mapOf(
"x" to x,
"y" to y,
"z" to z,
"timestamp" to System.currentTimeMillis()
))
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// .....
}
}
}
Dart侧
dart
// sensor_stream.dart
import 'package:flutter/services.dart';
class SensorStream {
final EventChannel _channel = EventChannel('com.xxxx/sensor');
Stream<Map<String, dynamic>>? _stream;
// 获取传感器数据流
Stream<Map<String, dynamic>> get sensorStream {
_stream ??= _channel
.receiveBroadcastStream()
.map((data) => Map<String, dynamic>.from(data));
return _stream!;
}
// 使用
void startListening() {
sensorStream.listen((data) {
print('加速度: X=${data['x']}, Y=${data['y']}, Z=${data['z']}');
// 计算设备倾斜角度
double pitch = 180 * atan(data['x'] / sqrt(pow(data['y'], 2) + pow(data['z'], 2))) / pi;
double roll = 180 * atan(data['y'] / sqrt(pow(data['x'], 2) + pow(data['z'], 2))) / pi;
print('倾斜角度: Pitch=$pitch°, Roll=$roll°');
}, onError: (error) {
print('传感器错误: $error');
}, onDone: () {
print('传感器监听结束');
});
}
// 在Widget中使用
Widget buildSensorWidget() {
return StreamBuilder<Map<String, dynamic>>(
stream: sensorStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('X: ${data['x'].toStringAsFixed(2)}'),
Text('Y: ${data['y'].toStringAsFixed(2)}'),
Text('Z: ${data['z'].toStringAsFixed(2)}'),
// 展示
CustomPaint(
painter: AccelerometerPainter(data),
size: Size(200, 200),
),
],
);
} else if (snapshot.hasError) {
return Text('error: ${snapshot.error}');
} else {
return CircularProgressIndicator();
}
},
);
}
}
EventChannel实际应用场景如下:
- 加速度计、陀螺仪等
- GPS位置实时变化
- 网络连接状态变化
- 蓝牙设备发现和数据接收
- 下载进度实时更新
五、自定义插件
5.1 插件项目结构
一个标准的Flutter插件目录结构如下:
bash
flutter_custom_plugin/
├── android/ # Android平台代码
│ ├── build.gradle # Android构建配置
│ ├── src/main/
│ │ ├── kotlin/
│ │ │ └── com/xxxx/flutter_custom_plugin/
│ │ │ └── FlutterCustomPlugin.kt
│ │ └── AndroidManifest.xml
├── ios/ # iOS平台代码
│ ├── Classes/
│ │ └── FlutterCustomPlugin.swift
│ └── flutter_custom_plugin.podspec
├── lib/ # Dart库代码
│ ├── flutter_custom_plugin.dart
│ └── src/
│ └── implementation.dart
├── xxxx/ # 案例应用
│ ├── android/
│ ├── ios/
│ └── lib/main.dart
├── pubspec.yaml # 插件配置
└── README.md # 文档
5.2 以网络状态监听插件为例
第一步:创建插件项目
bash
flutter create --template=plugin --org=com.xxxx--platforms=android,ios flutter_network_plugin
cd flutter_network_plugin
第二步:编写Dart API
dart
// lib/flutter_network_plugin.dart
library flutter_network_plugin;
import 'dart:async';
import 'package:flutter/services.dart';
/// 网络类型枚举
enum NetworkType {
wifi,
mobile,
ethernet,
bluetooth,
vpn,
none,
unknown,
}
/// 网络状态类
class NetworkStatus {
final bool isConnected;
final NetworkType type;
final String? subtype;
final bool isRoaming;
final String? extraInfo;
NetworkStatus({
required this.isConnected,
required this.type,
this.subtype,
this.isRoaming = false,
this.extraInfo,
});
/// 从Map转换
factory NetworkStatus.fromMap(Map<dynamic, dynamic> map) {
return NetworkStatus(
isConnected: map['isConnected'] ?? false,
type: _parseNetworkType(map['type']),
subtype: map['subtype'],
isRoaming: map['isRoaming'] ?? false,
extraInfo: map['extraInfo'],
);
}
/// 转换为Map
Map<String, dynamic> toMap() {
return {
'isConnected': isConnected,
'type': type.toString().split('.').last,
'subtype': subtype,
'isRoaming': isRoaming,
'extraInfo': extraInfo,
};
}
/// 解析网络类型
static NetworkType _parseNetworkType(String? type) {
switch (type?.toLowerCase()) {
case 'wifi':
return NetworkType.wifi;
case 'mobile':
return NetworkType.mobile;
case 'ethernet':
return NetworkType.ethernet;
case 'bluetooth':
return NetworkType.bluetooth;
case 'vpn':
return NetworkType.vpn;
case 'none':
return NetworkType.none;
default:
return NetworkType.unknown;
}
}
@override
String toString() {
return 'NetworkStatus{isConnected: $isConnected, type: $type, subtype: $subtype}';
}
}
/// 网络状态插件
class FlutterNetworkPlugin {
static const MethodChannel _methodChannel =
MethodChannel('flutter_network_plugin/method');
static const EventChannel _eventChannel =
EventChannel('flutter_network_plugin/event');
/// 单例
static final FlutterNetworkPlugin _instance = FlutterNetworkPlugin._internal();
factory FlutterNetworkPlugin() => _instance;
FlutterNetworkPlugin._internal();
/// 获取当前网络状态
static Future<NetworkStatus> get currentStatus async {
try {
final Map<dynamic, dynamic> status =
await _methodChannel.invokeMethod('getCurrentStatus');
return NetworkStatus.fromMap(Map<String, dynamic>.from(status));
} on PlatformException catch (e) {
print('获取网络状态失败: ${e.message}');
return NetworkStatus(
isConnected: false,
type: NetworkType.unknown,
);
}
}
/// 检查是否有网络连接
static Future<bool> get isConnected async {
final status = await currentStatus;
return status.isConnected;
}
/// 网络状态变化流
static Stream<NetworkStatus> get onStatusChanged {
return _eventChannel
.receiveBroadcastStream()
.map((data) => NetworkStatus.fromMap(Map<String, dynamic>.from(data)))
.handleError((error) {
print('网络状态监听错误: $error');
});
}
/// 初始化插件
static Future<void> initialize() async {
try {
await _methodChannel.invokeMethod('initialize');
} on PlatformException catch (e) {
print('初始化插件失败: ${e.message}');
}
}
/// 释放资源
static Future<void> dispose() async {
try {
await _methodChannel.invokeMethod('dispose');
} on PlatformException catch (e) {
print('释放插件资源失败: ${e.message}');
}
}
}
第三步:实现Android端代码
kotlin
// android/src/main/kotlin/com/xxxx/flutter_network_plugin/FlutterNetworkPlugin.kt
package com.example.flutter_network_plugin
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import androidx.annotation.RequiresApi
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
/** FlutterNetworkPlugin */
class FlutterNetworkPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var context: Context
private lateinit var methodChannel: MethodChannel
private lateinit var eventChannel: EventChannel
// 事件发送器
private var eventSink: EventChannel.EventSink? = null
// 网络相关
private var connectivityManager: ConnectivityManager? = null
private var networkCallback: ConnectivityManager.NetworkCallback? = null
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
methodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
"flutter_network_plugin/method"
)
methodChannel.setMethodCallHandler(this)
eventChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
"flutter_network_plugin/event"
)
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
// 立即发送当前状态
sendCurrentStatus()
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
})
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
unregisterNetworkCallback()
}
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"getCurrentStatus" -> {
result.success(getCurrentNetworkStatus())
}
"initialize" -> {
initializeNetworkMonitoring()
result.success(null)
}
"dispose" -> {
unregisterNetworkCallback()
result.success(null)
}
else -> result.notImplemented()
}
}
/**
* 获取当前网络状态
*/
private fun getCurrentNetworkStatus(): Map<String, Any> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getNetworkStatusModern()
} else {
getNetworkStatusLegacy()
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun getNetworkStatusModern(): Map<String, Any> {
connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
val activeNetwork = connectivityManager?.activeNetwork
val networkCapabilities = connectivityManager?.getNetworkCapabilities(activeNetwork)
val isConnected = networkCapabilities != null &&
(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) ||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN))
val type = when {
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true -> "wifi"
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> "mobile"
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) == true -> "ethernet"
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) == true -> "bluetooth"
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true -> "vpn"
else -> "none"
}
return mapOf(
"isConnected" to isConnected,
"type" to type,
"isRoaming" to false,
"extraInfo" to null
)
}
@Suppress("DEPRECATION")
private fun getNetworkStatusLegacy(): Map<String, Any> {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
val activeNetworkInfo = cm.activeNetworkInfo
val isConnected = activeNetworkInfo?.isConnected == true
val type = when (activeNetworkInfo?.type) {
ConnectivityManager.TYPE_WIFI -> "wifi"
ConnectivityManager.TYPE_MOBILE -> "mobile"
ConnectivityManager.TYPE_ETHERNET -> "ethernet"
ConnectivityManager.TYPE_BLUETOOTH -> "bluetooth"
ConnectivityManager.TYPE_VPN -> "vpn"
else -> "none"
}
return mapOf(
"isConnected" to isConnected,
"type" to type,
"isRoaming" to activeNetworkInfo?.isRoaming ?: false,
"extraInfo" to activeNetworkInfo?.extraInfo
)
}
/**
* 初始化网络监控
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun initializeNetworkMonitoring() {
connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH)
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.build()
networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
sendCurrentStatus()
}
override fun onLost(network: Network) {
super.onLost(network)
sendCurrentStatus()
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
sendCurrentStatus()
}
}
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback!!)
}
/**
* 取消网络监控
*/
private fun unregisterNetworkCallback() {
networkCallback?.let {
connectivityManager?.unregisterNetworkCallback(it)
networkCallback = null
}
}
/**
* 发送当前状态到Dart
*/
private fun sendCurrentStatus() {
val status = getCurrentNetworkStatus()
eventSink?.success(status)
}
}
第四步:实现iOS端代码
swift
// ios/Classes/FlutterNetworkPlugin.swift
import Flutter
import Foundation
import SystemConfiguration
import Network
public class FlutterNetworkPlugin: NSObject, FlutterPlugin {
private var eventSink: FlutterEventSink?
private var monitor: NWPathMonitor?
private let queue = DispatchQueue(label: "NetworkMonitor")
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = FlutterNetworkPlugin()
// 方法通道
let methodChannel = FlutterMethodChannel(
name: "flutter_network_plugin/method",
binaryMessenger: registrar.messenger()
)
registrar.addMethodCallDelegate(instance, channel: methodChannel)
// 事件通道
let eventChannel = FlutterEventChannel(
name: "flutter_network_plugin/event",
binaryMessenger: registrar.messenger()
)
eventChannel.setStreamHandler(instance)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getCurrentStatus":
result(getCurrentNetworkStatus())
case "initialize":
initializeNetworkMonitoring()
result(nil)
case "dispose":
disposeNetworkMonitoring()
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
// 获取当前网络状态
private func getCurrentNetworkStatus() -> [String: Any] {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}
}) else {
return [
"isConnected": false,
"type": "none",
"isRoaming": false,
"extraInfo": nil
]
}
var flags: SCNetworkReachabilityFlags = []
if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
return [
"isConnected": false,
"type": "none",
"isRoaming": false,
"extraInfo": nil
]
}
let isReachable = flags.contains(.reachable)
let needsConnection = flags.contains(.connectionRequired)
let isConnected = isReachable && !needsConnection
var type = "none"
if flags.contains(.isWWAN) {
type = "mobile"
} else if flags.contains(.connectionRequired) {
// 需要连接但不可达
} else {
type = "wifi"
}
return [
"isConnected": isConnected,
"type": type,
"isRoaming": false,
"extraInfo": nil
]
}
// 初始化网络监控
@available(iOS 12.0, *)
private func initializeNetworkMonitoring() {
monitor = NWPathMonitor()
monitor?.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
let isConnected = path.status == .satisfied
var type = "none"
if path.usesInterfaceType(.wifi) {
type = "wifi"
} else if path.usesInterfaceType(.cellular) {
type = "mobile"
} else if path.usesInterfaceType(.wiredEthernet) {
type = "ethernet"
} else if path.usesInterfaceType(.loopback) {
type = "loopback"
} else if path.usesInterfaceType(.other) {
type = "other"
}
let status: [String: Any] = [
"isConnected": isConnected,
"type": type,
"isRoaming": false,
"extraInfo": nil
]
// 发送到Dart
DispatchQueue.main.async {
self.eventSink?(status)
}
}
monitor?.start(queue: queue)
}
// 释放网络监控
private func disposeNetworkMonitoring() {
monitor?.cancel()
monitor = nil
}
}
extension FlutterNetworkPlugin: FlutterStreamHandler {
public func onListen(withArguments arguments: Any?,
eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
// 立即发送当前状态
let status = getCurrentNetworkStatus()
events(status)
// 初始化监控
if #available(iOS 12.0, *) {
initializeNetworkMonitoring()
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
disposeNetworkMonitoring()
return nil
}
}
第五步:配置插件
yaml
# pubspec.yaml
name: flutter_network_plugin
description: A Flutter plugin for monitoring network status.
version: 1.0.0
homepage: https://github.com/xxxx/flutter_network_plugin
environment:
sdk: ">=2.17.0 <3.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
plugin:
platforms:
android:
package: com.example.flutter_network_plugin
pluginClass: FlutterNetworkPlugin
ios:
pluginClass: FlutterNetworkPlugin
第六步:编写使用案例
dart
// xxxx/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_network_plugin/flutter_network_plugin.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
NetworkStatus _currentStatus = NetworkStatus(
isConnected: false,
type: NetworkType.unknown,
);
StreamSubscription<NetworkStatus>? _subscription;
@override
void initState() {
super.initState();
_initNetworkPlugin();
}
Future<void> _initNetworkPlugin() async {
// 初始化插件
await FlutterNetworkPlugin.initialize();
// 获取当前状态
final status = await FlutterNetworkPlugin.currentStatus;
setState(() {
_currentStatus = status;
});
// 监听状态变化
_subscription = FlutterNetworkPlugin.onStatusChanged.listen((status) {
setState(() {
_currentStatus = status;
});
});
}
@override
void dispose() {
_subscription?.cancel();
FlutterNetworkPlugin.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('网络状态插件'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 连接状态指示器
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _currentStatus.isConnected ? Colors.green : Colors.red,
),
child: Icon(
_currentStatus.isConnected ? Icons.wifi : Icons.wifi_off,
size: 50,
color: Colors.white,
),
),
SizedBox(height: 20),
// 状态信息
Text(
_currentStatus.isConnected ? '已连接' : '未连接',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 10),
Text(
'网络类型: ${_currentStatus.type.toString().split('.').last}',
style: TextStyle(fontSize: 18),
),
if (_currentStatus.subtype != null)
Text(
'子类型: ${_currentStatus.subtype}',
style: TextStyle(fontSize: 16),
),
SizedBox(height: 30),
// 刷新按钮
ElevatedButton(
onPressed: () async {
final status = await FlutterNetworkPlugin.currentStatus;
setState(() {
_currentStatus = status;
});
},
child: Text('刷新状态'),
),
],
),
),
),
);
}
}
六、优化
6.1 性能优化技巧
1. 减少跨平台调用次数
dart
// 不推荐:多次调用
Future<void> saveUser(User user) async {
await _channel.invokeMethod('saveName', user.name);
await _channel.invokeMethod('saveAge', user.age);
await _channel.invokeMethod('saveEmail', user.email);
}
// 推荐
Future<void> saveUser(User user) async {
await _channel.invokeMethod('saveUser', {
'name': user.name,
'age': user.age,
'email': user.email,
});
}
2. 使用Isolate处理耗时任务
dart
Future<void> processLargeData(List<dynamic> data) async {
// 在Isolate中处理,避免阻塞UI
final result = await compute(_processInIsolate, data);
await _channel.invokeMethod('saveResult', result);
}
static List<dynamic> _processInIsolate(List<dynamic> data) {
// 耗时处理逻辑
return processedData;
}
3. 内存管理
kotlin
// Android侧:及时释放资源
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
eventSink?.endOfStream()
eventSink = null
}
6.2 错误处理
1. 统一的错误处理
dart
class PluginError implements Exception {
final String code;
final String message;
final dynamic details;
PluginError({
required this.code,
required this.message,
this.details,
});
@override
String toString() => 'PluginError($code): $message';
}
Future<T> safeInvoke<T>(String method, [dynamic arguments]) async {
try {
final result = await _channel.invokeMethod(method, arguments);
return result as T;
} on PlatformException catch (e) {
throw PluginError(
code: e.code,
message: e.message ?? 'Unknown error',
details: e.details,
);
} catch (e) {
throw PluginError(
code: 'UNKNOWN',
message: e.toString(),
details: e,
);
}
}
csharp
### 6.3 平台差异处理策略
#### 1. 功能检测与降级
```dart
class PlatformFeatureDetector {
static Future<bool> isFeatureAvailable(String feature) async {
try {
return await _channel.invokeMethod('checkFeature', feature);
} on PlatformException {
return false;
}
}
static Future<T> withFallback<T>({
required Future<T> Function() primary,
required Future<T> Function() fallback,
String? feature,
}) async {
if (feature != null && !await isFeatureAvailable(feature)) {
return await fallback();
}
try {
return await primary();
} catch (e) {
PluginLogger.error('Primary method failed', e);
return await fallback();
}
}
}
2. 条件编译
dart
// 使用条件导入处理平台差异
export 'src/network_plugin_interface.dart';
// 在platform目录下
// network_plugin_android.dart
// network_plugin_ios.dart
// network_plugin_web.dart
总结
至此,Flutter的插件开发以及如何与Android/iOS进行原生交互就全部介绍完了,我们掌握了如下知识点:
- Platform Channel原理-Flutter与原生平台通信的底层机制
- MethodChannel使用-双向方法调用的实现
- EventChannel-原生到Dart的数据流传输
- 自定义插件开发完整流程
- 处理Android/iOS的差异
- 提升插件性能的技巧
插件开发是Flutter开发的重要技能,能够帮助我们深入理解Flutter架构,充分利用各平台的能力。遇到问题时,多查阅官方文档,多调试。
如果这篇文章对你有帮助,别忘了一键三连~~~,有任何问题或建议,欢迎在评论区留言讨论。下期见!