Flutter插件(Plugin)开发 - Android视角-程序员宅基地

技术标签: 程序员  flutter  android  

default:
result.notImplemented();
break;
}
}

具体本地MediaPlayer的操作就不细说了,大家可以去看源码。MethodChannel就添加完了。此外我们还需要上报播放器的状态和播放时的进度,这就需要在registerWith里再注册两个EventChannel了

public static void registerWith(Registrar registrar) {

// 上报播放器的状态的EventChannel
EventChannel status_channel = new EventChannel(registrar.messenger(), “flutter_music_plugin.event.status”);
status_channel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
// 把eventSink存起来
plugin.setStateSink(eventSink);
}

@Override
public void onCancel(Object o) {

}
});
//上报播放进度的EventChannel
EventChannel position_channel = new EventChannel(registrar.messenger(), “flutter_music_plugin.event.position”);
position_channel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
// 把eventSink存起来
plugin.setPositionSink(eventSink);
}

@Override
public void onCancel(Object o) {

}
});
}

注册完以后我们就拿到了两个EventSink,当需要的时候就可以用需要的EventSink给Flutter App上报事件了。

Native这边还有一环是打开本地音频文件的操作,这里我偷个懒,用发送Intent的方式来让用户在第三方app中选择音频文件。如果是在Activity中我会用startActivityForResultonActivityResult来获取音频文件,可是我们现在开发的是一个插件,不是Activity怎么办?

回想一下我们用来注册插件的静态函数registerWith,入参的类型是Registrar。看看它里面都有啥?

public interface Registrar {
//返回 Host app的Activity
Activity activity();
//返回 Application Context.
Context context();
//返回 活动Context
Context activeContext();
//返回 BinaryMessenger 主要用来注册Platform channels
BinaryMessenger messenger();
//返回 TextureRegistry,从里面可以拿到SurfaceTexture
TextureRegistry textures();
//返回 当前Host app创建的FlutterView
FlutterView view();
//返回Asset对应的文件路径
String lookupKeyForAsset(String var1);
//返回Asset对应的文件路径
String lookupKeyForAsset(String var1, String var2);
//插件对外发布的一个"值"
PluginRegistry.Registrar publish(Object var1);
//注册权限相关的回调
PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener var1);
//注册ActivityResult回调
PluginRegistry.Registrar addActivityResultListener(PluginRegistry.ActivityResultListener var1);
//注册NewIntent回调
PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener var1);
//注册UserLeaveHint回调
PluginRegistry.Registrar addUserLeaveHintListener(PluginRegistry.UserLeaveHintListener var1);
//注册View销毁回调
PluginRegistry.Registrar addViewDestroyListener(PluginRegistry.ViewDestroyListener var1);
}

。。。简直就是个宝库啊。里面的中文注释我是照官方英文文档翻译的,有些方法的用途也不太明确,有待大家的发掘。本例中目前只需要两个方法,调用activity()就拿到Host App的Activity。addActivityResultListener设置处理返回结果的回调。代码如下:

// 实现 PluginRegistry.ActivityResultListener
public class FlutterMusicPlugin implements MethodCallHandler, PluginRegistry.ActivityResultListener {

private Activity mActivity;
// 加个构造函数,入参是Activity
private FlutterMusicPlugin(Activity activity) {
// 存起来
mActivity = activity;
}

public static void registerWith(Registrar registrar) {
//传入Activity
final FlutterMusicPlugin plugin = new FlutterMusicPlugin(registrar.activity());

// 注册ActivityResult回调
registrar.addActivityResultListener(plugin);
}

@Override
public void onMethodCall(MethodCall call, Result result) {
switch (call.method) {

case “open”:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(“audio/*”);
mActivity.startActivityForResult(intent, REQUEST_CODE_OPEN);
break;

}
}

@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_OPEN && resultCode == RESULT_OK) {
Uri uri = data.getData();
if (uri != null) {
// 拿到音频文件uri,开始播放。
play(uri);
} else {
mStateSink.error(“ERROR”, “invalid media file”, null);
}
return true;
}
return false;
}
}

我们改造一下FlutterMusicPlugin, 增加以Activity为入参的构造函数,在静态函数registerWith里实例化的时候传入Host app的Activity。同时注册自身来处理onActivityResult回调。 在onMethodCall方法内"open"下启动第三方选择音频文件的页面。当用户选好了某首歌返回的时候,插件这边就会拿到音频文件uri,并开始播放。

至此,Native端的逻辑就完成了,我们再来看看插件的Flutter端怎么做。

插件Flutter端

IDE在lib目录下会帮你自动生成flutter_music_plugin.dart文件,这个就是插件的Flutter代码所在了,内容比较简单,就是对我们定义好的Platform channels的包装。直接上代码:

typedef void EventHandler(Object event);

class FlutterMusicPlugin {
static const MethodChannel _channel = const MethodChannel(‘flutter_music_plugin’);
static const EventChannel _status_channel = const EventChannel(‘flutter_music_plugin.event.status’);
static const EventChannel _position_channel = const EventChannel(‘flutter_music_plugin.event.position’);

static Future open() async {
await _channel.invokeMethod(‘open’);
}

static Future pause() async {
await _channel.invokeMethod(‘pause’);
}

static Future start() async {
await _channel.invokeMethod(‘start’);
}

static Future getDuration() async {
int duration = await _channel.invokeMethod(‘getDuration’);
return Duration(milliseconds: duration);
}

static listenStatus(EventHandler onEvent, EventHandler onError) {
_status_channel.receiveBroadcastStream().listen(onEvent, onError: onError);
}

static listenPosition(EventHandler onEvent, EventHandler onError) {
_position_channel.receiveBroadcastStream().listen(onEvent, onError: onError);
}
}

插件Example App

除了自身的逻辑之外,一个插件还要有示例应用来演示其API怎么使用,同时,示例应用也是我们开发,调试,验证插件的必备工具。本例中的示例可参考example目录下的main.dart文件。使用插件API的主要逻辑都在State中。简要代码如下

@override
void initState() {
super.initState();
// 在这里注册EventChannles,参数传入响应的回调
FlutterMusicPlugin.listenStatus(_onPlayerStatus, _onPlayerStatusError);
FlutterMusicPlugin.listenPosition(_onPosition, _onPlayerStatusError);
}

// 根据播放状态调用pause或start
void _playPause() {
switch (_status) {
case “started”:
FlutterMusicPlugin.pause();
break;
case “paused”:
case “completed”:
FlutterMusicPlugin.start();
break;
}
}
// 打开媒体文件
void _open() {
FlutterMusicPlugin.open();
}
// MediaPlayer出错事件处理
void _onPlayerStatusError(Object event) {
print(event);
}
// MediaPlayer状态改变事件处理
void _onPlayerStatus(Object event) {
setState(() {
_status = event;
});
if (_status == “started”) {
_getDuration();
}
}
// 获取音频时长
void _getDuration() async {
Duration duration = await FlutterMusicPlugin.getDuration();
setState(() {
_duration = duration;
});
}
// 播放进度事件处理
void _onPosition(Object event) {
Duration position = Duration(milliseconds: event);
setState(() {
_position = position;
});
}

发布

当你的插件开发测试完成以后,你就可以把你的插件发布出去了。 发布之前,先检查pubspec.yaml, README.mdCHANGELOG.md这几个文件的内容是否完整正确。然后运行下面这个命令检查插件是否可以发布。

$ flutter packages pub publish --dry-run

如果有问题存在的话,会在终端输出相关信息,你需要据此做出修改直到返回成功。具体遇到的问题可以参考官方文档

最后去掉--dry-run以后再运行以上命令。

$ flutter packages pub publish

恭喜你,你的插件终于发布出去了。

插件注册

从前文开发插件的过程中我们知道了在插件Android代码里有一个静态函数registerWith,这个函数可以把插件注册到Host App。那么问题来了,插件是什么时候注册的呢?这个静态函数是被谁调用的呢? 答案就在example app的MainActivity里:

public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 插件在这里注册
GeneratedPluginRegistrant.registerWith(this);
}
}

onCreate函数里,有这么一行代码GeneratedPluginRegistrant.registerWith(this)。插件就是在这里注册的。再看看GeneratedPluginRegistrant的内容就明白了:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,没有人能随随便便成功。

加油,共勉。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,没有人能随随便便成功。

加油,共勉。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/2401_84121702/article/details/137890978

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签