Flutter包开发与插件发布—Federated Plugin架构与pub.dev发布|新宇宙博客
Back to listFlutter 包开发与插件发布
Site Owner
Published on 2026-05-22
详解Flutter Package与Plugin区别、Federated Plugin架构设计、Platform Interface抽象及pub.dev发布规范
Flutter 包开发与插件发布 (Package & Plugin Development)
模块 :模块十 · 架构与工程化
前置知识 :Platform Channel、Dart 语言、pub.dev 基础
Package vs Plugin:本质区别
Package(纯 Dart 包) Plugin(含原生代码的包)
┌─────────────────────┐ ┌──────────────────────────────┐
│ Dart 代码 │ │ Dart API 层 │
│ lib/ │ │ lib/ │
│ *.dart │ │ *.dart │
│ test/ │ ├──────────────────────────────┤
│ pubspec.yaml │ │ Android(Kotlin/Java) │
│ │ │ android/ │
│ 例子: │ │ ... │
│ - dio(HTTP) │ ├──────────────────────────────┤
│ - riverpod │ │ iOS(Swift/ObjC) │
│ - freezed │ │ ios/ │
│ - json_serializable│ │ ... │
└─────────────────────┘ ├──────────────────────────────┤
│ macOS / Windows / Linux │
│ macos/ windows/ linux/ │
└──────────────────────────────┘
例子:
- camera(相机)
- geolocator(GPS)
- url_launcher(打开 URL)
Federated Plugin:多平台拆分架构
大型 Plugin 通常采用 Federated Plugin 架构,将不同平台的实现拆分为独立包:
my_camera (用户安装这一个包)
│
├── my_camera_platform_interface (抽象层,定义 Dart 接口)
│ └── CameraPlatform (abstract class)
│
├── my_camera_android (Android 实现)
│ └── CameraAndroid extends CameraPlatform
│
├── my_camera_ios (iOS 实现)
│ └── CameraIOS extends CameraPlatform
│
└── my_camera_web (Web 实现)
└── CameraWeb extends CameraPlatform
平台团队可以独立维护各平台实现
用户只引用 App-facing 包,不需要知道哪个包处理哪个平台
平台实现可以被第三方替换(如替换 Google Maps 为其他地图)
创建一个 Federated Plugin
第一步:创建包结构
flutter create --template=plugin my_battery
cd my_battery
flutter create --template=package my_battery_platform_interface
flutter create --template=plugin_ffi my_battery_android
flutter create --template=plugin my_battery_android \
--platforms=android
flutter create --template=plugin my_battery_ios \
--platforms=ios
// my_battery_platform_interface/lib/my_battery_platform_interface.dart
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
// 使用 plugin_platform_interface 防止非法继承(必须用 implements,不能 extends)
abstract class BatteryPlatform extends PlatformInterface {
BatteryPlatform() : super(token: _token);
// 防止直接 extends(强制使用 implements 或 MockPlatformInterfaceMixin)
static final Object _token = Object();
// 默认实现:抛出未实现错误
static BatteryPlatform _instance = _DefaultBatteryPlatform();
static BatteryPlatform get instance => _instance;
static set instance(BatteryPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
// 定义平台 API(抽象方法)
Future<int> getBatteryLevel() {
throw UnimplementedError('getBatteryLevel() 未实现');
}
Stream<BatteryState> get onBatteryStateChanged {
throw UnimplementedError('onBatteryStateChanged 未实现');
}
}
// 兜底实现(未安装平台包时使用)
class _DefaultBatteryPlatform extends BatteryPlatform {}
// 状态枚举
enum BatteryState { full, charging, discharging, unknown }
第三步:App-facing 包(API 入口) // my_battery/lib/my_battery.dart
// 这是用户调用的 API,内部代理到 Platform Interface
import 'package:my_battery_platform_interface/my_battery_platform_interface.dart';
export 'package:my_battery_platform_interface/my_battery_platform_interface.dart'
show BatteryState;
class Battery {
const Battery._();
static const Battery _instance = Battery._();
factory Battery() => _instance;
/// 获取电量百分比(0-100)
Future<int> get batteryLevel => BatteryPlatform.instance.getBatteryLevel();
/// 监听电池状态变化
Stream<BatteryState> get onBatteryStateChanged =>
BatteryPlatform.instance.onBatteryStateChanged;
}
第四步:Android 实现
package com.example.my_battery_android
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
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
class MyBatteryPlugin : FlutterPlugin , MethodChannel.MethodCallHandler {
private lateinit var methodChannel: MethodChannel
private lateinit var eventChannel: EventChannel
private lateinit var context: Context
override fun onAttachedToEngine (binding: FlutterPlugin .FlutterPluginBinding ) {
context = binding.applicationContext
methodChannel = MethodChannel(binding.binaryMessenger, "my_battery_android" )
methodChannel.setMethodCallHandler(this )
eventChannel = EventChannel(binding.binaryMessenger, "my_battery_android/events" )
eventChannel.setStreamHandler(BatteryStateStreamHandler(context))
}
override fun onMethodCall (call: MethodCall , result: MethodChannel .Result ) {
when (call.method) {
"getBatteryLevel" -> {
val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
if (batteryLevel != Int .MIN_VALUE) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE" , "无法获取电量" , null )
}
}
else -> result.notImplemented()
}
}
override fun onDetachedFromEngine (binding: FlutterPlugin .FlutterPluginBinding ) {
methodChannel.setMethodCallHandler(null )
}
}
class BatteryStateStreamHandler (private val context: Context) : EventChannel.StreamHandler {
private var broadcastReceiver: android.content.BroadcastReceiver? = null
override fun onListen (arguments: Any ?, events: EventChannel .EventSink ?) {
broadcastReceiver = object : android.content.BroadcastReceiver() {
override fun onReceive (context: Context ?, intent: Intent ?) {
val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1 ) ?: -1
val state = when (status) {
BatteryManager.BATTERY_STATUS_CHARGING -> "charging"
BatteryManager.BATTERY_STATUS_DISCHARGING -> "discharging"
BatteryManager.BATTERY_STATUS_FULL -> "full"
else -> "unknown"
}
events?.success(state)
}
}
context.registerReceiver(
broadcastReceiver,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
}
override fun onCancel (arguments: Any ?) {
context.unregisterReceiver(broadcastReceiver)
broadcastReceiver = null
}
}
// my_battery_android/lib/my_battery_android.dart
// Android 实现:注册到 Platform Interface
import 'package:my_battery_platform_interface/my_battery_platform_interface.dart';
class BatteryAndroid extends BatteryPlatform {
static void registerWith() {
// 注册为默认实现
BatteryPlatform.instance = BatteryAndroid();
}
static const MethodChannel _methodChannel = MethodChannel('my_battery_android');
static const EventChannel _eventChannel = EventChannel('my_battery_android/events');
@override
Future<int> getBatteryLevel() async {
final int level = await _methodChannel.invokeMethod('getBatteryLevel');
return level;
}
@override
Stream<BatteryState> get onBatteryStateChanged {
return _eventChannel.receiveBroadcastStream().map((state) {
return switch (state as String) {
'charging' => BatteryState.charging,
'discharging' => BatteryState.discharging,
'full' => BatteryState.full,
_ => BatteryState.unknown,
};
});
}
}
第五步:iOS 实现
import Flutter
import UIKit
public class MyBatteryPlugin : NSObject , FlutterPlugin {
private var eventSink: FlutterEventSink ?
public static func register (with registrar : FlutterPluginRegistrar ) {
let methodChannel = FlutterMethodChannel (
name: "my_battery_ios" ,
binaryMessenger: registrar.messenger()
)
let eventChannel = FlutterEventChannel (
name: "my_battery_ios/events" ,
binaryMessenger: registrar.messenger()
)
let instance = MyBatteryPlugin ()
registrar.addMethodCallDelegate(instance, channel: methodChannel)
eventChannel.setStreamHandler(instance)
}
public func handle (_ call : FlutterMethodCall , result : @escaping FlutterResult ) {
switch call.method {
case "getBatteryLevel" :
UIDevice .current.isBatteryMonitoringEnabled = true
let batteryLevel = UIDevice .current.batteryLevel
if batteryLevel < 0 {
result(FlutterError (code: "UNAVAILABLE" , message: "无法获取电量" , details: nil ))
} else {
result(Int (batteryLevel * 100 ))
}
default :
result(FlutterMethodNotImplemented )
}
}
}
extension MyBatteryPlugin : FlutterStreamHandler {
public func onListen (
withArguments arguments : Any ? ,
eventSink events : @escaping FlutterEventSink
) -> FlutterError ? {
eventSink = events
UIDevice .current.isBatteryMonitoringEnabled = true
NotificationCenter .default.addObserver(
self ,
selector: #selector (batteryStateDidChange),
name: UIDevice .batteryStateDidChangeNotification,
object: nil
)
return nil
}
public func onCancel (withArguments arguments : Any ? ) -> FlutterError ? {
NotificationCenter .default.removeObserver(self )
eventSink = nil
return nil
}
@objc private func batteryStateDidChange () {
let state: String
switch UIDevice .current.batteryState {
case .charging: state = "charging"
case .full: state = "full"
case .unplugged: state = "discharging"
default : state = "unknown"
}
eventSink? (state)
}
}
pubspec.yaml 版本约束语法
dependencies:
some_package: 1.2 .3
dio: ^5.3.0
flutter_bloc: '>=8.0.0 <9.0.0'
some_package: any
my_private_package:
git:
url: https://github.com/your-org/my_private_package.git
ref: main
my_local_package:
path: ../my_local_package
发布到 pub.dev
发布前检查清单
dart pub publish --dry-run
dart pub global activate pana
pana .
pubspec.yaml 完整配置 name: my_battery
description: >
A Flutter plugin to access battery information on Android and iOS.
Provides battery level and battery state stream.
version: 0.1 .0
homepage: https://github.com/your-org/my_battery
repository: https://github.com/your-org/my_battery
issue_tracker: https://github.com/your-org/my_battery/issues
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: '>=3.10.0'
dependencies:
flutter:
sdk: flutter
my_battery_platform_interface: ^0.1.0
my_battery_android: ^0.1.0
my_battery_ios: ^0.1.0
plugin_platform_interface: ^2.1.0
flutter:
plugin:
platforms:
android:
package: com.example.my_battery_android
pluginClass: MyBatteryPlugin
ios:
pluginClass: MyBatteryPlugin
发布流程
dart pub login
dart pub publish --dry-run
dart pub publish
pub.dev 评分机制(Pub Points) 满分 160 分,评分维度:
Follow Dart file conventions(30分)
├── pubspec.yaml 格式正确
├── README.md 存在且内容丰富
├── CHANGELOG.md 存在
└── LICENSE 文件存在
Provide documentation(20分)
├── 公共 API 有文档注释
└── 示例代码(example/ 目录)
Platform support(20分)
└── 声明支持的平台(Android/iOS/Web/macOS 等)
Pass static analysis(50分)
├── dart analyze 无错误
└── dart format 格式正确
Support up-to-date dependencies(40分)
└── 依赖包是最新版本
Plugin 最佳实践
错误处理规范 // 统一的错误类型
class BatteryException implements Exception {
const BatteryException(this.message, {this.code});
final String message;
final String? code;
@override
String toString() => 'BatteryException($code): $message';
}
// 在 Platform Interface 中转换 PlatformException
@override
Future<int> getBatteryLevel() async {
try {
return await _methodChannel.invokeMethod<int>('getBatteryLevel') ?? 0;
} on PlatformException catch (e) {
throw BatteryException(
e.message ?? '未知错误',
code: e.code,
);
}
}
API 设计原则 // ✅ 好的 Plugin API 设计
class Battery {
// 工厂构造函数(允许 Mock 和测试)
factory Battery() => Battery._();
const Battery._();
// 属性而非方法(当操作是读取状态时)
Future<int> get batteryLevel => BatteryPlatform.instance.getBatteryLevel();
// Stream 用于持续监听
Stream<BatteryState> get onBatteryStateChanged =>
BatteryPlatform.instance.onBatteryStateChanged;
}
// ❌ 糟糕的 API 设计
class BatteryManager {
// 静态方法无法 Mock
static Future<int> getBatteryLevel() async { ... }
// 不一致的命名
static Stream<int> batteryLevelStream() { ... }
static Future<String> getState() async { ... }
}
过关标准 ✅ 能解释 Federated Plugin 的三层结构(platform_interface + 平台实现 + app-facing)的设计意图
✅ 能用 pubspec.yaml 的 ^ 约束语法描述版本兼容范围
✅ 能开发一个支持 Android + iOS 的 Federated Plugin 并发布到 pub.dev
✅ 理解 PlatformInterface.verifyToken 为什么能防止非法继承
✅ 能通过 pana 工具检查并优化包的 pub.dev 评分
全系列总结 至此,Flutter 深度学习系列 32 篇 全部完成,覆盖内容:
模块 文章编号 核心主题 Dart 语言 01-05 类型系统、异步、泛型、Dart 3 Widget 体系 06-08 三棵树、生命周期、组合模式 布局渲染 09-11 约束模型、自定义绘制、Sliver 状态管理 12-14 InheritedWidget、方案对比、Riverpod 导航路由 15-16 Navigator 1.0/2.0、go_router 动画系统 17-19 隐式/显式动画、物理动画、页面转场 网络数据 20-22 HTTP、本地存储、Isolate 平台集成 23-25 Platform Channel、FFI、多端适配 性能优化 26-28 渲染分析、内存管理、编译优化 架构工程 29-32 Clean Architecture、测试、CI/CD、Plugin