什么是 Flutter 插件包?
Flutter 插件包与 Android Gradle 中的依赖包一样的意思。一些官方或者开源组织开发的包能显著提升我们的开发效率,减少开发成本。同时我们能从一些优秀的开源框架中学到很多知识,作为一个程序员,经常去 Github 逛一逛看看项目,或者 Fork 开源项目贡献代码,能得到很大的提升。
Flutter 包分为下面两类。
- Dart包:不依赖于特定平台,对 Flutter 框架具有依赖性,这种包仅用于Flutter。
- 插件包:依赖于特定平台,一种专用的 Dart 包,其中包含用 Dart 代码编写的API,以及针对Android(使用Java或Kotlin)和针对iOS(使用OC或Swift)平台的特定实现,也就是说插件包括原生代码。
本文仅介绍 Flutter 插件包的整个开发与发布流程,至于 Dart 包,过程都是类似的,读者可以查阅相关文章进行了解。
创建 Flutter 插件包项目
正文开始前,读者需要对 Flutter 的平台通道有所了解,如果你还不知道,可以先阅读我之前写的 Flutter | 如何优雅的调用 Android 原生方法?,然后再回来继续本文的学习。如果你已经掌握平台通道的相关知识,跟着我的步伐,继续往下~
本文将带读者在 Android 平台上实现一个调节音量大小的插件包项目,并发布到 Dart 仓库。首先打开 Android Studio,创建 Flutter Plugin 项目,如下。
整个项目创建完成后,目录结构是下面这个样子的,和 Flutter 项目差不多。系统会根据你创建项目所填的包名(我的包名是 cn.blogss.volume_control
),自动在 android 和 lib 目录下生成两个类,分别是VolumeControlPlugin.kt
和 volume_control.dart
。
VolumeControlPlugin.kt
实现了 FlutterPlugin 和 MethodCallHandler 接口。可以发现这和我们编写 Android 端平台通道代码基本一样。实例化了一个名叫 volume_control 的平台通道,之后我们只要在 onMethodCall 方法中根据业务逻辑处理来自平台的消息并返回结果即可。
/** VolumeControlPlugin */
class VolumeControlPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel : MethodChannel
// Flutter Engine 启动时会自动调用这个方法,实例化平台通道
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "volume_control")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { // 这里处理来自平台的消息
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
// Flutter Engine关闭时,释放内存
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
volume_control.dart
和我们编写 Flutter 端平台通道代码基本一样。内部实例化一个平台通道,然后可以在内部编写各种异步方法,来与特定平台进行通信,接收平台返回的结果。
class VolumeControl {
static const MethodChannel _channel =
const MethodChannel('volume_control');
static Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
以上就是创建 Flutter 插件项目的整个过程,向读者介绍了系统为我们自动生成的两个重要类。之后我们的开发重心围绕这两个类来开展。
编写调节音量代码并测试
Android 端代码
首先在 VolumeControlPlugin.kt
编写需要实现的方法。如下,我写了四个对应的方法名供 Flutter 端来调用。分别是设置音量最大范围、获取当前电量、改变媒体音量、改变系统音量。VolumeManager
内部实现了这四个方法的具体逻辑,由于篇幅关系,且本文的目的是带读者熟悉整个 Flutter 插件开发流程,这里不贴出 VolumeManager
类的源代码,也不讲解其实现细节。代码放在 volume_flutter,感兴趣的读者可以去看看。
/** VolumeControlPlugin */
class VolumeControlPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel : MethodChannel
private lateinit var volumeManager: VolumeManager
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "volume_control")
channel.setMethodCallHandler(this)
volumeManager = VolumeManager(flutterPluginBinding.applicationContext)
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when(call.method){
"setMaxVol" -> { // 设置最大音量范围
volumeManager.setMaxVol(call.arguments as Double);
}
"getCurrentVol" -> { // 获取当前音量
volumeManager.setAudioType(call.arguments as Int)
result.success(volumeManager.currentVolume);
}
"changeMediaVoice" -> { // 改变媒体音量
volumeManager.setAudioType(VolumeManager.TYPE_MUSIC)
val curVoice = volumeManager.setVoice(call.arguments as Double);
result.success(curVoice)
}
"changeSysVoice" -> { //改变系统音量
volumeManager.setAudioType(VolumeManager.TYPE_SYSTEM)
val curVoice = volumeManager.setVoice(call.arguments as Double);
result.success(curVoice)
}
else -> {
result.notImplemented()
}
}
}
}
Flutter 端代码
在 volume_control.dart
中编写 4 个异步方法,来调用上面 Android 端我们写好的处理方法,如下。
class VolumeControl {
static const MethodChannel _channel = const MethodChannel('volume_control');
/// 设置音量最大范围
/// setMaxVol 方法考虑到了音量的最大值可以自由设置,如果不使用这个方法,默认音量最大值是 100
static Future<void> setMaxVol(double num) async{
await _channel.invokeMethod("setMaxVol",num);
}
/// 获取当前音量
static Future<double> getCurrentVol(AudioType audioType) async{
return await _channel.invokeMethod("getCurrentVol",_getStreamInt(audioType)) as double;
}
/// 改变媒体音量
static Future<double> changeMediaVoice(double num) async{
return await _channel.invokeMethod("changeMediaVoice",num) as double;
}
/// 改变系统音量
static Future<double> changeSysVoice(double num) async{
return await _channel.invokeMethod("changeSysVoice",num) as double;
}
}
enum AudioType {
/// Controls the Voice Call volume
STREAM_VOICE_CALL,
/// Controls the system volume
STREAM_SYSTEM,
/// Controls the ringer volume
STREAM_RING,
/// Controls the media volume
STREAM_MUSIC,
// Controls the alarm volume
STREAM_ALARM,
/// Controls the notification volume
STREAM_NOTIFICATION
}
int _getStreamInt(AudioType audioType) {
switch (audioType) {
case AudioType.STREAM_VOICE_CALL:
return 0;
case AudioType.STREAM_SYSTEM:
return 1;
case AudioType.STREAM_RING:
return 2;
case AudioType.STREAM_MUSIC:
return 3;
case AudioType.STREAM_ALARM:
return 4;
case AudioType.STREAM_NOTIFICATION:
return 5;
default:
return null;
}
}
在 Flutter 页面看看效果
Android 端和 Flutter 端的代码我们编写完毕,现在在项目生成的 example 目录下的 main.dart
来编写页面示例代码,来展示插件的功能。注意 example 是开发者写给使用者看的,告诉他们这个插件如何使用的一个 Flutter 项目,相当于帮助文档,我觉得这点很好,极大了加快了我们的上手速度。
在 main.dart
中用一个 Slider 滑块组件来展示下效果。按以下步骤编码。
- 进入页面的时候调用 getCurrentVol 方法来获取当前媒体音量,显示初始状态。
- 滑动滑块调用 changeMediaVoice 方法来改变媒体音量。
main.dart
页面代码如下,也很简单。
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double _musicVoice;
@override
void initState() {
super.initState();
///1.获取当前媒体音量
initCurrentVol();
}
/// 获取当前媒体音量
Future<void> initCurrentVol () async{
_musicVoice = await VolumeControl.getCurrentVol(AudioType.STREAM_MUSIC);
if(!mounted) return;
setState(() {});
}
/// 改变媒体音量
Future<void> changeMediaVoice(double vol) async{
await VolumeControl.changeMediaVoice(vol);
_musicVoice = vol;
setState(() {});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: (_musicVoice != null) ? Slider(
value: _musicVoice,
min: 0,
max: 100,
inactiveColor: Colors.grey,
activeColor: Colors.blue,
onChanged: (vol){
/// 2. 滑动改变媒体音量
changeMediaVoice(vol);
},
): Container(),
),
),
);
}
}
实际效果如下,滑动滑块时,系统媒体音量也随之改变。我的测试机型是小米 MI 6X。其他机型可能会有差异,请读者注意。
将开发好的插件包上传到 Dart 仓库
我们的 Flutter 插件包整个开发流程就结束了。现在将它上传到 Dart 仓库,方便其他开发者可以使用这个插件包。在发布之前,检查 LICENSE
、pubspec.yaml
、README.md
以及 CHANGELOG.md
四个文件。
选择开源许可证(LICENSE)
软件开源许可证,大概有上百种。最流行的六种 --- GPL、BSD、MIT、Mozilla、Apache 和 LGPL。读者可以从 Choose an open source license 选择适合自己的证书,我这里选择 MIT。
将复制的内容粘贴到 LICENSE
,用当前年份替换掉 [year]
,版权所有者替换掉 [fullname]
。如下图,证书就算是弄完了。
修改 pubspec.yaml
name: volume_control
description: A new Flutter plugin.
version: 0.0.1
author:
homepage:
这里按实际情况修改 description
插件的简要描述,version
插件的版本,homepage
项目主页,其中 author
已经不支持使用了,读者需要直接删除,不然后面检查会不通过,修改后如下。
name: volume_control
description: A Flutter plugin which can control android volume.
version: 0.0.1
homepage: https://github.com/liqvip/volume_control
修改 README.md 和 CHANGELOG.md
README.md 文件不用多说,读者可以根据自己插件是干什么的、有什么用、使用方法等自由发挥。
CHANGELOG.md 文件用来记录每个版本的更改。也是根据实际情况来填写。
## 0.0.1
initial commit
很简单,对于 0.0.1
版本我只填了一句话,嘻嘻~
开始上传
- 首先在 Android Studio Termial 中输入如下命令,来检查我们编写的好的上述文件是否符合发布的要求。
flutter pub publish --dry-run
- 如果检查没有问题,控制台会输出如下提示信息。
Package has 0 warnings.
- 然后输入如下命令,开始上传
flutter pub publish --server=https://pub.dartlang.org
会提示你一旦发布就是永久的,不能够取消发布。输入 y 继续下一步
Publishing is forever; packages cannot be unpublished.
Policy details are available at https://pub.dev/policy
Do you want to publish volume_control 0.0.1 (y/N)?
控制台接着会输出一个链接,这里我们要复制这个链接到浏览器打开,然后会提示你登录验证谷歌邮箱,没有的需要用 VPN 注册一个谷歌邮箱。
Do you want to publish volume_control 0.0.1 (y/N)? y
Pub needs your authorization to upload packages on your behalf.
In a web browser, go to https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&response_type=code&client_id=818368855108-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.googleusercontent.com&redirect_uri=http%3A
%2F%2Flocalhost%3A55779&code_challenge=t9GweRvzHgPt6F1-1I42-3e8eg1MeA7xovsNLCsDHks&code_challenge_method=S256&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".
Waiting for your authorization...
下面是我用自己的邮箱,验证通过了的结果。
回到控制台,你可能会看到下面的报错信息。报错信息告诉我们,收到了验证信息正在处理但最终却走不到下一步,最后只能超时了。很明显这是网络问题。
Waiting for your authorization...
Authorization received, processing...
It looks like accounts.google.com is having some trouble.
Pub will wait for a while before trying to connect again.
OS Error: 信号灯超时时间已到
, errno = 121, address = accounts.google.com, port = 56479
pub finished with exit code 69
这是因为即使你设置了代理,此时终端中的 http 和 https 并不会被代理,所以我们需要设置一下终端代理。根据下面提供的命令,读者可以在不同的操作系统上设置终端代理,注意 http 和 https 都要设置,还有我的 ssr 代理端口是 1080,读者需要根据你的实际代理端口填写。
Windows
# 设置代理
set http_proxy=http://127.0.0.1:1080
set https_proxy=http://127.0.0.1:1080
# 验证代理是否设置成功
curl -vv http://www.google.com
# 取消代理
set http_proxy=
set https_proxy=
Linux
export http_proxy=http://127.0.0.1:1080;
export https_proxy=http://127.0.0.1:1080;
根据上面提供的命令,在 Windows 下设置终端代理后,测试下代理是否设置成功,只需请求一下 Google。返回如下结果表示代理设置成功。
代理设置完了之后,继续执行发布命令。
flutter pub publish --server=https://pub.dartlang.org
结果如下,显然这次网络问题已经解决了,但是 Dart 仓库上有一个和我们同名的插件包。所以我们将volume_control
改成 volume_flutter
,并将其他相关的类名也修改一下,然后继续发布。这里项目名最好先去 Dart 仓库搜一搜有没有被占用。如果被占用了就取个不同的名字。不然这里这很难受了,555~
10分钟过去,我名称改完了,兄弟们,继续执行发布命令。上传成功了,激动得飞起,嘻嘻~
在 Dart 仓库查看
最后一步去 Dart 仓库 volume_flutter 查看最后的战果。注意仓库会有延迟,没那么快就可以找到你刚刚上传的插件,需要等待个几分钟。结果如下,我们完成了整个插件的开发与发布过程。
写在最后
本文带领读者实现了一个在 Flutter 中调节 Android 音量的插件项目,并将其发布到了 Dart 仓库。之后如果有开发者想使用这个插件,只需要在 pubspec.yaml
中添加如下依赖即可。使用方法和我们在 exmaple 目录编写的示例代码一致。
dependencies:
volume_flutter: ^0.0.1
通过本文,读者应该能够完全掌握如何开发一个插件包并将其发布到 Dart 仓库。这中间我们遇到了很多困难,踩过很多坑,尤其在最后的发布步骤。但都一个个解决了。
如果你对我感兴趣,请移步到 http://blogss.cn ,
或关注公众号:程序员小北,进一步了解。
- 如果本文帮助到了你,欢迎点赞和关注 ❤️
- 由于作者水平有限,文中如果有错误,欢迎在评论区指正 ✔️
- 本文首发于掘金,未经许可禁止转载 ©️