乐于分享
好东西不私藏

Flutter 插件开发从零到发布:手把手带你写一个原生插件(附完整代码)

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),失败返回 -1  static Future<intgetBatteryLevel() 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 : FlutterPluginMethodChannel.MethodCallHandler {    private lateinit var channel: MethodChannel    private lateinit var context: Context    // 插件注册时调用    override funonAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {        context = binding.applicationContext        channel = 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 BatteryManager            manager.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) ?: -1            val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1            if (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 BatteryInfoPluginNSObjectFlutterPlugin {    public static func register(with registrarFlutterPluginRegistrar) {        let channel = FlutterMethodChannel(            name: "com.example.battery_info_plugin",  // 和 Dart 端一致!            binaryMessenger: registrar.messenger()        )        let instance = BatteryInfoPlugin()        registrar.addMethodCallDelegate(instance, channel: channel)    }    public func handle(_ callFlutterMethodCallresult@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 = true        let level = UIDevice.current.batteryLevel        // 模拟器返回 -1,真机返回 0.0~1.0        guard 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%' : '获取失败';    });  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBarAppBar(titleconst Text('电量插件测试')),      bodyCenter(        childColumn(          mainAxisSize: MainAxisSize.min,          children: [            Text('当前电量:$_batteryInfo', styleconst TextStyle(fontSize24)),            const SizedBox(height16),            ElevatedButton(              onPressed: _fetchBattery,              childconst 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_plugin        pluginClass: BatteryInfoPlugin      ios:        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 开发经验 🙌