乐于分享
好东西不私藏

Flutter跨平台插件开发与发布Pub实战

Flutter跨平台插件开发与发布Pub实战

一、 环境准备与插件项目初始化

做什么:在开始编写代码前,我们需要搭建好开发环境并使用Flutter命令创建一个包含多平台配置的插件模板项目。

怎么做:请确保你已经安装了Flutter SDK,并配置了Android Studio、Xcode(macOS)以及Visual Studio(Windows包含C++桌面开发工作负载)。打开终端,使用`flutter create`命令并指定`--template=plugin`来生成插件。为了同时支持Android、iOS和Windows,我们必须通过`--platforms`参数显式声明。

为什么:Flutter默认的插件模板可能只包含Android和iOS。显式指定平台参数可以确保Flutter CLI自动为我们生成对应平台的底层原生通信代码和配置文件,省去手动建立目录和修改配置的繁琐工作。

bash
# 进入你的工作目录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捕获),能让使用你插件的开发者获得最佳体验。

dart
// 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发来的消息。

kotlin
// 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_SERVICEas 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的浮点数)。

swift
// 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`字段。

cpp
// 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通信无异常的必要步骤。

dart
// 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,从而被更多开发者发现和使用。

yaml
# pubspec.yamlnamebattery_plugindescription一个用于获取设备电池电量的跨平台Flutter插件,支持Android, iOS和Windows。version0.0.1# 初始版本号,每次发布必须递增homepagehttps://github.com/yourname/battery_plugin# 你的开源仓库地址environment:sdk'>=2.19.0<4.0.0'flutter">=3.3.0"dependencies:flutter:sdkflutterplugin_platform_interface^2.0.2# 跨平台接口必备依赖dev_dependencies:flutter_test:sdkflutterflutter_lints^2.0.0# 声明这是一个Flutter插件及其支持的平台flutter:plugin:platforms:android:packagecom.example.battery_pluginpluginClassBatteryPluginPluginios:pluginClassBatteryPluginPluginwindows:pluginClassBatteryPluginPlugin

八、 将插件发布上传至pub.dev

做什么:将经过本地测试且无报错的插件正式上传到Flutter官方包仓库。

怎么做:打开终端,确保当前路径位于插件根目录。首先执行`flutter pub publish --dry-run`检查包内容。如果没有报错,执行`flutter pub publish`命令。系统会自动打开浏览器要求你使用Google账号授权pub.dev的上传权限。授权完成后,终端将自动完成上传。

为什么:`--dry-run`可以防止你在不知情的情况下上传错误的文件或失败的测试。授权步骤是pub.dev保障包所有者身份安全的必要机制。上传成功后,全球开发者就可以通过`flutter pub add`使用你的插件了。

bash
# 确保所有的静态分析通过 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,你都可以按照这套架构游刃有余地进行扩展。感谢你的阅读,快去开发你的第一个跨平台插件吧!