<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Windows應用開發有著較為豐富和多樣的技術選型。C#/WPF 這種偏Native的閉源方案,目前開發人員相對比較小眾了。C++/QT 的跨平臺框架,C++對於GUI開發來說上手會更難。JavaScript/CEF/Electron 基於Chromium 的跨端框架,使用前端技術棧來構建桌面應用,效能會略低一些。總而言之各有所長,有一點可以確定的是,跨端能力成為了選型的重要考量。
Flutter從誕生之初起,其核心目標就是跨平臺,不僅僅支援Android和iOS的行動端裝置,同時包括桌面端和Web端。隨著2022年2月Flutter 2.10的推出,也帶來了首個支援Windows平臺的穩定版本。基於Flutter的跨平臺特性,行動端或Web端的Flutter應用也能夠在Windows系統上執行,Windows應用開發者能夠享受到Flutter開發帶來的便利和生產力上的提升,同時行動端開發者也能夠快速上手Windows應用開發了。
在進一步探索和預演之後,通過Flutter的能力,可以很方便地將行動端的業務模組遷移至PC端,儘可能地實現一碼多端,降低業務維護成本,以此為出發點,進行了Windows平臺的接入。
閒魚App已經在Android和iOS平臺上有了多年的積累,並且採用了Native和Flutter混合的技術方案,Flutter和Native相輔相成,共同組成了App的完整生態。如果想要讓Flutter相關的模組在Windows平臺上執行,那就需要讓Windows平臺補齊Android和iOS平臺提供給Flutter的能力。比如通過Platform Channel提供給Flutter側相關的Native能力,通過Platform View將Native檢視嵌入到Flutter頁面中,都需要在Windows平臺上進行重新開發。
Windows平臺通過Plugin或FFI的方式提供相關能力,需要使用C++編寫相關的平臺程式碼。如果Plugin的程式碼可以自閉環,即所有C++程式碼都可以在Plugin內編寫完成,那這個Plugin可以單獨抽成一個Dart庫。但是如果Plugin的程式碼需要複用其他Plugin或者主工程的C++程式碼,粗暴一點就是拷貝程式碼,或者通過CMakeLists來控制相互之間的依賴關係,通過find_package來完成標頭檔案和庫檔案的連結。一旦依賴關係比較複雜,CMakeLists就會變得臃腫,依賴關係發生變化時,也會牽一髮而動全身。隨著系統複雜度的提升,開發人員的增加,模組之間相互耦合在一起,單一模組的修改都會影響到所有模組。
針對上述的問題,對於底層的模組化設計,梳理了需要遵循的設計原則:
首先基於上述的設計原則,制定了模組化拆解的XModule方案,依據職責來劃分模組,設計對外暴露的抽象介面,抽象介面保持最小化原則,完成介面實現,編譯出模組的動態連結庫DLL,依賴到主工程並放置到特定目錄,執行時通過外掛機制進行動態載入。
其次針對模組化帶來的依賴管理複雜的問題,引入了vcpkg的依賴管理方案,通過清單模式便捷地管理各個模組,可以自動引入間接依賴,並且版本衝突問題也不復存在了。
結合XModule和vcpkg之後,最終形成了下面的結構,後面將詳細展開。
上述是一個登入模組的例子,Module 作為基礎類別,定義了模組的一些生命週期方法。LoginModule是對外公開的業務介面,裡面僅包含外部會用到的和登入業務相關的方法。LoginModuleImplV1類是登入邏輯的具體實現,不對外公開,裡面的私有成員變數和方法對外部是隱藏的,同時實現了Module和LoginModule的介面。Provider用於建立和管理Module範例。
這裡採用的思路是,底層模組和模組之間,上層和底層之間只依賴介面標頭檔案,標頭檔案內包含有限的需要對外暴露的介面。通過XModule這個框架,將實現和介面進行分離。
為了將介面和實現分離,用到了 pimpl (Pointer to Implementation) 的理念,將物件的實現細節隱藏在指標背後。LoginModule介面負責定義對外公開的API,LoginModuleImplV1類負責定義LoginModule的具體實現,也就是呼叫的指標實際指向的物件。呼叫方只能知道LoginModule中公開的API,而無法知道LoginModuleImplV1的實現細節,可以降低呼叫方的使用門檻,也可以降低錯誤使用的可能性。pimpl不僅解除了介面和實現之間的耦合關係,還可以降低檔案間的編譯依賴關係,起到“編譯防火牆”的作用,可以提高一定的編譯效率。
// LoginModuleProvider 通過宏自動生成 X_MODULE_PROVIDER_DEFINE_SINGLE(LoginModule, MIN_VERSION, MAX_VERSION); // LoginModuleImplV1Provider 通過宏自動生成 X_MODULE_DEFINE_SECONDARY_PROVIDER(LoginModuleImplV1, LoginModule);
XModule的模版開發方式,會增加很多類檔案,為了方便,通過宏來控制Provider類的自動生成。其中MIN_VERSION和MAX_VERSION是該Module介面能支援的最小和最大的版本範圍,可以限制後期dll外掛化載入時,不載入在版本之外的dll,避免產生衝突和錯誤,目前Provider的GetVersion使用的是MAX_VERSION。
// 由 X_MODULE_DEFINE_SECONDARY_PROVIDER 宏自動生成 class DLLEXPORT LoginModuleImplV1Provider : public LoginModuleProvider { public: LoginModule* Create() const { LoginModuleImplV1* p = new LoginModuleImplV1(); ((Module*)p)->OnCreate(); return p; } };
LoginModuleImplV1Provider可以通過呼叫Create方法拿到對應的LoginModuleImplV1範例。
x_module::ModuleCenter* module_center = x_module::ModuleCenter::GetInstance(); module_center->AcceptProviderType<LoginModuleProvider>();
ModuleCenter是所有Module的管理類,先通過x_module::ModuleCenter::GetInstance()拿到ModuleCenter的範例,它是一個跨dll的單例。然後要用之前的LoginModuleProvider去註冊一個Module型別到ModuleCenter中。LoginModuleProvider中定義了支援的Module型別,以及最小版本和最大版本,如果後續掃描到的dll中提供的對應型別的Provider中GetVersion返回的值不在最大版本和最小版本之間,那麼就不會被允許載入進來。
module_center->AddProvider(new LoginModuleImplV1Provider());
通過這種方式,可以將LoginModuleImplV1Provider註冊到ModuleCenter中,然後建立並管理LoginModuleImplV1的範例。但是這樣就顯式地依賴了LoginModuleImplV1Provider,違反了前面說過的依賴倒置原則,對開閉原則也不友好,因為這樣就只能通過修改程式碼來實現擴充套件了。
#include <x_module/connector.h> #include "login_module/login_module_impl.h" X_MODULE_CONNECTOR bool XModuleConnect(x_module::Owner& owner) { owner.add(new LoginModuleImplV1Provider()); return true; }
為了在載入dll時,來註冊Provider,增加了一個connector.cc,新增一個XModuleConnect方法,讓dll被載入之後,能夠找到XModuleConnect這個符號方法,並進行呼叫,在XModuleConnect被呼叫的時候,會呼叫AddProvider將Provider進行註冊。
std::string path = GetProgramDir(); module_center->Install(path, "login_module");
由於目前login_module.dll是直接放在exe同目錄的,所以這裡直接獲取了一下exe絕對路徑,然後呼叫Install方法,將路徑和dll名login_module傳入進去,這樣就完成了註冊。
auto* p_login_module = module_center->ModuleFromProtocol<LoginModule, LoginModuleProvider>(); if (p_login_module == nullptr) { (*move_result)->Error("-100", "login module 為空"); return; } bool islogin = p_login_module->IsLogin();
在使用時,只需要LoginModule和LoginModuleProvider這兩個抽象,就能獲取真實的LoginModuleImplV1這個範例,呼叫方僅需關心LoginModule所公開的API,完全螢幕蔽了對實現的依賴。後續底層擴充套件成了LoginModuleImplV2,只要LoginModule的公開API不變,對上層是無感知的。這種方式完全遵循了前面提到的設計原則,對團隊內的多人維護以及後續的更新迭代都帶來了穩定的保障。
模組拆分之後,帶來的副作用就是依賴管理會變得更加複雜,到C++這邊就是CMakeLists的膨脹。從行動端的角度來看這個問題,Android可以通過Gradle來管理依賴,依賴庫構建成aar之後上傳到Maven倉庫,implementation 'androidx.recyclerview:recyclerview:1.1.0'
像這樣通過包名、庫名和版本號來依賴具體的庫。iOS有CocoaPods,通過新增pod 'AFNetworking', '~> 2.6'
到Podfile來完成依賴的新增。前端也有NPM這樣的包管理器,所有依賴都在package.json這個檔案中宣告和管理。Flutter側也可以通過pubspec來管理各個依賴庫。為了獲得一致的體驗,解決C++側依賴管理的痛點,我們引入了微軟官方推出的vcpkg,vcpkg的清單模式可以得到類似的體驗。
這裡以fish-ffi-module模組為例子,檔案結構如下,其中include檔案裡面是對外公開的標頭檔案,src檔案包含當前庫內部使用的程式碼,cmake檔案下的config.cmake.in模版檔案用於生成xxx-config.cmake的檔案,用於被find_package找到。
. ├── CMakeLists.txt ├── LICENSE ├── cmake │ └── config.cmake.in ├── include │ └── fish_ffi_module.h ├── src │ ├── connector.cc │ ├── fish_ffi_module_impl_v1.cc │ └── fish_ffi_module_impl_v1.h ├── vcpkg-configuration.json └── vcpkg.json
vcpkg-configuration.json設定了私有源,後面會講到。vcpkg.json檔案,宣告了當前庫所依賴的其他庫,即vcpkg的依賴清單,其中"dependencies"欄位宣告了所使用的依賴名稱。
{ "name": "fish-ffi-module", "version": "1.0.0", "description": "A fish-ffi module based on fish-ffi-sdk.", "homepage": "", "dependencies": [ "fish-ffi-sdk", "x-module", "flutter-sdk" ] }
CMake工程最重要的就是CMakeLists檔案了,裡面設定了編譯相關的設定,新增了相關的註釋來幫助理解。
cmake_minimum_required(VERSION 3.15) # 倉庫版本常數,升級時修改 set(FISH_FFI_MODULE_VERSION "1.0.0") project(fish-ffi-module VERSION ${FISH_FFI_MODULE_VERSION} DESCRIPTION "A fish-ffi module based on fish-ffi-sdk." HOMEPAGE_URL "" LANGUAGES CXX) option(BUILD_SHARED_LIBS "Build using shared libraries" ON) # vcpkg清單中新增依賴之後,通過find_package就能找到 find_package(fish-ffi-sdk CONFIG REQUIRED) find_package(flutter-sdk CONFIG REQUIRED) find_package(x-module CONFIG REQUIRED) # configure_package_config_file 生成config要用到 include(CMakePackageConfigHelpers) # install 安裝要用到 include(GNUInstallDirs) # 當前庫的標頭檔案和原始檔 aux_source_directory(include HEADER_LIST) aux_source_directory(src SRC_LIST) add_library(fish-ffi-module SHARED ${HEADER_LIST} ${SRC_LIST} ) # 設定別名 add_library(fish-ffi-module::fish-ffi-module ALIAS fish-ffi-module) # 設定動態庫匯出宏,PRIVATE為編譯時,INTERFACE為執行時 if (BUILD_SHARED_LIBS AND WIN32) target_compile_definitions(fish-ffi-module PRIVATE "FISH_FFI_MODULE_EXPORT=__declspec(dllexport)" INTERFACE "FISH_FFI_MODULE_EXPORT=__declspec(dllimport)") endif () target_compile_features(fish-ffi-module PUBLIC cxx_std_17) # 新增標頭檔案 target_include_directories(fish-ffi-module PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> ) # 連結庫檔案 target_link_libraries(fish-ffi-module PRIVATE fish-ffi-sdk::fish-ffi-sdk) target_link_libraries(fish-ffi-module PRIVATE flutter-sdk::flutter-sdk) target_link_libraries(fish-ffi-module PRIVATE x-module::x-module) # 基於config.cmake.in的模板生成xxx-config.cmake的檔案 configure_package_config_file( cmake/config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/fish-ffi-module-config.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_DATADIR}/fish-ffi-module NO_SET_AND_CHECK_MACRO) # 生成xx-config-version.cmake檔案 write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/fish-ffi-module-config-version.cmake VERSION ${FISH_FFI_MODULE_VERSION} COMPATIBILITY SameMajorVersion) # 將上面生成的兩個config檔案,安裝到share/fish-ffi-module下 install( FILES ${CMAKE_CURRENT_BINARY_DIR}/fish-ffi-module-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/fish-ffi-module-config-version.cmake DESTINATION ${CMAKE_INSTALL_DATADIR}/fish-ffi-module) # 安裝標頭檔案 install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) # install target install(TARGETS fish-ffi-module EXPORT fish-ffi-module-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) # 匯出 install(EXPORT fish-ffi-module-targets NAMESPACE fish-ffi-module:: DESTINATION ${CMAKE_INSTALL_DATADIR}/fish-ffi-module)
這裡面最重要的一點是設定xx-config.cmake和xx-config-version.cmake的生成,vcpkg會在原始碼首次拉下來的時候進行編譯,編譯完在相應庫的share目錄生成上述兩個檔案,並且在CMake設定階段執行,這樣在使用find_package的時候就能獲取到這個庫以及對應版本號。總結一下就是,vcpkg幫助完成了程式碼的下載、編譯和設定,然後就可以方便的連結三方庫了。
私有源的自定義非常簡單,其實就是個Git倉庫,push到私有的git託管服務上即可。只需要將依賴庫的最新commit資訊記錄到這個倉庫裡面,通過模版化的設定就能完成依賴庫的釋出。
. ├── ports │ ├── fish-ffi-module │ │ ├── portfile.cmake │ │ └── vcpkg.json │ └── x-module │ ├── portfile.cmake │ └── vcpkg.json ├── versions │ ├── f- │ │ └── fish-ffi-module.json │ └── x- │ │ └── x-module.json │ └──baseline.json └── LICENSE
vcpkg裡面對依賴庫的定義叫port,這裡定義了兩個port,分別是fish-ffi-module和x-module。其中的檔案說明如下:
git rev-parse HEAD:ports/x-module
來生成git-tree,然後通過git commit --amend
追加提交到剛剛的commit中。在需要使用私有源的CMake工程根目錄,新增vcpkg-configuration.json,裡面內容如下。default-registry為預設源,指向官方的地址即可。registries下新增自定義的私有源,再通過指定packages,表示裡面的庫需要在這個私有源查詢。這樣就完成了私有源的設定。
{ "default-registry": { "kind": "git", "repository": "https://github.com/microsoft/vcpkg", "baseline": "f4b262b259145adb2ab0116a390b08642489d32b" }, "registries": [ { "kind": "git", "repository": "xxx.git", "baseline": "1ad54586a5a2fadb8c44d3f8f47754e849fc5a38", "packages": [ "x-module", "fish-ffi-sdk", "fish-ffi-module"] } ] }
在versions資料夾下還有一個baseline.json的檔案,這個檔案主要是設定基線用的,不像其他的依賴管理工具,vcpkg主要是通過這個基線來設定當前所使用的版本號的。
vcpkg可以勝任依賴管理的相關工作,綜上所述只是一個簡單使用,相比其他平臺的依賴管理工具略顯繁瑣,除此之外還有很多其他能力,需要到vcpkg.io的官方檔案裡面探索了。
Flutter應用接入Windows平臺,主要遇到的問題就是Windows側的一些能力的提供,需要對齊Android和iOS的已有能力。因為使用的是C++的開發語言,對於行動端開發者並不是那麼友好,學習曲線相對會比較抖。不過一旦平臺側的能力完善之後,又可以迴歸到Flutter這個熟悉的領域了,享受Flutter開發帶來的便捷。此外Windows應用的開發不僅僅只是螢幕加大版的行動端開發,還包括不同的輸入裝置(鍵盤滑鼠)、互動習慣、樣式風格、作業系統特性等,為了更好的平臺體驗,會帶來一定的適配成本,這一塊後續也將持續投入。
以上就是Flutter應用Windows平臺接入實踐詳解的詳細內容,更多關於Flutter接入Windows平臺的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45