Flutter 自定义插件基础
1、Flutter插件是什么?官方插件库
在开发Flutter应用过程中会涉及到平台相关接口调用,例如数据库操作、相机调用、外部浏览器跳转等业务场景。实际上Flutter自身并不支持直接在平台上实现这些功能,而是通过插件包接口去调用指定平台API,从而实现在原生平台上执行特定功能。
2、Flutter插件的目录结构
一个典型的Flutter插件包含以下核心目录和文件:
- lib:这是对接Dart端代码的入口,该文件接收参数后,通过Channel将数据发送到原生端。
- android:存放安卓端代码实现的目录。
- ios:存放iOS原生端代码实现的目录。
- example:一个依赖于该插件的Flutter应用程序,用于演示如何使用插件。
- README.md:介绍插件的文件。
- CHANGELOG.md:记录每个版本中的更改。
- LICENSE:包含软件包许可条款的文件。
3、Flutter插件包的创建方式
3.1 使用命令行创建
使用以下命令创建一个新的插件包:
flutter create --template=package hello
可以通过--org参数指定包标识符,例如:
flutter create --template=package --org com.example hello
要指定iOS和Android代码使用的语言类型,可以添加参数:
flutter create --template=plugin -i swift -a kotlin hello
3.2 使用Android Studio直接创建新工程
在Android Studio中,可以直接通过新建Flutter Plugin项目来创建插件,这会在图形界面中完成相同的初始化过程。
4、Flutter插件功能编写
Flutter插件模板生成后,在lib文件夹下会自动生成一个对外的入口Dart类。该插件所包含的所有功能都以此类为入口,供外部调用。以一个名为hello的插件为例,其核心方法是platformVersion,它对外提供方法调用,但方法内部的实现逻辑是通过原生端获取的。
对应Android原生端的入口文件需要继承MethodChannel.MethodCallHandler接口,在onMethodCall方法回调中处理来自Dart端的请求,并通过result对象将结果返回给Dart端。如果Dart端不需要返回结果,也可以不调用result.success(Object o)。
对于需要打开Intent并在onActivityResult方法中获取结果的需求(如调起相机拍照),则需继承PluginRegistry.ActivityResultListener接口。但请注意,直接将源码放在项目中的插件在运行时onActivityResult方法可能不会被调用,因为MainActivity中的onActivityResult会拦截调用动作。因此,必须将插件放在远端仓库中才能正常接收结果。
5、Flutter插件的两种注册方式
5.1 通过registerWith方式注册(旧方式)
这是一种早期通过反射进行加载的注册方式,目前老版本项目中的插件可能仍在使用。但从Flutter v1.12.x开始,官方推荐使用新方式,旧方式可能在未来的更新中被废除。示例代码:
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), PLUGIN_NAME);
channel.setMethodCallHandler(new FlutterXUpdatePlugin().initPlugin(channel, registrar));
}
在旧方式中,获取activity对象时使用registrar.activity()。
5.2 通过Flutter引擎注册(新方式)
从Flutter 1.12.X版本起,Embedding-V2 API在Android平台默认开启,所有官方插件都已迁移到新API。新API在混合开发中提供了更好的支持和内存优化。插件的注册方式定义在Android端的mainfest.xml文件中,需添加以下配置:
<meta-data
android:name="flutterEmbedding"
android:value="2" />
在插件的plugin文件中,继承FlutterPlugin接口,使用新方式进行初始化:
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
mMethodChannel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), PLUGIN_NAME);
mApplication = (Application) flutterPluginBinding.getApplicationContext();
mMethodChannel.setMethodCallHandler(this);
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
mMethodChannel.setMethodCallHandler(null);
mMethodChannel = null;
}
如需获取当前插件依附的activity(即MainActivity),则需要插件集成ActivityAware接口,并通过回调获取:
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
mActivity = new WeakReference<>(binding.getActivity());
}
@Override
public void onDetachedFromActivity() {
mActivity = null;
}
6、Flutter与原生之间如何交互
Flutter与原生的交互模型类似于客户端-服务器(C-S)模型。Flutter作为Client层,原生作为Server层,两者通过MethodChannel进行消息通信。原生端向Flutter提供已有的Native组件功能。
6.1 什么是MethodChannel?
Flutter定义了三种Channel模型:
- BasicMessageChannel:用于传递字符串和半结构化的信息。
- MethodChannel:用于传递方法调用(method invocation)。
- EventChannel:用于数据流(event streams)的通信。
MethodChannel包含三个成员变量:
- String name:Channel的唯一标识,命名应独一无二,推荐采用“组件名_Channel名”组合。
- BinaryMessenger messenger:Platform端与Flutter端通信的工具,使用二进制格式数据。Channel会注册一个对应的BinaryMessageHandler到BinaryMessenger。
- MethodCodec codec:消息编解码器,用于将二进制数据转化为Handler能识别的数据。MethodCodec主要对MethodCall对象进行序列化与反序列化。
6.2 Flutter与原生之间的通信流程
从Dart层调用开始:
_channel.invokeMethod("方法名", 参数)
invokeMethod会将方法名和参数封装成MethodCall对象,通过MethodCodec编码成二进制格式,再通过BinaryMessenger的send方法发送。在Flutter引擎的native代码中,最终会将消息传递给Java层的MethodHandler,触发onMethodCall方法。处理完逻辑后,通过Result对象回传结果给Dart端。
6.3 MethodChannel的注册时机
在插件运行时,会调用registerWith方法,在生成MethodChannel对象的同时向它注册一个MethodHandler。MethodHandler与MethodChannel是一一对应的关系,确保消息能正确路由和处理。
7、原生和Flutter之间数据交互的类型限制
Flutter与原生之间传递的数据类型受到一定限制,通常支持基本类型(如int、double、bool、String)、List、Map等。需要确保传递的数据是可序列化的,避免复杂对象直接传递。具体限制可以参考Flutter官方文档关于StandardMessageCodec的说明。
8、插件包的发布
发布Flutter插件包可参考Flutter中文网的Package发布教程。通常步骤包括:检查代码、更新版本号、运行发布命令,并将包上传到pub.dev等仓库。
编写包含Android和iOS的Flutter插件实现过程
随着Flutter日益成熟,使用Flutter开发跨平台应用越来越普遍。虽然Flutter强大,但有时仍需调用原生功能,如极光推送、地图等SDK。当官方或社区未提供相应插件时,我们需要自己实现。
插件创建与目录结构
首先,通过Flutter命令或IDE创建一个插件项目。插件目录主要包括:
- android:安卓端代码位置。
- ios:iOS端代码位置。
- lib:Flutter端代码位置,负责与原生交互。
- example:测试项目,用于验证插件功能。
Flutter部分实现
在lib目录下,创建核心Dart类处理交互。例如,FlutterPluginTest_1类定义MethodChannel并设置回调:
class FlutterPluginTest_1 {
MethodChannel _channel;
FlutterPluginTest_1.init(int id) {
_channel = new MethodChannel('FlutterPluginTest_1');
_channel.setMethodCallHandler(platformCallHandler);
}
Future<void> changeNativeTitle(String str) async {
return _channel.invokeListMethod('changeNativeTitle', str);
}
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "clickAciton":
print('收到原生回调 ---- ${call.arguments}');
return;
}
}
}
为显示原生UI,创建TestView类,使用AndroidView或UiKitView嵌入原生视图:
class TestView extends StatefulWidget {
final TestViewCreatedCallback onCreated;
final String titleStr;
TestView({Key key, this.onCreated, this.titleStr});
@override
_TestViewState createState() => _TestViewState();
}
class _TestViewState extends State<TestView> {
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return AndroidView(
viewType: 'testView',
onPlatformViewCreated: onPlatformViewCreated,
creationParams: {'titleStr': widget.titleStr},
creationParamsCodec: const StandardMessageCodec(),
);
} else if (Platform.isIOS) {
return UiKitView(
viewType: 'testView',
onPlatformViewCreated: onPlatformViewCreated,
creationParams: {'titleStr': widget.titleStr},
creationParamsCodec: const StandardMessageCodec(),
);
} else {
return Text('当前平台不支持');
}
}
Future<void> onPlatformViewCreated(id) async {
if (widget.onCreated != null) {
widget.onCreated(new FlutterPluginTest_1.init(id));
}
}
}
在example项目中,可以调用TestView来测试插件功能。
原生部分实现
iOS端
在iOS目录中,找到FlutterPluginTest_1Plugin类,它负责注册插件。创建一个TestFlutterPluginViewFactory来处理视图创建,以及TestFlutterPluginView来绘制原生UI并处理交互。例如,在TestFlutterPluginView中,可以设置一个按钮,点击时通过MethodChannel调用Flutter方法。
Android端
类似地,在Android目录中,FlutterPluginTest_1Plugin类注册插件。创建TestFlutterPluginViewFactory和TestFlutterPluginView,在后者中实现原生视图和交互逻辑。例如,一个简单的TextView,通过MethodChannel接收Flutter的调用并显示Toast。
插件集成与使用
要在其他Flutter项目中使用本地插件,只需在pubspec.yaml中添加依赖路径:
dependencies:
flutter:
sdk: flutter
flutter_plugin_test_1:
path: /path/to/your/plugin
然后运行flutter pub get,即可在代码中导入和使用插件,就像在example项目中一样。
通过以上步骤,我们完成了Flutter自定义插件的开发、测试和集成。这涵盖了从基础概念到具体实现的完整流程,帮助开发者理解如何扩展Flutter功能以调用原生平台能力。