日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Flutter桌面開發windows插件開發_Android

作者:Karl_wei ? 更新時間: 2022-12-25 編程語言

前言

通過此篇文章,你將了解到:

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

相關推薦

欄目分類
最近更新