Flutter 插件开发从零到发布:手把手带你写一个原生插件(附完整代码)
引言
你有没有遇到过这样的情况:
-
Flutter 找不到合适的插件,想自己写一个 -
用别人的插件总出问题,想自己接入原生 SDK -
想学插件开发,但文档看着像天书
今天这篇文章,我们就从零开始,手把手写一个 获取设备电池电量 的 Flutter 插件,覆盖 Android + iOS 双端,最后说说怎么发布到 pub.dev。
读完你能学到:
-
👉 Flutter 插件的通信原理(MethodChannel) -
👉 Android 原生代码怎么写 -
👉 iOS 原生代码怎么写 -
👉 如何封装成 Dart API 给别人用 -
👉 发布插件的关键步骤
正文
1. 使用场景
Flutter 插件适用于以下场景:
-
✔ 调用系统能力(电量、蓝牙、传感器) -
✔ 接入第三方原生 SDK(如推送、支付) -
✔ 封装已有的 Android/iOS 代码给 Flutter 使用 -
❌ 纯 UI 组件不需要用插件,直接用 Dart Package
2. 创建插件项目
用命令行创建标准插件结构:
flutter create --template=plugin --platforms=android,ios battery_info_plugin
创建后目录结构如下:
battery_info_plugin/├── lib/ ← Dart 层(给用户调用)├── android/ ← Android 原生代码├── ios/ ← iOS 原生代码├── example/ ← 示例 App(用来调试)└── pubspec.yaml
3. Dart 层:定义 API

先写好 Dart 接口,这是插件的”门面”。
lib/battery_info_plugin.dart:
import 'package:flutter/services.dart';class BatteryInfoPlugin {// 定义通信通道,名字要唯一(通常用包名)static const MethodChannel _channel = MethodChannel('com.example.battery_info_plugin',);/// 获取当前电量(0~100),失败返回 -1static Future<int> getBatteryLevel() async {try {final int level = await _channel.invokeMethod('getBatteryLevel');return level;} on PlatformException catch (e) {print('获取电量失败: ${e.message}');return -1;}}}
几个关键点:
–MethodChannel 是 Flutter 与原生通信的核心
-
通道名必须唯一,建议用反向域名– invokeMethod调用原生方法,方法名要和原生端一致
4. Android 端实现
打开如下文件
android/src/main/kotlin/…/BatteryInfoPlugin.kt:
package com.example.battery_info_pluginimport android.content.Contextimport android.content.Intentimport android.content.IntentFilterimport android.os.BatteryManagerimport android.os.Buildimport io.flutter.embedding.engine.plugins.FlutterPluginimport io.flutter.plugin.common.MethodCallimport io.flutter.plugin.common.MethodChannelclass BatteryInfoPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {private lateinit var channel: MethodChannelprivate lateinit var context: Context// 插件注册时调用override funonAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {context = binding.applicationContextchannel = MethodChannel(binding.binaryMessenger,"com.example.battery_info_plugin" // 和 Dart 端保持一致!)channel.setMethodCallHandler(this)}// 处理来自 Dart 的方法调用override funonMethodCall(call: MethodCall, result: MethodChannel.Result) {when (call.method) {"getBatteryLevel" -> {val level = getBatteryLevel()if (level != -1) {result.success(level)} else {result.error("UNAVAILABLE", "无法获取电量", null)}}else -> result.notImplemented()}}private fungetBatteryLevel(): Int {return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {val manager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManagermanager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)} else {// Android 4.x 兼容写法val intent = context.registerReceiver(null,IntentFilter(Intent.ACTION_BATTERY_CHANGED))val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1if (level == -1 || scale == -1) -1 else (level * 100 / scale)}}override funonDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {channel.setMethodCallHandler(null)}}
踩坑提醒 ⚠️:
通道名一定要和 Dart 端 完全一致(包括大小写),否则会静默失败,非常难排查!
5. iOS 端实现
打开如下文件
ios/Classes/BatteryInfoPlugin.swift:
import Flutterimport UIKitpublic class BatteryInfoPlugin: NSObject, FlutterPlugin {public static func register(with registrar: FlutterPluginRegistrar) {let channel = FlutterMethodChannel(name: "com.example.battery_info_plugin", // 和 Dart 端一致!binaryMessenger: registrar.messenger())let instance = BatteryInfoPlugin()registrar.addMethodCallDelegate(instance, channel: channel)}public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {switch call.method {case "getBatteryLevel":let level = getBatteryLevel()if level >= 0 {result(level)} else {result(FlutterError(code: "UNAVAILABLE",message: "无法获取电量(模拟器不支持)",details: nil))}default:result(FlutterMethodNotImplemented)}}private func getBatteryLevel() -> Int {UIDevice.current.isBatteryMonitoringEnabled = truelet level = UIDevice.current.batteryLevel// 模拟器返回 -1,真机返回 0.0~1.0guard level >= 0 else { return -1 }return Int(level * 100)}}
踩坑提醒 ⚠️:
iOS 模拟器不支持电量 API,
batteryLevel返回-1,调试时请用真机!
6. 在 Example App 中调试
打开 example/lib/main.dart,调用我们的插件:
import 'package:battery_info_plugin/battery_info_plugin.dart';class_MyAppStateextendsState<MyApp> {String _batteryInfo = '未知';Future<void> _fetchBattery() async {final level = await BatteryInfoPlugin.getBatteryLevel();setState(() {_batteryInfo = level >= 0 ? '$level%' : '获取失败';});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('电量插件测试')),body: Center(child: Column(mainAxisSize: MainAxisSize.min,children: [Text('当前电量:$_batteryInfo', style: const TextStyle(fontSize: 24)),const SizedBox(height: 16),ElevatedButton(onPressed: _fetchBattery,child: const Text('获取电量'),),],),),);}}
7. 发布到 pub.dev(进阶)
发布前需要完善:
pubspec.yaml 关键字段:
name: battery_info_plugindescription: A Flutter plugin to get battery level on Android and iOS.version: 0.0.1homepage: https://github.com/your_name/battery_info_pluginenvironment:sdk: ">=2.17.0 <4.0.0"flutter: ">=3.0.0"flutter:plugin:platforms:android:package: com.example.battery_info_pluginpluginClass: BatteryInfoPluginios:pluginClass: BatteryInfoPlugin
发布命令:
# 先检查有没有问题flutter pub publish --dry-run# 正式发布(需要登录 Google 账号)flutter pub publish
发布 checklist:
-
✔ 补充 README.md(示例代码 + 截图) -
✔ 补充 CHANGELOG.md -
✔ 添加 LICENSE文件 -
✔ 格式化代码: dart format . -
✔ 静态分析: flutter analyze
总结
-
👉 Flutter 插件通过 MethodChannel 实现 Dart ↔ 原生通信,通道名必须两端完全一致 -
👉 Android实现FlutterPlugin + MethodCallHandler接口;iOS实现FlutterPlugin协议
-
👉原生方法用result.success()返回数据,用 result.error()返回错误 -
👉 iOS 真机调试才能获取到真实电量,模拟器返回 -1 是正常的 -
👉 发布前务必跑 --dry-run,补齐 README / CHANGELOG / LICENSE
有问题欢迎在评论区留言,或者加群交流 Flutter 开发经验 🙌
夜雨聆风