一、 环境准备与插件项目初始化
做什么:在开始编写代码前,我们需要搭建好开发环境并使用Flutter命令创建一个包含多平台配置的插件模板项目。
怎么做:请确保你已经安装了Flutter SDK,并配置了Android Studio、Xcode(macOS)以及Visual Studio(Windows包含C++桌面开发工作负载)。打开终端,使用`flutter create`命令并指定`--template=plugin`来生成插件。为了同时支持Android、iOS和Windows,我们必须通过`--platforms`参数显式声明。
为什么:Flutter默认的插件模板可能只包含Android和iOS。显式指定平台参数可以确保Flutter CLI自动为我们生成对应平台的底层原生通信代码和配置文件,省去手动建立目录和修改配置的繁琐工作。
# 进入你的工作目录cd ~/projects # 使用flutter create命令创建插件项目# -t plugin 表示创建插件模板# --platforms 指定要支持的平台# com.example.battery 为你插件的包名前缀,建议使用自己的域名倒排 fluttercreate--template=plugin--platforms=android,ios,windows-akotlin-iswift-ccppbattery_plugin # 进入项目目录查看生成的文件结构cd battery_plugin ls-R 二、 插件Dart端接口设计
做什么:编写对外暴露的Dart API。这部分代码是跨平台的,无论底层是哪个操作系统,Flutter应用调用的都是这套统一的接口。
怎么做:在生成的`lib/battery_plugin_method_channel.dart`和`lib/battery_plugin.dart`中编写代码。我们使用`MethodChannel`与原生端进行通信。定义一个方法名(例如`getBatteryLevel`),并通过`invokeMethod`触发底层调用。
为什么:Dart端是连接Flutter UI和原生底层的桥梁。保持Dart接口的纯洁性和良好的容错处理(如PlatformException捕获),能让使用你插件的开发者获得最佳体验。
// lib/battery_plugin_method_channel.dartimport'package:flutter/foundation.dart';import'package:flutter/services.dart';import'battery_plugin_platform_interface.dart';/// MethodChannel实现类,负责与原生端通信classBatteryPluginMethodChannelextends BatteryPluginPlatform {/// 创建名为 'battery_plugin' 的通信通道@visibleForTestingfinal methodChannel =const MethodChannel('battery_plugin');@override Future<int?> getBatteryLevel() async {try {// 通过invokeMethod调用原生端的 'getBatteryLevel' 方法final level =await methodChannel.invokeMethod<int>('getBatteryLevel');return level; } on PlatformException catch (e) {// 捕获原生端抛出的异常 debugPrint("Failed to get battery level: '${e.message}'.");returnnull; } }}// lib/battery_plugin.dartimport'battery_plugin_platform_interface.dart';classBatteryPlugin {/// 对外暴露的获取电量方法 Future<int?> getBatteryLevel() {return BatteryPluginPlatform.instance.getBatteryLevel(); }}三、 Android平台兼容实现 (Kotlin)
做什么:实现Android端获取电池电量的原生逻辑。
怎么做:打开`android/src/main/kotlin/你的包名/BatteryPluginPlugin.kt`。实现`onMethodCall`回调,当收到`getBatteryLevel`指令时,使用Android系统的`BatteryManager`获取当前电量百分比并返回。
为什么:每个操作系统的底层API完全不同。Android通过系统服务获取硬件信息,我们需要在对应的原生代码中注册对应的监听并处理Flutter发来的消息。
// android/src/main/kotlin/com/example/battery/BatteryPluginPlugin.ktpackage com.example.battery_pluginimport android.content.Contextimport android.os.BatteryManagerimport androidx.annotation.NonNullimport io.flutter.embedding.engine.plugins.FlutterPluginimport io.flutter.plugin.common.MethodCallimport io.flutter.plugin.common.MethodChannelimport io.flutter.plugin.common.MethodChannel.MethodCallHandlerimport io.flutter.plugin.common.MethodChannel.ResultclassBatteryPluginPlugin: FlutterPlugin, MethodCallHandler {privatelateinitvar channel : MethodChannelprivatelateinitvar context: ContextoverridefunonAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "battery_plugin") channel.setMethodCallHandler(this) context = flutterPluginBinding.applicationContext }overridefunonMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {if (call.method=="getBatteryLevel") {// 获取Android系统的BatteryManagerval batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager// 获取电量百分比 (0-100)val batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)if (batteryLevel !=-1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } else { result.notImplemented() } }overridefunonDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) }}四、 iOS平台兼容实现 (Swift)
做什么:实现iOS端获取电池电量的原生逻辑。
怎么做:打开`ios/Classes/BatteryPluginPlugin.swift`。重写`handle(_ call: FlutterMethodCall)`方法。使用iOS的`UIDevice`来获取电池信息。需要注意,iOS获取真实电量前必须开启`isBatteryMonitoringEnabled`。
为什么:iOS有严格的沙盒机制和API调用规范,电池状态属于受保护的用户隐私状态信息,需要开启监听开关才能读取到具体数值(返回值为0到1的浮点数)。
// ios/Classes/BatteryPluginPlugin.swiftimportFlutterimportUIKitpublicclassBatteryPluginPlugin: NSObject, FlutterPlugin {publicstaticfuncregister(with registrar: FlutterPluginRegistrar) {let channel = FlutterMethodChannel(name: "battery_plugin", binaryMessenger: registrar.messenger())let instance = BatteryPluginPlugin() registrar.addMethodCallDelegate(instance, channel: channel) }publicfunchandle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {switch call.method {case"getBatteryLevel":// 必须开启电池监控才能获取到真实电量 UIDevice.current.isBatteryMonitoringEnabled = truelet state = UIDevice.current.batteryStateif state == .unknown { result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available.", details: nil)) } else {// iOS电池电量返回的是0.0到1.0的浮点数,需转换为百分比整数let batteryLevel = Int(UIDevice.current.batteryLevel *100) result(batteryLevel) }default: result(FlutterMethodNotImplemented) } }}五、 Windows平台兼容实现 (C++)
做什么:实现Windows桌面端获取电池电量的底层逻辑。
怎么做:打开`windows/battery_plugin_plugin.cpp`。在`HandleMethodCall`方法中,判断方法名,如果匹配,则调用Windows系统API获取电源状态。Windows端需要引入``并使用`GetSystemPowerStatus`函数。
为什么:Windows桌面平台的硬件接口与移动端截然不同。在Windows中,系统电源状态是通过结构体`SYSTEM_POWER_STATUS`来表征的,我们需要解析这个结构体中的`BatteryLifePercent`字段。
// windows/battery_plugin_plugin.cpp#include"battery_plugin_plugin.h"#include<windows.h>#include<flutter/method_channel.h>#include<flutter/plugin_registrar_windows.h>#include<flutter/standard_method_codec.h>#include<memory>#include<sstream>namespace battery_plugin {voidBatteryPluginPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows *registrar) {auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>( registrar->messenger(), "battery_plugin",&flutter::StandardMethodCodec::GetInstance());auto plugin = std::make_unique<BatteryPluginPlugin>(); channel->SetMethodCallHandler( [plugin_pointer = plugin.get()](constauto&call, auto result) { plugin_pointer->HandleMethodCall(call, std::move(result)); }); registrar->AddPlugin(std::move(plugin));}BatteryPluginPlugin::BatteryPluginPlugin() {}BatteryPluginPlugin::~BatteryPluginPlugin() {}void BatteryPluginPlugin::HandleMethodCall(const flutter::MethodCall<flutter::EncodableValue>&method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {if (method_call.method_name().compare("getBatteryLevel") ==0) { SYSTEM_POWER_STATUS status;// 调用Windows API获取系统电源状态if (GetSystemPowerStatus(&status)) {// BatteryLifePercent 返回值是 0-100,255表示未知状态if (status.BatteryLifePercent !=255) { result->Success(flutter::EncodableValue((int)status.BatteryLifePercent)); } else { result->Error("UNAVAILABLE", "Battery level not available."); } } else { result->Error("SYSTEM_ERROR", "Failed to get system power status."); } } else { result->NotImplemented(); }}} // namespace battery_plugin六、 编写测试用例验证插件
做什么:在`example`项目中编写UI代码,调用我们刚刚开发的插件,在三个平台上分别进行验证。
怎么做:打开`example/lib/main.dart`,编写一个带有按钮和文本的简单界面。点击按钮时调用`BatteryPlugin().getBatteryLevel()`,并通过`setState`更新界面显示。连接你的Android/iOS手机或启动Windows桌面应用进行测试。
为什么:开发完成后必须进行功能验证。通过example项目可以以真实App的方式运行插件,这是确保MethodChannel通信无异常的必要步骤。
// example/lib/main.dartimport'package:flutter/material.dart';import'package:battery_plugin/battery_plugin.dart';void main() { runApp(const MyApp());}classMyAppextends StatelessWidget {const MyApp({super.key});@override Widget build(BuildContext context) {return MaterialApp( title: 'Plugin Demo', home: const BatteryHomePage(), ); }}classBatteryHomePageextends StatefulWidget {const BatteryHomePage({super.key});@override State<BatteryHomePage> createState() => _BatteryHomePageState();}class_BatteryHomePageStateextends State<BatteryHomePage> {final _batteryPlugin = BatteryPlugin();String _batteryLevel ='Unknown'; Future<void> _getBatteryLevel() async {final level =await _batteryPlugin.getBatteryLevel(); setState(() { _batteryLevel = level !=null?'$level%':'Failed to get battery level.'; }); }@override Widget build(BuildContext context) {return Scaffold( appBar: AppBar( title: const Text('跨平台插件测试'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('当前电量: $_batteryLevel', style: const TextStyle(fontSize: 24)),const SizedBox(height: 20), ElevatedButton( onPressed: _getBatteryLevel, child: const Text('获取设备电量'), ), ], ), ), ); }}七、 完善发布配置与文档
做什么:在正式上传到pub.dev之前,必须完善`pubspec.yaml`文件,提供清晰的README.md说明文档以及CHANGELOG.md版本记录。
怎么做:修改插件根目录下的`pubspec.yaml`,填写description、version、homepage等元数据。同时编写介绍插件功能和使用方法的README文件。然后运行`flutter pub publish --dry-run`进行发布前检查。
为什么:pub.dev有着严格的包质量评分体系。完善的描述、合法的版本号、详尽的文档和通过的代码静态分析,决定了你的插件能否获得高Pub Points,从而被更多开发者发现和使用。
# pubspec.yamlname: battery_plugindescription: 一个用于获取设备电池电量的跨平台Flutter插件,支持Android, iOS和Windows。version: 0.0.1# 初始版本号,每次发布必须递增homepage: https://github.com/yourname/battery_plugin# 你的开源仓库地址environment:sdk: '>=2.19.0<4.0.0'flutter: ">=3.3.0"dependencies:flutter:sdk: flutterplugin_platform_interface: ^2.0.2# 跨平台接口必备依赖dev_dependencies:flutter_test:sdk: flutterflutter_lints: ^2.0.0# 声明这是一个Flutter插件及其支持的平台flutter:plugin:platforms:android:package: com.example.battery_pluginpluginClass: BatteryPluginPluginios:pluginClass: BatteryPluginPluginwindows:pluginClass: BatteryPluginPlugin八、 将插件发布上传至pub.dev
做什么:将经过本地测试且无报错的插件正式上传到Flutter官方包仓库。
怎么做:打开终端,确保当前路径位于插件根目录。首先执行`flutter pub publish --dry-run`检查包内容。如果没有报错,执行`flutter pub publish`命令。系统会自动打开浏览器要求你使用Google账号授权pub.dev的上传权限。授权完成后,终端将自动完成上传。
为什么:`--dry-run`可以防止你在不知情的情况下上传错误的文件或失败的测试。授权步骤是pub.dev保障包所有者身份安全的必要机制。上传成功后,全球开发者就可以通过`flutter pub add`使用你的插件了。
# 确保所有的静态分析通过 flutteranalyze # 运行测试 flutter test# 执行发布前的模拟打包检查,这会验证pubspec.yaml和所有文件结构 flutterpubpublish--dry-run # 如果一切正常,执行真正的发布命令 flutterpubpublish # 如果遇到网络问题,可以配置国内或官方的镜像代理(可选)# export PUB_HOSTED_URL=https://pub.dev# export FLUTTER_STORAGE_BASE_URL=https://storage.googleapis.com✨ 至此,从零开始开发一款同时兼容Android、iOS、Windows三大平台的Flutter插件,并成功发布到pub.dev的完整流程就已经梳理完毕。核心关键在于使用统一的MethodChannel进行跨平台消息分发,并在各自的工程目录下编写原生实现。掌握了这套标准模板,未来无论是接入硬件蓝牙、串口通信,还是各种独特的底层SDK,你都可以按照这套架构游刃有余地进行扩展。感谢你的阅读,快去开发你的第一个跨平台插件吧!