当需要与native交互的时候,就需要使用原生语言为Flutter写插件。通过Flutter插件,可以使用原生语言实现Flutter没有提供的功能。比如调用系统提供的底层功能、调用第三方库等等。

在Flutter插件中,是通过Platform Channel进行通信的。共三种交互方式:

  • BasicMessageChannel :用于传递字符串和半结构化信息;
  • EventChannel:用于数据流的监听与发送。
  • MethodChannel :用于传递方法调用和处理回调,这是最常用的一种交互方式;

创建插件

flutter create -t plugin --platforms android flutter_plugin_t1

创建一个android平台的插件。默认使用kotlin语言。如果想使用java语言,需要传入参数:

flutter create -t plugin --platforms android -a java flutter_plugin_t1

默认的组织名为:com.example,我们可以通过创建时传入--org更改,比如:

flutter create -t plugin --org top.mydata --platforms android flutter_plugin_t1

创建完成之后,我们还可以通过下面的命令为插件提供更多平台:

flutter create -t plugin --platforms ios flutter_plugin_t1
flutter create -t plugin --platforms macos flutter_plugin_t1
flutter create -t plugin --platforms linux flutter_plugin_t1
flutter create -t plugin --platforms windows flutter_plugin_t1

如果是ios,可以使用-i objc来选择使用Object C,默认使用的swift

插件的基本结构

flowchart TB root(flutter_plugin_t1) subgraph 平台相关代码 android else("ios<br>linux<br>windows<br>macos<br>web") end root-->android root-->else root-->test root-->example root-->lib root-->pubspec.yaml root-->...

其中:

  • pubspec.yaml
    Flutter项目的配置文件
  • test 目录中
    单元测试的代码。
  • example
    插件的使用范例代码。
  • lib
    插件flutter部分的代码。
  • android、iso、linux、macos、windows...
    平台相关部分的代码。

封装类

在插件模板中,实现了一个获取平台版本号的例子。

  • 文件lib/hello_plugin.dart
    
    import 'flutter_plugin_t1_platform_interface.dart';

class FlutterPluginT1 {
Future<String?> getPlatformVersion() {
return FlutterPluginT1Platform.instance.getPlatformVersion();
}
}

在该文件中定义了一个名为`FlutterPluginT1`的类,其中有一个名为`getPlatformVersion`方法。使用者只需创建该类的对象,调用这个方法就可以了。但是,这个方法中,并没有具体的实现代码。它的具体实现代码在平台目录中。那么,又是怎么调用过去的呢。原理就不讲了,我们仅仅直白的讲解代码结构。
在文件`lib/hello_plugin_platform_interface.dart`和`lib/hello_plugin_method_channel.dart`中,我们也能找到同样名为`getPlatformVersion`的方法。

在`hello_plugin_platform_interface.dart`只是定义了接口,`hello_plugin_method_channel.dart`才是接口实现。
接口定义没啥好说的,依葫芦画瓢就可以了。关键的还是接口实现:
```dart
@override
  Future<String?> getPlatformVersion() async {
    final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
    return version;
  }

上面的代码中,通过methodChannel执行了一个名为getPlatformVersion的函数,返回值类型为String。真正的函数实现在各种的平台中,至于中间的封包、解包过程,flutter已经做了很好的封装。

Android系统

在文件android/src/main/kotlin/top/mydata/flutter_plugin_t1/FlutterPluginT1Plugin.ktonMethodCall方法中。

  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }

代码很简单粗暴,判断调用的函数名,如果是getPlatformVersion,就返回一个字符串Android ${android.os.Build.VERSION.RELEASE}

Linux系统

在文件linux/flutter_plugin_t1_plugin.ccflutter_plugin_t1_plugin_handle_method_call函数中:

static void flutter_plugin_t1_plugin_handle_method_call(
    FlutterPluginT1Plugin* self,
    FlMethodCall* method_call) {
  g_autoptr(FlMethodResponse) response = nullptr;

  const gchar* method = fl_method_call_get_name(method_call);

  if (strcmp(method, "getPlatformVersion") == 0) {
    struct utsname uname_data = {};
    uname(&uname_data);
    g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
    g_autoptr(FlValue) result = fl_value_new_string(version);
    response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
  } else {
    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  }

  fl_method_call_respond(method_call, response, nullptr);
}

处理语言导致的实现区别,跟Android没有逻辑上的差异。

Windows

在文件windows/flutter_plugin_t1_plugin.cppHandleMethodCall函数中:

void FlutterPluginT1Plugin::HandleMethodCall(
    const flutter::MethodCall<flutter::EncodableValue> &method_call,
    std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
  if (method_call.method_name().compare("getPlatformVersion") == 0) {
    std::ostringstream version_stream;
    version_stream << "Windows ";
    if (IsWindows10OrGreater()) {
      version_stream << "10+";
    } else if (IsWindows8OrGreater()) {
      version_stream << "8";
    } else if (IsWindows7OrGreater()) {
      version_stream << "7";
    }
    result->Success(flutter::EncodableValue(version_stream.str()));
  } else {
    result->NotImplemented();
  }
}

其他平台其实也都是大同小异。

参数传递

调用侧

@override
  Future<int?> add(int a, int b) async {
    final sum = await methodChannel.invokeMethod<int>('add', {"msg": msg});
    return sum;
  }

Android下的实现

  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else if (call.method == "add") {
          val a = call.argument<Integer>("a")!!
          val b = call.argument<Integer>("b")!!
      result.success(a + b)
    } else {
      result.notImplemented()
    }
  }

这样写,后面会不好维护。可以用when语句,如下:

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    when(call.method) {
        "getPlatformVersion" -> handleGetPlatformVersion(call, result)
        "add" -> handleAdd(call, result)
        else -> result.notImplemented()
      }
}

func handleGetPlatformVersion(@NonNull call: MethodCall, @NonNull result: Result) {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
}

func handleAdd(@NonNull call: MethodCall, @NonNull result: Result) {
      val a = call.argument<Integer>("a")!!
      val b = call.argument<Integer>("b")!!
    result.success(a + b)
}

原生调用Flutter

通信是双向的,在原生代码中也可以调用Flutter。方法跟Flutter类似,比如,在Android原生代码中调用Flutter提供的函数ShowMessage,方法如下:

Android中

channel.invokeMethod("ShowMessage", {"content": "hello world"})

Flutter中

默认模板中没有这部分代码。需要在MethodChannelXXXXX中加入以下代码:



  MethodChannelXXXXX() {
    methodChannel.setMethodCallHandler(_handleMethodCall);
  }

  // _handleMethodCall
  Future<void> _handleMethodCall(MethodCall call) async {
    switch (call.method) {
      case 'ShowMessage':
        _handleShowMessage(call);
        break;
      default:
        throw ('Not implemented: ${call.method}');
    }
  }


  void _handleShowMessage(MethodCall call) async {
    final content = call.arguments["content"];
    print(content);
  }
``

代码跟Flutter调用原生非常类似,不过多解释了。

下一篇,我们聊一下,通过`插件`+`NDK`在flutter中调用C++代码。