经过此篇文章,你将了解到:

  1. Flutter插件的基本介绍;
  2. windows插件开发的真实踩坑经验。

⚠️本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!

前言

咱们都知道,Flutter的定位更多是作为一个跨渠道的UI结构,关于原生渠道的功能,开发过程中常常需求插件来供给。不幸的是Windows的生态又极端不完整,插件开发必不可少。但网上windows的文章少之又少,所以本篇文章,咱们一起来聊聊插件开发的一些技巧。

插件介绍

Flutter的插件首要分两种:package和plugin。

  • Package是纯dart代码的库,不触及原生渠道的代码;
  • Plugin是原生插件库,是一种特殊的Package。Plugin需求开发者分别在各原生渠道完结对应的才能。

其中Plugin是咱们要着重讲的,既然是原生渠道完结,那跟dart层就必然需求通讯。Flutter Plugin的通讯首要有:methodChannel、eventChannel、basicMessageChannel。

  • MethodChannel:同步调用的通道,调用后能够经过result回来成果。能够 Native 端自动调用,也能够Flutter自动调用,归于双向通讯。这种通讯方法是咱们日常开发中为最常用的方法, 要害点是Native 端的调用需求在主线程中履行
  • EventChannel:异步事情告诉的通道,一般是Native端自动宣布告诉,Flutter接纳通讯信息。
  • BasicMessageChannel:长链接的通道,双端能够随时宣布音讯,对方收到音讯后能够运用reply进行回复。一般常用于需求双向通讯可不知道何时需求发送的场景。

windows插件编写

Flutter Android的生态算是比较完整的,而且网上95%的插件文章,都是以移动端为主,关于不了解Windows开发的同学极度不友好。因而本篇文章咱们不讲Android端的完结,要点讲Windows端的实践,不过我也不是C++技能栈的,只能浅浅共享我踩过的坑。

  1. 怎样创立通讯通道?
// MethodChannel
void XXXPlugin::RegisterWithRegistrar(
	flutter::PluginRegistrarWindows* registrar) {
        // 创立一个MethodChannel
	auto channel =
		std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
			registrar->messenger(), "usb_tool",
			&flutter::StandardMethodCodec::GetInstance());
        // 创立插件目标
	auto plugin = std::make_unique<XXXPlugin>();
        // 把通道设置给插件,同时传入音讯的处理入口
	channel->SetMethodCallHandler(
		[plugin_pointer = plugin.get()](const auto& call, auto result) {
		plugin_pointer->HandleMethodCall(call, std::move(result));
	});
}
// EventChannel
// 创立事情流处理目标
auto eventHandler = std::make_unique<
StreamHandlerFunctions<EncodableValue>>(
	[plugin_pointer = plugin.get()](
		const EncodableValue* arguments,
		std::unique_ptr<EventSink<EncodableValue>>&& events)
		-> std::unique_ptr<StreamHandlerError<EncodableValue>> {
			return plugin_pointer->OnListen(arguments, std::move(events));
	},
	[plugin_pointer = plugin.get()](const EncodableValue* arguments)
		-> std::unique_ptr<StreamHandlerError<EncodableValue>> {
			return plugin_pointer->OnCancel(arguments);
	});
// 创立EventChannel目标
auto eventChannel = std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
	registrar->messenger(), eventChannelName,
	&flutter::StandardMethodCodec::GetInstance());
// 把通道设置给插件
eventChannel->SetStreamHandler(std::move(eventHandler));

最终咱们还需求把插件注册进项目中

registrar->AddPlugin(std::move(plugin));
  1. 怎样处理音讯?
    在上面创立的过程中,其实现已把处理方法的传递给插件了。
// MethodChannel的处理
// result即通讯的目标
void XXXPlugin::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->Succes回复音讯
		result->Success(flutter::EncodableValue(version_stream.str()));
	} else {
		result->NotImplemented();
	}
}
// 自动向Flutter端发送音讯
std::unique_ptr < flutter::StreamHandlerError<flutter::EncodableValue>> XXXPlugin::OnListen(const flutter::EncodableValue* arguments,
	std::unique_ptr<flutter::EventSink<flutter::EncodableValue>>&& events) {
	// 自动发送
	events_.reset(events.release());
	return nullptr;
}
// Flutter取消监听时触发
std::unique_ptr < flutter::StreamHandlerError<flutter::EncodableValue>> UsbToolPlugin::OnCancel(const flutter::EncodableValue* arguments) {
	return nullptr;
}

BasicMessageChannel我暂时还没有用过,这儿就不做记录了。可是看C++的api,仍是很简单就能找到的。至于Flutter端的,无需多言。只要通讯层连通了,其他想怎样玩都能够。

Windows插件的一些坑

这是本篇文章的要点。咱们都知道Flutter是单线程的机制,来到原生渠道也一样,Platform是运行在Flutter的主线程的,自然是不能做任何耗时的,否则会卡住主线程,体系会把咱们以为无呼应的运用,从而杀死运用。

咱们常常会在运用windows插件时,感觉点击卡顿,其实便是许多插件没有做这个处理,导致事情队列等候调度。这首要是因为在windows的开发习惯上,耗时操作会丢到子线程异步履行,然后主线程怎样等候履行成果?运用while一直去查询是否履行完结,这在windows上成为挂起。

不过一个有趣的现象是:当有耗时操作的时候,Flutter的动画是能够流程播放的,可是点击事情却卡住了,这时候C++的同学就会扯,你看动画都是流程的,问题肯定出在Flutter上?其实是因为动画在Flutter中归于微使命,它的优先级是高于事情队列的。而while也是分配到事情队列中,所以动画优先履行,点击却需求一直等到while完毕。

在Android中,为了防止这个问题,咱们一般会运用协程,把耗时操作丢给协程,让体系帮咱们进行使命调度,经过await拿到履行完之后的成果,再把成果回来给dart层。整个机制其实仍是保留了flutter的单线程机制,从而防止了卡顿问题。

在Windows端,其实也有协程这个概念,比如WinRT、C++都有供给协程的才能。但问题在于协程这个东西,关于C++来说太新了,同时C++的历史包袱实在太重,到现在仍是用着很老版本的库。这就导致许多C++的库没办法迁移到协程这种方法,至少在我现在的事务中,切换成本极高,简直没办法完结。

但问题总得处理,目前咱们首要运用异步告诉的方法,来处理这个问题。此异步是真异步,非flutter单线程使命调度的异步。咱们会把耗时的操作丢给子线程,可是咱们不再经过while进行异步转同步,而是在子线程中,自动经过channel去告诉会Dart层。

if (*method == "getAsync") {
            async_pipe_stream_->Get(request, std::bind(&XXXPlugin::OnResponse, this, std::placeholders::_1, *uuid));
            // 直接回来true,但真实的履行成果再OnResponse中自动回来
            result->Success(EncodableValue(true)); 
            return;
        }

在插件的dart代码中,咱们需求自动创立一个MethodChannel的接纳器,异步接纳到后,经过履行事务端传入的回调告诉回去。

class NativePlugin {
  static const MethodChannel _channel =
      MethodChannel('com.open.flutter/xxx/xxx');
  static NativePlugin? _instance;
  // 获取实例,单例
  static NativePlugin getInstance({String defaultToken = _token}) {
    _instance ??= NativePlugin._internal(defaultToken);
    return _instance!;
  }
  // 私有命名结构函数,做一次初始化
  NativePlugin._internal(String defaultToken) {
    _defaultToken = defaultToken;
    _channel.setMethodCallHandler((MethodCall call) async {
      if (call.method == 'onResponse') {
        final arguments = Map<String, dynamic>.from(call.arguments);
        // 履行事务端传入的回调
        await _onResponse(arguments);
      }
    });
  }

插件的Flutter层需求接纳/维护回调列表,不过此方法有危险,传入的回调简单造成闭包问题,添加一些内存泄露的风险;可是关于没办法运用协程的C++插件来说,此计划确实能够处理不少问题。亲测可用的!

写在最终

这篇文章,适合了解Flutter插件开发,可是想接触C++的同学学习评论。
此专栏从窗口办理、分辨率适配、桌面小工具、项目结构、插件编写;下次咱们讲讲怎样进行打包!