網站首頁 編程語言 正文
前言
通過此篇文章,你將了解到:
Flutter插件的基本介紹;
windows插件開發的真實踩坑經驗。
我們都知道,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++技術棧的,只能淺淺分享我踩過的坑。
- 如何創建通信通道?
// 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++插件來說,此方案確實可以解決不少問題。
原文鏈接:https://juejin.cn/post/7170627272830320654
相關推薦
- 2022-07-24 基于python實現雙向鏈表_python
- 2022-09-24 C#中ref關鍵字的用法_C#教程
- 2022-04-24 Python元素集合的列表切片_python
- 2023-12-11 Spring中的事務管理
- 2024-03-24 feignClient注入失敗
- 2022-05-01 C語言main()函數的參數問題詳解_C 語言
- 2022-05-23 一起來學習C++的函數指針和函數對象_C 語言
- 2023-03-15 Pandas操作兩個Excel實現數據對應行的合并_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支