Flutter桌面開發(fā)windows插件開發(fā)
前言
通過此篇文章,你將了解到:
Flutter插件的基本介紹;
windows插件開發(fā)的真實踩坑經(jīng)驗。
我們都知道,F(xiàn)lutter的定位更多是作為一個跨平臺的UI框架,對于原生平臺的功能,開發(fā)過程中經(jīng)常需要插件來提供。不幸的是Windows的生態(tài)又極其不完整,插件開發(fā)必不可少。但網(wǎng)上windows的文章少之又少,所以本篇文章,我們一起來聊聊插件開發(fā)的一些技巧。
插件介紹
Flutter的插件主要分兩種:package和plugin。
- Package是純dart代碼的庫,不涉及原生平臺的代碼;
- Plugin是原生插件庫,是一種特殊的Package。Plugin需要開發(fā)者分別在各原生平臺實現(xiàn)對應的能力。
其中Plugin是我們要著重講的,既然是原生平臺實現(xiàn),那跟dart層就勢必需要通訊。Flutter Plugin的通訊主要有:methodChannel、eventChannel、basicMessageChannel。
- MethodChannel:同步調(diào)用的通道,調(diào)用后可以通過result返回結(jié)果??梢?Native 端主動調(diào)用,也可以Flutter主動調(diào)用,屬于雙向通信。這種通信方式是我們?nèi)粘i_發(fā)中為最常用的方式, 關(guān)鍵點是Native 端的調(diào)用需要在主線程中執(zhí)行。
- EventChannel:異步事件通知的通道,一般是Native端主動發(fā)出通知,F(xiàn)lutter接收通信信息。
- BasicMessageChannel:長鏈接的通道,雙端可以隨時發(fā)出消息,對方收到消息后可以使用reply進行回復。一般常用于需要雙向通信可不知道何時需要發(fā)送的場景。
windows插件編寫
Flutter Android的生態(tài)算是比較完整的,而且網(wǎng)上95%的插件文章,都是以移動端為主,對于不熟悉Windows開發(fā)的同學極度不友好。因此本篇文章我們不講Android端的實現(xiàn),重點講Windows端的實踐,不過我也不是C++技術(shù)棧的,只能淺淺分享我踩過的坑。
- 如何創(chuàng)建通信通道?
// MethodChannel void XXXPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows* registrar) { // 創(chuàng)建一個MethodChannel auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>( registrar->messenger(), "usb_tool", &flutter::StandardMethodCodec::GetInstance()); // 創(chuàng)建插件對象 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 // 創(chuàng)建事件流處理對象 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); }); // 創(chuàng)建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));
- 如何處理消息? 在上面創(chuàng)建的過程中,其實已經(jīng)把處理方法的傳遞給插件了。
// 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端發(fā)送消息 std::unique_ptr < flutter::StreamHandlerError<flutter::EncodableValue>> XXXPlugin::OnListen(const flutter::EncodableValue* arguments, std::unique_ptr<flutter::EventSink<flutter::EncodableValue>>&& events) { // 主動發(fā)送 events_.reset(events.release()); return nullptr; } // Flutter取消監(jiān)聽時觸發(fā) std::unique_ptr < flutter::StreamHandlerError<flutter::EncodableValue>> UsbToolPlugin::OnCancel(const flutter::EncodableValue* arguments) { return nullptr; }
BasicMessageChannel我暫時還沒有用過,這里就不做記錄了。但是看C++的api,還是很簡單就能找到的。至于Flutter端的,無需多言。只要通信層連通了,其他想怎么玩都可以。
Windows插件的一些坑
這是本篇文章的重點。我們都知道Flutter是單線程的機制,來到原生平臺也一樣,Platform是運行在Flutter的主線程的,自然是不能做任何耗時的,不然會卡住主線程,系統(tǒng)會把我們認為無響應的應用,從而殺死應用。
我們經(jīng)常會在使用windows插件時,感覺點擊卡頓,其實就是很多插件沒有做這個處理,導致事件隊列等待調(diào)度。這主要是因為在windows的開發(fā)習慣上,耗時操作會丟到子線程異步執(zhí)行,然后主線程如何等待執(zhí)行結(jié)果?使用while一直去查詢是否執(zhí)行完成,這在windows上成為掛起。
不過一個有趣的現(xiàn)象是:當有耗時操作的時候,F(xiàn)lutter的動畫是可以流程播放的,但是點擊事件卻卡住了,這時候C++的同學就會扯,你看動畫都是流程的,問題肯定出在Flutter上?其實是因為動畫在Flutter中屬于微任務,它的優(yōu)先級是高于事件隊列的。而while也是分配到事件隊列中,所以動畫優(yōu)先執(zhí)行,點擊卻需要一直等到while結(jié)束。
在Android中,為了避免這個問題,我們一般會使用協(xié)程,把耗時操作丟給協(xié)程,讓系統(tǒng)幫我們進行任務調(diào)度,通過await拿到執(zhí)行完之后的結(jié)果,再把結(jié)果返回給dart層。整個機制其實還是保留了flutter的單線程機制,從而避免了卡頓問題。
在Windows端,其實也有協(xié)程這個概念,比如WinRT、C++都有提供協(xié)程的能力。但問題在于協(xié)程這個東西,對于C++來說太新了,同時C++的歷史包袱實在太重,到現(xiàn)在還是用著很老版本的庫。這就導致很多C++的庫沒辦法遷移到協(xié)程這種方式,至少在我現(xiàn)在的業(yè)務中,切換成本極高,幾乎沒辦法完成。
但問題總得解決,目前我們主要使用異步通知的方式,來解決這個問題。此異步是真異步,非flutter單線程任務調(diào)度的異步。我們會把耗時的操作丟給子線程,但是我們不再通過while進行異步轉(zhuǎn)同步,而是在子線程中,主動通過channel去通知會Dart層。
if (*method == "getAsync") { async_pipe_stream_->Get(request, std::bind(&XXXPlugin::OnResponse, this, std::placeholders::_1, *uuid)); // 直接返回true,但真正的執(zhí)行結(jié)果再OnResponse中主動返回 result->Success(EncodableValue(true)); return; }
在插件的dart代碼中,我們需要主動創(chuàng)建一個MethodChannel的接收器,異步接收到后,通過執(zhí)行業(yè)務端傳入的回調(diào)通知回去。
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!; } // 私有命名構(gòu)造函數(shù),做一次初始化 NativePlugin._internal(String defaultToken) { _defaultToken = defaultToken; _channel.setMethodCallHandler((MethodCall call) async { if (call.method == 'onResponse') { final arguments = Map<String, dynamic>.from(call.arguments); // 執(zhí)行業(yè)務端傳入的回調(diào) await _onResponse(arguments); } }); }
插件的Flutter層需要接收/維護回調(diào)列表,不過此方式有隱患,傳入的回調(diào)容易造成閉包問題,增加一些內(nèi)存泄露的風險;
但是對于沒辦法使用協(xié)程的C++插件來說,此方案確實可以解決不少問題。
以上就是Flutter桌面開發(fā)windows插件開發(fā)的詳細內(nèi)容,更多關(guān)于Flutter windows插件開發(fā)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android ScrollView實現(xiàn)反彈效果的實例
這篇文章主要介紹了 Android ScrollView實現(xiàn)反彈效果的實例的相關(guān)資料,這里自定義scrollview 并實現(xiàn)反彈效果,需要的朋友可以參考下2017-07-07RecyclerView+PagerSnapHelper實現(xiàn)抖音首頁翻頁的Viewpager效果
這篇文章主要為大家詳細介紹了RecyclerView+PagerSnapHelper實現(xiàn)抖音首頁翻頁的Viewpager效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10Android SQLite事務處理結(jié)合Listview列表顯示功能示例
這篇文章主要介紹了Android SQLite事務處理結(jié)合Listview列表顯示功能,較為詳細的分析了Android使用sqlite數(shù)據(jù)庫進行事務操作并結(jié)合Listview進行列表顯示的相關(guān)操作技巧,需要的朋友可以參考下2017-07-07AndroidStudio升級4.1坑(無法啟動、插件plugin不好用、代碼不高亮)
這篇文章主要介紹了AndroidStudio升級4.1坑(無法啟動、插件plugin不好用、代碼不高亮),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10android實現(xiàn)session保持簡要概述及實現(xiàn)
其實sesion在瀏覽器和web服務器直接是通過一個叫做name為sessionid的cookie來傳遞的,所以只要在每次數(shù)據(jù)請求時保持sessionid是同一個不變就可以用到web的session了,感興趣的你可以參考下本文或許對你有所幫助2013-03-03