关于flutter:Flutter桌面开发-windows插件开发

4次阅读

共计 4976 个字符,预计需要花费 13 分钟才能阅读完成。

Flutter 桌面开发 – windows 插件开发

Flutter桌面开发 – windows插件开发

前言

咱们都晓得,Flutter的定位更多是作为一个跨平台的 UI 框架,对于原生平台的性能,开发过程中常常须要插件来提供。可怜的是 Windows 的生态又极其不残缺,插件开发必不可少。但网上 windows 的文章少之又少,所以本篇文章,咱们一起来聊聊插件开发的一些技巧。
插件介绍
Flutter 的插件次要分两种:packageplugin

Package是纯 dart 代码的库,不波及原生平台的代码;
Plugin是原生插件库,是一种非凡的 PackagePlugin 须要开发者别离在各原生平台实现对应的能力。

其中 Plugin 是咱们要着重讲的,既然是原生平台实现,那跟 dart 层就势必须要通信。Flutter Plugin 的通信次要有:methodChanneleventChannelbasicMessageChannel

MethodChannel:同步调用的通道,调用后能够通过 result 返回后果。能够 Native 端被动调用,也能够 Flutter 被动调用,属于双向通信。这种通信形式是咱们日常开发中为最罕用的形式,关键点是 Native 端的调用须要在主线程中执行。
EventChannel:异步事件告诉的通道,个别是 Native 端被动发出通知,Flutter 接管通信信息。
BasicMessageChannel:长链接的通道,双端能够随时收回音讯,对方收到音讯后能够应用 reply 进行回复。个别罕用于须要双向通信可不晓得何时须要发送的场景。

windows 插件编写

Flutter Android的生态算是比拟残缺的,而且网上 95% 的插件文章,都是以挪动端为主,对于不相熟 Windows 开发的同学极度不敌对。因而本篇文章咱们不讲 Android 端的实现,重点讲 Windows 端的实际,不过我也不是 C ++ 技术栈的,只能浅浅分享我踩过的坑。

如何创立通信通道?

// 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));

如何解决音讯?

在下面创立的过程中,其实曾经把解决办法的传递给插件了。

// 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 ++ 插件来说,此计划的确能够解决不少问题。亲测可用的!

正文完
 0