前面写了关于uniapp中定义原生插件,这篇文章介在 Flutter 日常开发中,我们同样会遇到一个尴尬的局面:项目需要一个特定的原生能力,但 pub.dev 上要么没有现成的插件,要么已有的插件年久失修、无法满足需求。这时候,自己动手写一个原生插件就成了唯一的选择。
本文将以一个实际的场景------封装一个原生返回数据的方法------为例,带你走完从创建插件、编写原生代码到调试发布的完整流程。
一、先搞懂:Flutter 和原生到底是怎么"说话"的
在动手之前,有必要先理解 Flutter 与原生通信的底层机制。
Flutter 与 Native 的通信基于 Platform Channel ,本质上是一个 C/S 模型:Flutter 端作为 Client,iOS/Android 端作为 Host。Flutter 通过 MethodChannel 向 Native 发送方法调用,Native 收到消息后执行对应的原生代码,再将结果通过 Result 回调返回给 Flutter。
整个调用链路大致是这样的:
- Dart 层调用
invokeMethod,方法名和参数被序列化为二进制消息; - 消息通过 BinaryMessenger 发送到 Flutter 引擎的 C++ 层;
- 引擎根据 channel 名称查找注册的处理器,在原生主线程异步执行;
- 原生代码执行完毕后,通过
result回调返回数据,原路返回给 Dart 层。
理解了这个流程,后面的开发就只是"填空"而已。
二、创建插件项目:Package 还是 Plugin?
Flutter 生态中有两种"包",创建方式不同,用途也不同:
- Dart Package :纯 Dart 代码,比如
http、path,通过--template=package创建; - Plugin Package :包含原生代码,可以调用 Android/iOS/桌面平台 API,通过
--template=plugin创建。
我们这次要做的是原生插件,所以使用 plugin 模板。在终端执行:
bash
css
flutter create --org com.example --template=plugin --platforms=android,ios -a kotlin -i swift my_native_plugin
参数说明:
--org:组织标识,采用反向域名表示法,会用于包名和 Bundle ID;--platforms:指定支持的平台,这里选择 Android 和 iOS;-a:Android 开发语言,可选kotlin或java;-i:iOS 开发语言,可选swift或objc。
创建完成后,项目结构大致如下:
text
bash
my_native_plugin/
├── lib/
│ └── my_native_plugin.dart # Dart API 入口
├── android/ # Android 原生实现
├── ios/ # iOS 原生实现
├── example/ # 调试用的示例 App
└── pubspec.yaml
example 目录非常关键,它相当于插件的"沙盒",你可以在这里直接运行插件、验证功能,而不需要额外建一个项目来测试。
三、Dart 端:定义你的 API
Flutter 插件的设计模式遵循一个清晰的层次:公开 API 层 → 平台接口层 → 平台实现层。
打开 lib/my_native_plugin.dart,你会看到脚手架已经生成了三个关键文件:
my_native_plugin.dart:对外暴露的 API;my_native_plugin_platform_interface.dart:平台接口定义;my_native_plugin_method_channel.dart:基于 MethodChannel 的默认实现。
我们要做的是在这个框架里增加一个自定义方法。假设我们需要一个 getPlatformInfo 方法,返回当前平台信息:
dart
javascript
// lib/my_native_plugin.dart
import 'my_native_plugin_platform_interface.dart';
class MyNativePlugin {
Future<String?> getPlatformInfo() {
return MyNativePluginPlatform.instance.getPlatformInfo();
}
}
然后在 platform interface 中声明:
dart
javascript
// lib/my_native_plugin_platform_interface.dart
Future<String?> getPlatformInfo() {
throw UnimplementedError('getPlatformInfo() has not been implemented.');
}
最后在 method channel 实现中完成实际的调用:
dart
rust
// lib/my_native_plugin_method_channel.dart
@override
Future<String?> getPlatformInfo() async {
final version = await methodChannel.invokeMethod<String>('getPlatformInfo');
return version;
}
这种分层设计的好处是:如果未来要为某个平台提供纯 Dart 实现,只需要替换 platform interface 的 instance 即可,对外 API 完全不用改。
四、Android 端:Kotlin 实现
Android 原生代码位于 android/src/main/kotlin/.../MyNativePlugin.kt。脚手架已经生成了一个实现了 FlutterPlugin 接口的类,核心逻辑在 onMethodCall 方法中:
kotlin
kotlin
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformInfo") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
这里有几个关键点:
- 方法名必须与 Dart 端
invokeMethod的参数完全一致 ,否则会走到notImplemented; result.success()返回数据给 Dart 端;如果执行出错,使用result.error();- 如果需要访问 Activity(比如调用
moveTaskToBack退到桌面),插件类需要实现ActivityAware接口,并在onAttachedToActivity中获取 Activity 引用。
如果你要开发的是更复杂的 Android 插件,建议用 Android Studio 打开 example/android 目录进行开发,这样能获得完整的代码补全和 Gradle 同步支持。
五、iOS 端:Swift 实现
iOS 原生代码位于 ios/Classes/MyNativePlugin.swift,注册逻辑在 register(with registrar:) 中:
swift
swift
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "my_native_plugin",
binaryMessenger: registrar.messenger())
let instance = MyNativePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
方法处理在 handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) 中:
swift
less
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformInfo":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
调试 iOS 代码的一个关键技巧 :Xcode 不能直接打开插件目录下的 iOS 工程,必须先构建一次示例项目来生成符号链接。在 example 目录下执行:
bash
css
flutter build ios --no-codesign --config-only
然后用 Xcode 打开 example/ios/Runner.xcworkspace,在 Project Navigator 中通过 Pods/Development Pods/my_native_plugin/../../example/ios/.symlinks/plugins/my_native_plugin/ios/Classes 路径找到插件源码。
六、运行与调试
直接在 example 目录下运行项目:
bash
arduino
cd example
flutter run
示例 App 的 main.dart 中调用插件方法,你可以在控制台看到输出。建议在开发过程中充分利用 flutter run 的热重载能力------虽然原生代码的修改需要重新构建,但 Dart 层的调整可以即时生效。
调试原生代码时,Android 端可以使用 Android Studio 的 Debugger 附加到 Flutter 进程;iOS 端则在 Xcode 中正常断点调试即可。
七、发布到 pub.dev(可选)
如果你的插件具有通用性,可以发布到 pub.dev 供社区使用。发布前需要准备:
- 完善的
README.md、CHANGELOG.md、LICENSE; - 在
pubspec.yaml中填写homepage和version; - 执行
flutter pub publish --dry-run检查是否有警告或错误。
正式发布时,由于国内网络环境,通常需要配置终端代理或使用镜像源。执行 flutter pub publish --server=https://pub.dartlang.org,然后根据提示完成 Google 账号授权即可。
写在最后
开发 Flutter 原生插件本质上是在做"桥梁工程"------Dart 端定义清晰的 API 接口,原生端忠实地执行平台特定的逻辑,MethodChannel 负责两端的消息传递。脚手架已经帮你处理了 90% 的样板代码,只需要在预留的"插槽"里填入业务逻辑。
建议从最简单的功能开始(比如返回一个系统版本号),把整个链路跑通之后,再逐步增加复杂度。当理解了数据是如何从 Dart 流到 Kotlin/Swift 再流回来的,剩下的就只是查对应平台的 API 文档而已了,当然,现在鸿蒙也再flutter中得到了支持,但是需要更换增强的fluttersdk,后面会介绍如何写鸿蒙原生插件。