網(wǎng)站首頁(yè) 編程語言 正文
前言
通過此篇文章,你將了解到:
Flutter插件的基本介紹;
windows插件開發(fā)的真實(shí)踩坑經(jīng)驗(yàn)。
我們都知道,F(xiàn)lutter的定位更多是作為一個(gè)跨平臺(tái)的UI框架,對(duì)于原生平臺(tái)的功能,開發(fā)過程中經(jīng)常需要插件來提供。不幸的是Windows的生態(tài)又極其不完整,插件開發(fā)必不可少。但網(wǎng)上windows的文章少之又少,所以本篇文章,我們一起來聊聊插件開發(fā)的一些技巧。
插件介紹
Flutter的插件主要分兩種:package和plugin。
- Package是純dart代碼的庫(kù),不涉及原生平臺(tái)的代碼;
- Plugin是原生插件庫(kù),是一種特殊的Package。Plugin需要開發(fā)者分別在各原生平臺(tái)實(shí)現(xiàn)對(duì)應(yīng)的能力。
其中Plugin是我們要著重講的,既然是原生平臺(tái)實(shí)現(xiàn),那跟dart層就勢(shì)必需要通訊。Flutter Plugin的通訊主要有:methodChannel、eventChannel、basicMessageChannel。
- MethodChannel:同步調(diào)用的通道,調(diào)用后可以通過result返回結(jié)果。可以 Native 端主動(dòng)調(diào)用,也可以Flutter主動(dòng)調(diào)用,屬于雙向通信。這種通信方式是我們?nèi)粘i_發(fā)中為最常用的方式, 關(guān)鍵點(diǎn)是Native 端的調(diào)用需要在主線程中執(zhí)行。
- EventChannel:異步事件通知的通道,一般是Native端主動(dòng)發(fā)出通知,F(xiàn)lutter接收通信信息。
- BasicMessageChannel:長(zhǎng)鏈接的通道,雙端可以隨時(shí)發(fā)出消息,對(duì)方收到消息后可以使用reply進(jìn)行回復(fù)。一般常用于需要雙向通信可不知道何時(shí)需要發(fā)送的場(chǎng)景。
windows插件編寫
Flutter Android的生態(tài)算是比較完整的,而且網(wǎng)上95%的插件文章,都是以移動(dòng)端為主,對(duì)于不熟悉Windows開發(fā)的同學(xué)極度不友好。因此本篇文章我們不講Android端的實(shí)現(xiàn),重點(diǎn)講Windows端的實(shí)踐,不過我也不是C++技術(shù)棧的,只能淺淺分享我踩過的坑。
- 如何創(chuàng)建通信通道?
// MethodChannel
void XXXPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows* registrar) {
// 創(chuàng)建一個(gè)MethodChannel
auto channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "usb_tool",
&flutter::StandardMethodCodec::GetInstance());
// 創(chuàng)建插件對(duì)象
auto plugin = std::make_unique<XXXPlugin>();
// 把通道設(shè)置給插件,同時(shí)傳入消息的處理入口
channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto& call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});
}
// EventChannel
// 創(chuàng)建事件流處理對(duì)象
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對(duì)象
auto eventChannel = std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
registrar->messenger(), eventChannelName,
&flutter::StandardMethodCodec::GetInstance());
// 把通道設(shè)置給插件
eventChannel->SetStreamHandler(std::move(eventHandler));
最后我們還需要把插件注冊(cè)進(jìn)項(xiàng)目中
registrar->AddPlugin(std::move(plugin));
- 如何處理消息? 在上面創(chuàng)建的過程中,其實(shí)已經(jīng)把處理方法的傳遞給插件了。
// MethodChannel的處理
// result即通信的對(duì)象
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回復(fù)消息
result->Success(flutter::EncodableValue(version_stream.str()));
} else {
result->NotImplemented();
}
}
// 主動(dòng)向Flutter端發(fā)送消息
std::unique_ptr < flutter::StreamHandlerError<flutter::EncodableValue>> XXXPlugin::OnListen(const flutter::EncodableValue* arguments,
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>>&& events) {
// 主動(dòng)發(fā)送
events_.reset(events.release());
return nullptr;
}
// Flutter取消監(jiān)聽時(shí)觸發(fā)
std::unique_ptr < flutter::StreamHandlerError<flutter::EncodableValue>> UsbToolPlugin::OnCancel(const flutter::EncodableValue* arguments) {
return nullptr;
}
BasicMessageChannel我暫時(shí)還沒有用過,這里就不做記錄了。但是看C++的api,還是很簡(jiǎn)單就能找到的。至于Flutter端的,無需多言。只要通信層連通了,其他想怎么玩都可以。
Windows插件的一些坑
這是本篇文章的重點(diǎn)。我們都知道Flutter是單線程的機(jī)制,來到原生平臺(tái)也一樣,Platform是運(yùn)行在Flutter的主線程的,自然是不能做任何耗時(shí)的,不然會(huì)卡住主線程,系統(tǒng)會(huì)把我們認(rèn)為無響應(yīng)的應(yīng)用,從而殺死應(yīng)用。
我們經(jīng)常會(huì)在使用windows插件時(shí),感覺點(diǎn)擊卡頓,其實(shí)就是很多插件沒有做這個(gè)處理,導(dǎo)致事件隊(duì)列等待調(diào)度。這主要是因?yàn)樵趙indows的開發(fā)習(xí)慣上,耗時(shí)操作會(huì)丟到子線程異步執(zhí)行,然后主線程如何等待執(zhí)行結(jié)果?使用while一直去查詢是否執(zhí)行完成,這在windows上成為掛起。
不過一個(gè)有趣的現(xiàn)象是:當(dāng)有耗時(shí)操作的時(shí)候,F(xiàn)lutter的動(dòng)畫是可以流程播放的,但是點(diǎn)擊事件卻卡住了,這時(shí)候C++的同學(xué)就會(huì)扯,你看動(dòng)畫都是流程的,問題肯定出在Flutter上?其實(shí)是因?yàn)閯?dòng)畫在Flutter中屬于微任務(wù),它的優(yōu)先級(jí)是高于事件隊(duì)列的。而while也是分配到事件隊(duì)列中,所以動(dòng)畫優(yōu)先執(zhí)行,點(diǎn)擊卻需要一直等到while結(jié)束。
在Android中,為了避免這個(gè)問題,我們一般會(huì)使用協(xié)程,把耗時(shí)操作丟給協(xié)程,讓系統(tǒng)幫我們進(jìn)行任務(wù)調(diào)度,通過await拿到執(zhí)行完之后的結(jié)果,再把結(jié)果返回給dart層。整個(gè)機(jī)制其實(shí)還是保留了flutter的單線程機(jī)制,從而避免了卡頓問題。
在Windows端,其實(shí)也有協(xié)程這個(gè)概念,比如WinRT、C++都有提供協(xié)程的能力。但問題在于協(xié)程這個(gè)東西,對(duì)于C++來說太新了,同時(shí)C++的歷史包袱實(shí)在太重,到現(xiàn)在還是用著很老版本的庫(kù)。這就導(dǎo)致很多C++的庫(kù)沒辦法遷移到協(xié)程這種方式,至少在我現(xiàn)在的業(yè)務(wù)中,切換成本極高,幾乎沒辦法完成。
但問題總得解決,目前我們主要使用異步通知的方式,來解決這個(gè)問題。此異步是真異步,非flutter單線程任務(wù)調(diào)度的異步。我們會(huì)把耗時(shí)的操作丟給子線程,但是我們不再通過while進(jìn)行異步轉(zhuǎn)同步,而是在子線程中,主動(dòng)通過channel去通知會(huì)Dart層。
if (*method == "getAsync") {
async_pipe_stream_->Get(request, std::bind(&XXXPlugin::OnResponse, this, std::placeholders::_1, *uuid));
// 直接返回true,但真正的執(zhí)行結(jié)果再OnResponse中主動(dòng)返回
result->Success(EncodableValue(true));
return;
}
在插件的dart代碼中,我們需要主動(dòng)創(chuàng)建一個(gè)MethodChannel的接收器,異步接收到后,通過執(zhí)行業(yè)務(wù)端傳入的回調(diào)通知回去。
class NativePlugin {
static const MethodChannel _channel =
MethodChannel('com.open.flutter/xxx/xxx');
static NativePlugin? _instance;
// 獲取實(shí)例,單例
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è)務(wù)端傳入的回調(diào)
await _onResponse(arguments);
}
});
}
插件的Flutter層需要接收/維護(hù)回調(diào)列表,不過此方式有隱患,傳入的回調(diào)容易造成閉包問題,增加一些內(nèi)存泄露的風(fēng)險(xiǎn);
但是對(duì)于沒辦法使用協(xié)程的C++插件來說,此方案確實(shí)可以解決不少問題。
原文鏈接:https://juejin.cn/post/7170627272830320654
相關(guān)推薦
- 2022-08-23 python數(shù)據(jù)分析繪圖可視化_python
- 2022-04-12 ASP動(dòng)態(tài)include文件_ASP基礎(chǔ)
- 2023-03-04 Golang錯(cuò)誤處理方式異常與error_Golang
- 2022-12-24 Kubernetes?ApiServer三大server權(quán)限與數(shù)據(jù)存儲(chǔ)解析_云和虛擬化
- 2022-11-08 PostgreSQL查看帶有綁定變量SQL的通用方法詳解_PostgreSQL
- 2022-12-10 C語言中如何實(shí)現(xiàn)桶排序_C 語言
- 2023-04-08 C語言字符串左旋的兩種實(shí)現(xiàn)方法_C 語言
- 2022-06-22 Git配置用戶簽名方式的拓展示例實(shí)現(xiàn)全面講解_其它綜合
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支