Flutter自定义插件开发完整指南

Viewed 0

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类,使用AndroidViewUiKitView嵌入原生视图:

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类注册插件。创建TestFlutterPluginViewFactoryTestFlutterPluginView,在后者中实现原生视图和交互逻辑。例如,一个简单的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功能以调用原生平台能力。

0 Answers