一、引言
背景
Flutter 多引擎模式允許在一個應(yīng)用中同時運行多個 Flutter 引擎實例。這種模式特別適用于一些復雜的應(yīng)用場景,如跨平臺應(yīng)用和需要獨立狀態(tài)管理的子模塊。
在Flutter跨平臺開發(fā)中,難免會遇到Flutter和原生交互的問題,特別是Flutter和原生頁面切換問題。在單引擎方案中(常見的如咸魚的FlutterBoost),管理Flutter與原生頁面之間的路由可能會比較復雜,特別是在需要在原生和Flutter頁面之間頻繁切換的情況下。多引擎方案允許為不同的頁面或模塊創(chuàng)建獨立的引擎,使得路由管理更為直觀和靈活。
在跨平臺應(yīng)用中,可能會有多個獨立開發(fā)的模塊,它們各自有自己的狀態(tài)管理需求。通過多引擎模式,每個模塊可以運行在獨立的 Flutter 引擎上,確保它們之間的狀態(tài)不會互相干擾。此外,某些大型應(yīng)用可能需要將不同的功能模塊分開處理,以實現(xiàn)更好的模塊化和獨立性,這時多引擎模式也是一個理想的解決方案。
多引擎路由方案是為了解決如何在多個 Flutter 引擎之間高效地管理頁面導航和狀態(tài)的問題。隨著應(yīng)用復雜性的增加,傳統(tǒng)的單引擎路由方式可能無法滿足需求,比如在獨立的模塊間實現(xiàn)無縫導航等。因此,設(shè)計一個合適的多引擎路由方案,可以幫助開發(fā)者更好地管理應(yīng)用的整體結(jié)構(gòu),并提升用戶體驗。
目標
在設(shè)計Flutter多引擎頁面路由方案時,主要的目標和解決的問題包括以下幾個方面:
- 性能優(yōu)化
目標:確保應(yīng)用在復雜的頁面切換和渲染過程中保持高性能,減少卡頓和延遲。
問題:Flutter多引擎方案可能會帶來額外的資源消耗,如內(nèi)存和CPU的占用增加,需要平衡各個引擎的資源分配。
- 頁面狀態(tài)管理
目標:在多個引擎之間實現(xiàn)一致的狀態(tài)管理,確保頁面切換時狀態(tài)保持正確。
問題:多個引擎之間可能會存在狀態(tài)同步的問題,如何在不同引擎間高效地共享和傳遞狀態(tài)是一個關(guān)鍵挑戰(zhàn)。
- 路由的靈活性與擴展性
目標:設(shè)計一個能夠適應(yīng)不同需求、具有高度靈活性和可擴展性的路由方案,以支持復雜的應(yīng)用場景。
問題:需要考慮如何處理復雜的頁面跳轉(zhuǎn)邏輯,包括在原生頁面與Flutter頁面之間的雙向路由跳轉(zhuǎn),以及多引擎之間的跨引擎路由跳轉(zhuǎn)。
- 模塊化設(shè)計
目標:將應(yīng)用拆分為多個模塊,每個模塊可以獨立開發(fā)、測試和部署,并且可以輕松地在不同引擎間集成。
問題:如何設(shè)計一個合理的模塊化架構(gòu),使得各個模塊之間的依賴關(guān)系明確,同時保持良好的解耦性。
- 原生與Flutter的無縫集成
目標:實現(xiàn)原生頁面與Flutter頁面之間的無縫切換,用戶體驗流暢。
問題:在頁面切換時,如何確保過渡的平滑性和一致性,以及如何處理原生與Flutter之間的通信和數(shù)據(jù)共享。
- 導航歷史的管理
目標:維護用戶的導航歷史,支持返回、前進等操作。
問題:在多引擎場景下,如何有效管理導航歷史,確保用戶能夠按預期行為返回到之前的頁面。
這些目標和問題構(gòu)成了設(shè)計Flutter多引擎頁面路由方案時的核心關(guān)注點,通過有效的解決這些問題,能夠?qū)崿F(xiàn)一個高效、靈活、可擴展的多引擎路由方案。
二、基礎(chǔ)概念與原理
2.1 Flutter 多引擎模式
Flutter 多引擎模式是一種在應(yīng)用中同時使用多個Flutter引擎的架構(gòu)方案,允許在一個應(yīng)用中同時運行多個Flutter引擎實例。每個引擎實例可以獨立管理其各自的路由、頁面和狀態(tài),彼此之間互不干擾。這種模式在復雜應(yīng)用中,特別是需要在Flutter頁面和原生頁面之間頻繁切換時,提供了更大的靈活性。
2.2 為什么使用多引擎模式?
路由管理:多引擎模式可以簡化跨模塊、跨平臺的路由管理,使頁面切換更加流暢。在需要頻繁切換Flutter頁面和原生頁面時,多引擎模式可以更自然地處理路由和狀態(tài)管理,原生頁面與Flutter頁面可以通過多個引擎實現(xiàn)無縫跳轉(zhuǎn),提升用戶體驗。
隔離性:每個引擎實例獨立運行,擁有自己的內(nèi)部導航棧、UI 和應(yīng)用程序狀態(tài),避免了單一引擎處理多個任務(wù)時可能出現(xiàn)的性能瓶頸和資源爭奪問題。
性能優(yōu)化:通過多個引擎并行處理復雜任務(wù),減少單個引擎的壓力,提升整體性能。
模塊化設(shè)計:允許不同團隊獨立開發(fā)各自的模塊,并在最終應(yīng)用中集成,增強了代碼的可維護性和擴展性。
2.3 Flutter 頁面路由機制
Flutter 提供了一套完整的路由機制來管理頁面導航,主要包括 Navigator 和 Route 這兩個核心概念。
Navigator:
Navigator是一個堆棧管理器,用于管理應(yīng)用中的路由棧。每次頁面切換時,新的Route會被壓入棧頂,而當頁面返回時,棧頂?shù)?Route會被彈出。可以通過
Navigator.push和Navigator.pop等方法來在路由棧上進行頁面的添加和移除。
Route:
-
Route表示導航堆棧中的一個頁面。Flutter 提供了幾種不同類型的Route,如MaterialPageRoute、CupertinoPageRoute和自定義的PageRoute,用于不同風格的頁面切換動畫和行為。
管理頁面路由:
在單引擎的情況下,頁面路由的管理主要依賴于
Navigator和Route的組合。通過Navigator.push可以將新的頁面壓入路由棧,而通過Navigator.pop則可以返回到前一個頁面。另外,F(xiàn)lutter 還支持命名路由(Named Routes),通過
Navigator.pushNamed和Navigator.pop來管理頁面跳轉(zhuǎn),這種方式使得頁面跳轉(zhuǎn)更為直觀和易于管理。
2.4 單引擎情況下原生頁面和 Flutter 頁面之間的路由
在單引擎的情況下,原生頁面和 Flutter 頁面之間的路由跳轉(zhuǎn)會遇到以下挑戰(zhàn):
狀態(tài)管理的復雜性:
- 由于原生和 Flutter 頁面之間的狀態(tài)并不共享,每次從原生頁面返回到 Flutter 頁面時,可能需要重新構(gòu)建 Flutter 頁面,導致數(shù)據(jù)的丟失或狀態(tài)的重置。這要求在頁面切換之間,需要精確管理和保存應(yīng)用狀態(tài)。
動畫和體驗不一致:
- 原生和 Flutter 頁面之間的過渡動畫可能不一致,這會導致用戶在不同平臺之間切換時的體驗割裂。需要額外處理動畫一致性問題。常見的是頻繁切換導致導航棧錯亂的問題。
深度鏈接和全局導航的處理:
- 深度鏈接(Deep Link)和全局導航的處理在單引擎情況下更為復雜,因為必須協(xié)調(diào)原生和 Flutter 兩套導航系統(tǒng),確保用戶可以從任意入口進入并導航至目標頁面。
性能問題:
- 在頻繁進行原生與 Flutter 頁面切換時,可能會產(chǎn)生性能瓶頸,尤其是在啟動或銷毀 Flutter 引擎時。這種情況下,如何平衡性能和用戶體驗是一個重要的考慮因素。
通過回顧這些問題,我們可以更清晰地理解為什么在復雜應(yīng)用中需要引入多引擎模式,以及多引擎頁面路由方案所要解決的核心問題。
三、多引擎頁面路由的挑戰(zhàn)
在多引擎模式下,每個 Flutter 引擎實例都是獨立的,這意味著它們各自維護獨立的內(nèi)存空間、狀態(tài)和生命周期。因此,如何在多個引擎之間共享狀態(tài)以及處理相關(guān)的挑戰(zhàn),成為了一個關(guān)鍵問題。
3.1 基礎(chǔ)性的問題
一致性和同步問題
在多個引擎之間保持狀態(tài)的一致性是一個挑戰(zhàn),尤其是在狀態(tài)頻繁變更時。如果不同引擎之間的狀態(tài)不同步,可能會導致應(yīng)用出現(xiàn)意外行為。
需要考慮如何確保狀態(tài)同步的實時性,以及如何處理同步延遲和沖突問題。
性能問題
狀態(tài)共享可能涉及大量的數(shù)據(jù)傳輸,尤其是在使用平臺通道或共享存儲時。如果同步數(shù)據(jù)量大或頻率高,可能會影響應(yīng)用的性能。
必須權(quán)衡狀態(tài)同步的實時性與系統(tǒng)性能之間的關(guān)系。
狀態(tài)的生命周期管理
每個引擎都有獨立的生命周期管理,這意味著當一個引擎被銷毀時,如何處理它的狀態(tài)成為一個問題。如果狀態(tài)需要在引擎被銷毀后仍然保持,就需要額外的機制來管理這些狀態(tài)。
通過合理設(shè)計狀態(tài)管理機制,并選擇合適的狀態(tài)同步方法,可以有效應(yīng)對多引擎模式下的狀態(tài)共享挑戰(zhàn)。
安全性問題
- 狀態(tài)共享需要確保數(shù)據(jù)的安全性,尤其是在跨平臺通道和共享存儲的情況下。需要考慮數(shù)據(jù)的加密和訪問控制,防止敏感數(shù)據(jù)在狀態(tài)共享過程中被泄露。
3.2 多引擎狀態(tài)共享
使用平臺通道(Platform Channels):
可以利用 Flutter 提供的
MethodChannel、EventChannel和BasicMessageChannel等平臺通道,將狀態(tài)信息在原生層和各個 Flutter 引擎之間進行傳遞。例如,使用MethodChannel將狀態(tài)數(shù)據(jù)從一個引擎?zhèn)鬟f到原生層,再由原生層將這些數(shù)據(jù)分發(fā)給另一個引擎。這種方式雖然靈活,但需要手動管理狀態(tài)同步,并且可能涉及較多的原生代碼實現(xiàn)。
通過共享存儲(Shared Storage):
在各個引擎之間使用共享存儲來保存狀態(tài)信息,例如通過數(shù)據(jù)庫、文件系統(tǒng)、或共享的內(nèi)存區(qū)域(如
SharedPreferences或UserDefaults)。當一個引擎需要獲取其他引擎的狀態(tài)時,可以從這些共享存儲中讀取數(shù)據(jù)。這種方法適用于需要持久化的數(shù)據(jù),但對于實時性要求較高的狀態(tài)同步,可能會有延遲或一致性問題。
引入中間件或事件總線(Event Bus):
在應(yīng)用中引入事件總線(Event Bus)或中間件架構(gòu),通過發(fā)布-訂閱模式實現(xiàn)各個引擎之間的狀態(tài)同步。狀態(tài)變化被發(fā)布為事件,各個引擎可以根據(jù)需要訂閱這些事件并更新自己的狀態(tài)。
這種方式解耦了狀態(tài)變化與引擎之間的關(guān)系,有助于保持代碼的清晰和模塊化,但引入事件總線可能會增加延遲和復雜度。
3.3 路由一致性
在多引擎模式下,不同的引擎可能負責不同的功能模塊,這樣的設(shè)計雖然能夠有效地隔離模塊、提升性能,但是在管理路由時,會帶來一定的復雜性。
在多引擎模式下,確保路由一致性需要綜合考慮不同的同步機制和設(shè)計模式。通過統(tǒng)一的路由管理服務(wù)、原生平臺的中介作用、共享的路由?;蛘呤录偩€,能夠有效地管理多個引擎之間的路由狀態(tài)同步,從而提升應(yīng)用的可靠性和用戶體驗。
3.4 資源管理
在多引擎模式下,資源管理變得尤為重要,因為每個引擎獨立運行,可能導致資源的重復加載和浪費。要在這種情況下高效地管理資源,需要采取一系列措施,確保資源的合理使用與優(yōu)化。
3.4.1 共享資源管理層
建立一個共享的資源管理層,用于管理各個引擎之間的資源訪問與分配。這個層可以作為一個全局服務(wù),通過依賴注入或單例模式進行共享。
實現(xiàn)思路:
緩存系統(tǒng):建立全局緩存系統(tǒng),避免多個引擎重復加載相同的資源。資源首次加載后,存儲在緩存中,其他引擎可以直接從緩存中讀取。
資源引用計數(shù):通過引用計數(shù)來跟蹤資源的使用情況,當所有引擎都不再需要某個資源時,才釋放該資源。
3.4.2 統(tǒng)一的網(wǎng)絡(luò)連接管理
在多引擎模式下,多個引擎可能會同時發(fā)起網(wǎng)絡(luò)請求。為了避免重復請求和網(wǎng)絡(luò)連接浪費,建議建立統(tǒng)一的網(wǎng)絡(luò)連接管理機制。
在多引擎模式下,高效的資源管理至關(guān)重要。通過共享資源管理層、統(tǒng)一的網(wǎng)絡(luò)連接管理、資源復用與懶加載、引擎生命周期管理以及異步任務(wù)與后臺處理等方法,可以有效減少資源浪費,提升應(yīng)用性能和用戶體驗。針對具體的應(yīng)用需求,可以靈活組合以上策略,確保資源的高效使用。
實現(xiàn)思路:
網(wǎng)絡(luò)請求代理:引入一個網(wǎng)絡(luò)請求代理層,所有的網(wǎng)絡(luò)請求通過該代理層進行統(tǒng)一管理。同樣的請求只發(fā)起一次,其結(jié)果在各個引擎間共享。
請求隊列與合并:如果多個引擎需要相同的數(shù)據(jù),可以將這些請求合并為一個,減少不必要的網(wǎng)絡(luò)消耗。
3.4.3 資源復用與懶加載
資源復用與懶加載是有效的資源管理策略,特別是在多引擎模式下??梢酝ㄟ^懶加載減少初始資源消耗,通過資源復用避免重復加載。
實現(xiàn)思路:
懶加載:只在需要時才加載資源,避免占用多余的內(nèi)存或計算資源。
資源復用:盡可能復用已經(jīng)加載的資源,避免多個引擎重復加載相同的資源。
3.4.4 引擎生命周期管理
管理各個引擎的生命周期,合理地分配和釋放資源。未使用的引擎應(yīng)該及時銷毀或休眠,以釋放資源。
實現(xiàn)思路:
引擎激活與休眠:當引擎不再需要活躍時,將其休眠或銷毀,并釋放所有占用的資源。
資源池:為常用資源建立資源池,避免頻繁的創(chuàng)建與銷毀操作,提高資源利用率。
3.4.5 異步任務(wù)與后臺處理
對于耗時的資源操作,可以采用異步任務(wù)或后臺處理,避免阻塞主線程,提升應(yīng)用的整體性能。
實現(xiàn)思路:
異步加載:將資源加載操作放在異步任務(wù)中,避免阻塞 UI 渲染。
后臺處理:使用后臺線程處理復雜的計算或 I/O 操作,減少主線程的負擔。
3.5 第三方庫的支持
在使用多引擎替代單引擎時,確實需要特別注意一些第三方庫和插件的兼容性問題。以下是一些關(guān)鍵點和注意事項:
3.5.1 Channel 管理
問題:在多引擎環(huán)境中,每個引擎都可能需要獨立的 Channel 實例。這意味著原生與 Flutter 的通信通道需要在每個引擎之間正確管理。
注意事項:
- 確保每個引擎的
Channel實例都正確創(chuàng)建并且不會互相覆蓋。 - 當一個引擎被銷毀時,處理好相關(guān)
Channel的銷毀,避免造成消息發(fā)送錯誤或異常。
3.5.2 狀態(tài)管理
問題:某些第三方庫可能假設(shè)只有一個 Flutter 引擎在運行,這可能導致在多引擎環(huán)境下出現(xiàn)狀態(tài)同步或管理問題。
注意事項:
- 驗證第三方庫的狀態(tài)管理是否支持多引擎環(huán)境,特別是涉及到跨引擎的數(shù)據(jù)同步和狀態(tài)保持。
- 考慮使用專門為多引擎設(shè)計的狀態(tài)管理解決方案,例如結(jié)合
EventBus和SharedMemoryCache實現(xiàn)跨引擎狀態(tài)同步。
3.5.3 資源共享
問題:在多引擎環(huán)境中,多個引擎之間的資源(如 GPU 上下文、字體資源等)需要有效共享,以避免不必要的內(nèi)存開銷和性能問題。
注意事項:
- 確保使用支持多引擎資源共享的庫或插件,特別是那些涉及圖形渲染和其他資源管理的庫。
- 使用
FlutterEngineGroup來優(yōu)化資源共享,提高多個引擎的創(chuàng)建和管理效率。
3.5.4 插件兼容性
問題:一些插件可能依賴于單引擎的特性或行為,在多引擎環(huán)境下可能不兼容或表現(xiàn)異常。
注意事項:
- 在決定使用某個插件之前,檢查其是否支持多引擎環(huán)境,查看插件的文檔或聯(lián)系插件維護者獲取支持信息。
- 如果某個插件不支持多引擎環(huán)境,考慮尋找替代插件或?qū)Σ寮M行適配。
3.5.5 解決方案
在進行多引擎替代單引擎的過程中,保持對第三方庫和插件兼容性的關(guān)注,并且通過詳細的測試來確保系統(tǒng)的穩(wěn)定性和性能。
在多引擎環(huán)境中遇到第三方庫不支持的情況時,可以通過以下兩種主要解決方案來解決這些問題:
本地化第三方庫,自行維護 Channel
-
自定義和調(diào)整:對不支持多引擎的第三方庫進行本地化處理,以確保它們能夠適應(yīng)多引擎環(huán)境的需求。這包括對庫的源代碼進行必要的修改和優(yōu)化,以支持多個
FlutterEngine實例的獨立通信和管理。 -
維護獨立的 Channel:手動管理和維護
Channel實例,確保每個FlutterEngine有獨立的通信通道。這樣可以避免Channel實例被覆蓋或重復使用的問題。
自行實現(xiàn)替代插件
- 開發(fā)自定義插件:在無法本地化現(xiàn)有庫的情況下,可以開發(fā)自定義插件以替代功能。設(shè)計并實現(xiàn)一個新的插件,確保其能夠支持多引擎環(huán)境中的所有必需功能。
- 功能匹配和擴展:自定義插件應(yīng)盡可能地匹配現(xiàn)有插件的功能,并根據(jù)需要進行擴展或優(yōu)化,以滿足特定的需求。
通過本地化第三方庫和自行實現(xiàn)替代插件,能夠有效應(yīng)對多引擎環(huán)境中第三方庫不兼容的問題。這些解決方案可以確保項目在復雜環(huán)境中的穩(wěn)定性和兼容性,同時提升系統(tǒng)的性能和可靠性。實施這些策略時,務(wù)必進行充分的測試和驗證,以確保所有修改和插件都能符合項目的需求和標準。
四、多引擎路由方案設(shè)計
4.1 設(shè)計原則
在設(shè)計多引擎路由方案時,需要遵循以下核心原則:
性能優(yōu)先: 確保路由方案不會對應(yīng)用性能造成負擔,特別是在內(nèi)存和CPU使用方面。引擎之間的通信應(yīng)該是高效的,避免不必要的資源消耗。
模塊化設(shè)計: 將不同的功能模塊劃分為獨立的引擎,確保它們可以獨立開發(fā)和維護。每個模塊可以專注于自己的職責,而不需要了解其他模塊的內(nèi)部實現(xiàn)。
易擴展性: 路由方案應(yīng)易于擴展,能夠支持未來的功能擴展或模塊添加。引擎之間的接口應(yīng)設(shè)計得盡可能通用,以便輕松引入新的模塊或引擎。
狀態(tài)管理: 需要確保多引擎之間的狀態(tài)同步,避免狀態(tài)不一致的問題??梢允褂霉蚕矸?wù)層、全局狀態(tài)管理器或依賴注入框架來管理狀態(tài)。
資源管理: 在多個引擎之間高效管理資源,避免重復加載和資源浪費。例如,共享常見的資源(如網(wǎng)絡(luò)連接、緩存)以減少資源開銷。
容錯性與穩(wěn)定性: 路由方案應(yīng)具備良好的容錯性,能夠處理異常情況,確保應(yīng)用的穩(wěn)定性。在某個引擎發(fā)生故障時,其他引擎應(yīng)能正常工作。
用戶體驗一致性: 確保用戶在不同引擎之間切換時,體驗的一致性。無論用戶在哪個模塊,路由邏輯應(yīng)保持連貫性和一致性。
4.2 架構(gòu)概覽
以下提供方案的總體架構(gòu)圖,展示插件的各個層級和模塊的基本構(gòu)成。
4.2.1 多引擎插件(Flutter Meteor)架構(gòu)

4.2.2 多引擎插件(Flutter Meteor)架構(gòu)簡介
Flutter層:
Navigator:用于管理 Flutter 頁面導航,負責處理從 Flutter 端發(fā)起的導航請求。通過參數(shù)判斷并決定打開 Flutter 頁面、原生頁面或啟動新引擎。
EventBus:負責在 Flutter 端發(fā)送和接收消息。當發(fā)送消息時,通過
BasicMessageChannel通知原生端,原生端再將消息分發(fā)給各個引擎和原生訂閱者;當接收到消息時,則向 Flutter 端的訂閱者傳遞消息。SharedCache:用于處理 Flutter 端發(fā)起的共享內(nèi)存讀寫操作,最終將數(shù)據(jù)存儲在原生端,實現(xiàn)原生與多個 Flutter 引擎之間的內(nèi)存共享。
SharedState:負責多引擎間的狀態(tài)共享,基于
EventBus、SharedCache和ChangeNotifier實現(xiàn)跨平臺、跨引擎的狀態(tài)同步。通過結(jié)合Provider,可以在 Flutter 端實現(xiàn)Widget狀態(tài)的同步。
Channel層:
NavigatorChannel:
NavigatorChannel是一個MethodChannel對象,用于在原生和 Flutter 之間處理頁面導航。EventBusChannel:
EventBusChannel是一個BasicMessageChannel對象,負責在原生與 Flutter 之間,以及 Flutter 不同引擎之間發(fā)送和接收消息。SharedCacheChannel:
SharedCacheChannel是一個BasicMessageChannel對象,用于在原生與 Flutter 之間,以及 Flutter 各個引擎之間進行共享內(nèi)存的讀寫操作。
Native層:
- Navigator: 負責處理原生端的頁面導航請求,根據(jù)需求決定是打開原生頁面還是啟動新的 Flutter 引擎。
- EventBus: 負責在原生端發(fā)送和接收信息,可以向原生訂閱者發(fā)送消息,同時通過各引擎的 BasicMessageChannel 向 Flutter 發(fā)送消息;在接收到消息后,向自身的訂閱者廣播,并通過各引擎的 BasicMessageChannel 向 Flutter 轉(zhuǎn)發(fā)消息。
- SharedCache: 處理原生端發(fā)起的共享內(nèi)存讀寫操作,確保原生和 Flutter 多引擎之間能夠共享內(nèi)存。
4.3 核心組件
- 多引擎管理器(EngineManager) : 負責多引擎的創(chuàng)建和生命周期管理,維護各引擎之間以及引擎與原生之間的通信通道。確保各引擎在不同上下文中的獨立運行,同時支持跨引擎與原生模塊的雙向通信。
- 路由導航器(Navigator) : 實現(xiàn)跨引擎的路由器,負責管理跨引擎的頁面導航。通過統(tǒng)一的路由機制,確保在不同引擎和原生模塊間的頁面跳轉(zhuǎn)流暢、一致,并處理復雜的導航場景。
-
引擎間通信機制: 使用
MethodChannel實現(xiàn) Flutter 與原生之間的路由導航,利用BasicMessageChannel處理多引擎之間的數(shù)據(jù)共享和通信,通過EventBus實現(xiàn)引擎間事件的廣播與訂閱,確保各模塊之間的無縫交互。 - 事件 總線 (EventBus) : 作為多引擎間以及各引擎與原生模塊間的消息中樞,負責消息的發(fā)布與訂閱,實現(xiàn)引擎間的協(xié)同工作,確保各模塊能夠及時接收到相關(guān)事件。
- 共享存儲(SharedMemoryCache) : 提供統(tǒng)一的緩存機制,管理多個引擎與原生模塊間需要共享的內(nèi)存數(shù)據(jù),確保數(shù)據(jù)在不同引擎實例間的一致性與持久性。
- 共享狀態(tài)(SharedState) : 實現(xiàn)狀態(tài)的同步與共享,負責更新多個引擎與原生模塊間需要保持一致的狀態(tài),確保在復雜的應(yīng)用場景中,各個引擎實例及原生模塊間的狀態(tài)能夠?qū)崟r同步,避免狀態(tài)不一致帶來的問題。
五、實現(xiàn)細節(jié)
5.1 引擎管理 (EngineManager)
如何在 Flutter 應(yīng)用中初始化多個引擎,并且如何管理它們的生命周期?
利用FlutterEngineGroup來創(chuàng)建和管理多個引擎,這樣引擎的生命周期由FlutterEngineGroup來自動維護。在 Flutter 應(yīng)用中使用 FlutterEngineGroup 可以方便地創(chuàng)建和管理多個 FlutterEngine 實例。FlutterEngineGroup 允許多個引擎共享 Dart VM 的資源,從而減少內(nèi)存占用和啟動時間。
在 Android 和 iOS 上添加多個 Flutter 實例的主要 API 基于新的
FlutterEngineGroup類(Android API,iOS API)來構(gòu)建FlutterEngines,而不是以前使用的FlutterEngine構(gòu)造器。盡管
FlutterEngineAPI 是直接的且更容易使用,但從相同的FlutterEngineGroup中產(chǎn)生的FlutterEngine具有性能優(yōu)勢,可以共享許多常見的、可重復使用的資源,例如 GPU 上下文、字體度量和隔離組快照,從而實現(xiàn)更快的初始渲染延遲和更低的內(nèi)存占用。從
FlutterEngineGroup中產(chǎn)生的FlutterEngines可以像通常構(gòu)建的緩存FlutterEngines一樣連接到 UI 類,如FlutterActivity或FlutterViewController。從
FlutterEngineGroup創(chuàng)建的第一個FlutterEngine不需要繼續(xù)存在,只要至少有一個FlutterEngine存活,后續(xù)的FlutterEngines仍可以共享資源。從
FlutterEngineGroup創(chuàng)建第一個FlutterEngine的性能特性與以前使用構(gòu)造器構(gòu)建FlutterEngine相同。當
FlutterEngineGroup中的所有FlutterEngines都被銷毀后,下一次創(chuàng)建的FlutterEngine具有與第一次引擎相同的性能特性。
FlutterEngineGroup本身不需要在所有生成的引擎之后繼續(xù)存在。銷毀FlutterEngineGroup不會影響現(xiàn)有的生成引擎,但會移除生成其他共享資源的FlutterEngines的能力。
代碼示例(以iOS為例):
class MeteorEngineManager: NSObject {
private static let channelProviderList = MeteorWeakArray<FlutterMeteorChannelProvider>()
// FlutterEngineGroup 用于管理所有引擎
private static let flutterEngineGroup = FlutterEngineGroup(name: "itbox.meteor.flutterEnginGroup", project: nil)
public static func createFlutterEngine(options: MeteorEngineGroupOptions? = nil) -> FlutterEngine {
var arguments: Dictionary<String, Any> = Dictionary<String, Any>.init()
let initialRoute = options?.initialRoute
let entrypointArgs = options?.entrypointArgs
if(initialRoute != nil) {
arguments["initialRoute"] = initialRoute
}
if(initialRoute != nil) {
arguments["routeArguments"] = entrypointArgs
}
var entrypointArgList:Array<String> = Array<String>.init()
do {
let jsonData = try JSONSerialization.data(withJSONObject: arguments, options: .prettyPrinted)
if let jsonString = String(data: jsonData, encoding: .utf8) {
entrypointArgList.append(jsonString)
}
} catch {
print("Error converting dictionary to JSON")
}
// 創(chuàng)建新引擎
let engineGroupOptions = FlutterEngineGroupOptions.init()
engineGroupOptions.entrypoint = options?.entrypoint
engineGroupOptions.initialRoute = initialRoute
engineGroupOptions.entrypointArgs = entrypointArgList
engineGroupOptions.libraryURI = options?.libraryURI
let flutterEngine: FlutterEngine = flutterEngineGroup.makeEngine(with: engineGroupOptions)
// engineCache[flutterEngine.binaryMessenger] = flutterEngine
return flutterEngine
}
/// 第一個引擎的Channel
public static func firstEngineChannelProvider() -> FlutterMeteorChannelProvider? {
return channelProviderList.allObjects.first
}
/// 最后一個引擎的Channel
public static func lastEngineChannelProvider() -> FlutterMeteorChannelProvider? {
return channelProviderList.allObjects.last
}
/// 所有引擎的Channel
public static func allEngineChannelProvider() -> [FlutterMeteorChannelProvider] {
return channelProviderList.allObjects
}
/// 保存Channel
public static func saveEngineChannelProvider(provider: FlutterMeteorChannelProvider) {
if !channelProviderList.contains(provider) {
channelProviderList.add(provider)
}
}
}
代碼中通過弱引用列表channelProviderList來維護各個引擎的methodChannel, 使得原生和Flutter或者Flutter引擎之間可以通過MethodChannel相互傳遞數(shù)據(jù)、同步狀態(tài)和路由跳轉(zhuǎn)等。
5.2 路由導航(Navigator)
在多引擎模式下,不同的引擎可能負責不同的功能模塊,這樣的設(shè)計雖然能夠有效地隔離模塊、提升性能,但是在管理全局路由時,會帶來一定的復雜性。為了確保全局路由的一致性,需要有專門的路由管理機制。
5.2.1 頁面路由管理
-
Flutter引擎內(nèi)部頁面跳轉(zhuǎn): 在Flutter內(nèi)部,頁面跳轉(zhuǎn)直接使用Flutter框架提供的
Navigator和Route進行管理。這種方式是最常見的Flutter頁面導航方案,能夠在同一引擎中實現(xiàn)流暢的頁面過渡和導航管理。 -
Flutter跳轉(zhuǎn)至Native: 當需要從Flutter頁面跳轉(zhuǎn)到原生頁面時,可以通過
MethodChannel與原生進行通信。通過這種方式,F(xiàn)lutter可以調(diào)用原生代碼來管理頁面跳轉(zhuǎn),從而實現(xiàn)從Flutter頁面切換到原生頁面。 - Native跳轉(zhuǎn)至Flutter: 通常情況下,從原生頁面跳轉(zhuǎn)到Flutter頁面時,會創(chuàng)建一個新的Flutter引擎,并通過原生代碼來打開Flutter頁面。這種方式有助于保持Flutter與原生之間的模塊化,同時允許多個引擎獨立運行,避免資源沖突。
-
Native跳轉(zhuǎn)至Native: 在原生頁面之間的跳轉(zhuǎn)完全由原生代碼處理。通過標準的原生導航機制,如
UINavigationController或FragmentManager,可以實現(xiàn)頁面的跳轉(zhuǎn)和管理,確保原生頁面之間的無縫切換。
5.2.2 導航棧管理
Flutter和Native各自維護各自的路由棧,提供全局接口,通過MethodChannel 可以動態(tài)獲取當前整個應(yīng)用的路由棧,;利用這個全局路由??梢允沟迷材芟窦僃lutter頁面跳轉(zhuǎn)一樣實現(xiàn)pushReplacementNamed、pushNamedAndRemoveUntil、popUntil等操作,保證跳轉(zhuǎn)方法的一致性;
導航棧結(jié)構(gòu)示例:

FlutterContainer在原生對應(yīng)的是iOS-FlutterViewController, Android-FlutterActivity;
NativeContainer在原生對應(yīng)的是iOS-UIViewViewController, Android-Activity;
示例中的導航棧:FlutterPage1->...->FlutterPage5->NativeContainerB->FlutterPage6->...->FlutterPage8->FlutterPage9->...->FlutterPage12->NativeContainerE
在頁面的導航過程中也是按著這個順序push和pop操作,原生和Flutter的Navigator相互配合,在適當?shù)臅r機切換頁面。
5.2.3 Navigator工作流程
push跳轉(zhuǎn)邏輯示例:

5.3 事件總線(EventBus)
5.3.1 EventBus工作流程

5.3.2 Flutter代碼示例
import 'package:flutter/services.dart';
import 'package:flutter_meteor/engine/engine.dart';
import 'package:hz_tools/hz_tools.dart';
typedef MeteorEventBusListener = void Function(dynamic arguments);
class MeteorEventBusListenerItem {
final String? listenerId;
final MeteorEventBusListener listener;
MeteorEventBusListenerItem(this.listenerId, this.listener);
}
class MeteorEventBus {
final BasicMessageChannel methodChannel =
const BasicMessageChannel('itbox.meteor.multiEnginEventChannel', StandardMessageCodec());
// 工廠方法構(gòu)造函數(shù) - 通過MeteorEventBus()獲取對象
factory MeteorEventBus() => _getInstance();
// instance的getter方法 - 通過MeteorEventBus.instance獲取對象2
static MeteorEventBus get instance => _getInstance();
// 靜態(tài)變量_instance,存儲唯一對象
static MeteorEventBus? _instance;
// 獲取唯一對象
static MeteorEventBus _getInstance() {
_instance ??= MeteorEventBus._internal();
return _instance!;
}
//初始化...
MeteorEventBus._internal() {
//初始化其他操作...
// _pluginPlatform = MeteorMethodChannel();
methodChannel.setMessageHandler(
(message) async {
HzLog.d('收到來自原生的通知message:$message');
receiveEvent(message);
},
);
}
final Map<String, List<MeteorEventBusListenerItem>> _listenerMap = {};
/// 添加訂閱者-接收事件
static void addListener(
{required String eventName, String? listenerId, required MeteorEventBusListener listener}) {
HzLog.t(
'MeteorEventBus addListener isMain:${MeteorEngine.isMain} eventName:$eventName, listener:$listener');
var list = instance._listenerMap[eventName];
list ??= <MeteorEventBusListenerItem>[];
list.add(MeteorEventBusListenerItem(listenerId, listener));
instance._listenerMap[eventName] = list;
}
/// 移除訂閱者-結(jié)束事件
/// 當listener 為空時會移除eventName的所有l(wèi)istener,因此慎用
static void removeListener(
{required String eventName, String? listenerId, MeteorEventBusListener? listener}) {
HzLog.t(
'MeteorEventBus removeListener isMain:${MeteorEngine.isMain} eventName:$eventName, listener:$listener');
var list = instance._listenerMap[eventName];
if (eventName.isEmpty || list == null) return;
if (listenerId != null) {
list.removeWhere(
(element) => element.listenerId == listenerId,
);
} else if (listener != null) {
list.removeWhere(
(element) => element.listener == listener,
);
} else {
list.clear();
}
instance._listenerMap[eventName] = list;
}
/// 已加訂閱者-發(fā)送事件
/// eventName 事件名稱
/// withMultiEngine 是否發(fā)送多引擎,默認true表示支持多引擎
/// data 傳送的數(shù)據(jù)
static void commit({required String eventName, bool? withMultiEngine = true, dynamic data}) {
HzLog.t(
'MeteorEventBus commit isMain:${MeteorEngine.isMain} eventName:$eventName, data:$data, withMultiEngine:$withMultiEngine');
if (withMultiEngine == true) {
/// 多引擎則交給原生處理
commitToMultiEngine(eventName: eventName, data: data);
} else {
/// 如果只支持當前引擎則直接調(diào)用
commitToCurrentEngine(eventName: eventName, data: data);
}
}
static void commitToCurrentEngine({required String eventName, dynamic data}) {
var list = instance._listenerMap[eventName];
HzLog.t(
'MeteorEventBus commitToCurrentEngine isMain:${MeteorEngine.isMain} eventName:$eventName, data:$data, listeners:$list');
if (list == null) return;
int len = list.length - 1;
//反向遍歷,防止在訂閱者在回調(diào)中移除自身帶來的下標錯位
if (list.isNotEmpty) {
for (var i = len; i > -1; --i) {
MeteorEventBusListener listener = list[i].listener;
listener.call(data);
}
}
}
static Future<dynamic> commitToMultiEngine({required String eventName, dynamic data}) async {
HzLog.d(
'MeteorEventBus commitToMultiEngine isMain:${MeteorEngine.isMain} eventName:$eventName, data:$data');
/// 如果支持多引擎則交給原生處理
Map<String, dynamic> methodArguments = {};
methodArguments['eventName'] = eventName;
methodArguments['data'] = data;
// final result = await instance.methodChannel
// .invokeMethod(MeteorChannelMethod.multiEngineEventCallMethod, methodArguments);
final result = await instance.methodChannel.send(methodArguments);
HzLog.t('MeteorEventBus commitToMultiEngine isMain:${MeteorEngine.isMain} result:$result');
return result;
}
static void receiveEvent(dynamic event) {
if (event is Map) {
final eventMap = event;
final eventName = event['eventName'];
HzLog.t('MeteorEventBus isMain:${MeteorEngine.isMain} allListeners:${instance._listenerMap}');
var list = instance._listenerMap[eventName];
list?.forEach((listenerItem) {
listenerItem.listener.call(eventMap['data']);
});
HzLog.d('MeteorEventBus isMain:${MeteorEngine.isMain} eventName:$eventName, listeners $list');
}
}
}
5.4 共享緩存(SharedMemoryCache)
共享緩存用于在多個Flutter引擎之間或Flutter與原生之間共享狀態(tài)信息。共享緩存可以分為內(nèi)存緩存和磁盤緩存兩種方式。
-
磁盤緩存:
磁盤緩存通常通過數(shù)據(jù)庫、文件系統(tǒng)或共享存儲區(qū)域(如SharedPreferences或UserDefaults)實現(xiàn)。當某個引擎需要獲取其他引擎的狀態(tài)時,可以從這些共享存儲中讀取數(shù)據(jù),這樣可以確保不同引擎在應(yīng)用的不同生命周期中持久化和共享狀態(tài)。 -
內(nèi)存緩存:
內(nèi)存緩存主要用于解決應(yīng)用運行過程中,各個引擎以及原生模塊之間的狀態(tài)共享問題。這類緩存存在于內(nèi)存中,僅在應(yīng)用運行時有效,當應(yīng)用退出后,這些狀態(tài)不會被保留。我們可以自定義內(nèi)存緩存來實現(xiàn)這一目的。
為了使得各個Flutter引擎以及Flutter與原生之間都能夠共享緩存,緩存的實際存儲應(yīng)放置在原生端。需要共享的狀態(tài)會被寫入同一個共享緩存中,各個引擎通過各自的Channel與原生端通信,從而實現(xiàn)狀態(tài)的存取和同步。這樣不僅能夠確保狀態(tài)的統(tǒng)一管理,還可以避免因多個引擎獨立運行而帶來的數(shù)據(jù)不一致問題。
5.4.1 緩存結(jié)構(gòu)示例圖

如圖所示緩存在原生端實現(xiàn),每個引擎都有對應(yīng)的Channel和原生進行通信,通過Channel存取共享狀態(tài)。
5.4.2 Flutter代碼示例
// Autogenerated from Pigeon (v21.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
PlatformException _createConnectionError(String channelName) {
return PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel: "$channelName".',
);
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
}
class SharedCacheApi {
/// Constructor for [SharedCacheApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
SharedCacheApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: __pigeon_binaryMessenger = binaryMessenger,
__pigeon_messageChannelSuffix =
messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? __pigeon_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String __pigeon_messageChannelSuffix;
Future<void> setString(String key, String? value) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.setString$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<String?> getString(String key) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.getString$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return (__pigeon_replyList[0] as String?);
}
}
Future<void> setBool(String key, bool? value) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.setBool$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<bool?> getBool(String key) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.getBool$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return (__pigeon_replyList[0] as bool?);
}
}
Future<void> setInt(String key, int? value) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.setInt$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<int?> getInt(String key) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.getInt$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return (__pigeon_replyList[0] as int?);
}
}
Future<void> setDouble(String key, double? value) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.setDouble$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<double?> getDouble(String key) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.getDouble$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return (__pigeon_replyList[0] as double?);
}
}
Future<void> setList(String key, List<Object?>? value) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.setList$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<List<Object?>?> getList(String key) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.getList$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return (__pigeon_replyList[0] as List<Object?>?)?.cast<Object?>();
}
}
Future<void> setMap(String key, Map<String?, Object?>? value) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.setMap$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<Map<String?, Object?>?> getMap(String key) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.getMap$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return (__pigeon_replyList[0] as Map<Object?, Object?>?)?.cast<String?, Object?>();
}
}
Future<void> setBytes(String key, List<int?>? value) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.setBytes$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
Future<List<int?>?> getBytes(String key) async {
final String __pigeon_channelName =
'cn.itbox.flutter_meteor.CacheApi.getBytes$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return (__pigeon_replyList[0] as List<Object?>?)?.cast<int?>();
}
}
}
5.6 狀態(tài)共享(SharedState)
利用 EventBus、SharedMemoryCache 和 ChangeNotifier 的組合,實現(xiàn)跨平臺和跨引擎之間的狀態(tài)同步機制。
-
EventBus:
用于在不同的Flutter引擎之間,或者Flutter與原生之間,實現(xiàn)消息的高效傳遞。通過EventBus,可以廣播和接收來自不同引擎或平臺的狀態(tài)變化事件,從而觸發(fā)相應(yīng)的狀態(tài)更新。 -
SharedMemoryCache:
作為共享狀態(tài)的存儲媒介,SharedMemoryCache確保了跨引擎和跨平臺狀態(tài)的一致性和持久性。各個引擎和原生模塊可以通過它共享和訪問相同的狀態(tài)數(shù)據(jù),實現(xiàn)狀態(tài)的同步和統(tǒng)一管理。 -
ChangeNotifier:
負責監(jiān)聽狀態(tài)變化并通知訂閱者,在狀態(tài)發(fā)生改變時自動觸發(fā)相應(yīng)的UI更新。ChangeNotifier與EventBus和SharedMemoryCache配合,使得狀態(tài)變化能夠及時傳播到各個引擎和平臺,并驅(qū)動對應(yīng)的UI響應(yīng)。
業(yè)務(wù)端還可以結(jié)合 Provider 框架,通過注入ChangeNotifier來實現(xiàn)Widget的狀態(tài)同步。Provider能夠簡化狀態(tài)管理,使得跨引擎和跨平臺的狀態(tài)同步變得更加直觀和高效,同時確保UI在狀態(tài)變化時能夠保持一致性和實時性。
5.6.1 實現(xiàn)流程

5.6.2 Flutter代碼示例
import 'package:flutter/cupertino.dart';
import 'package:flutter_meteor/flutter_meteor.dart';
class GlobalStateService extends ChangeNotifier with GlobalSharedStateMixin {
String eventName = 'GlobalStateChanged';
GlobalStateService() {
fetchState();
MeteorEventBus.addListener(
eventName: eventName,
listener: (dynamic data) {
if (data is String) {
_updateCurrentEngineState(data);
}
},
);
}
String _sharedState = "Initial State";
String get sharedState => _sharedState;
// Fetch state from native platform
Future<void> fetchState() async {
final String? state = await getString('state');
_updateCurrentEngineState(state ?? 'Initial State');
}
// Update state on both Flutter and native platform
Future<void> updateState(String newState) async {
// _updateCurrentEngineState(newState);
await setString('state', newState);
MeteorEventBus.commit(eventName: eventName, data: newState);
}
void _updateCurrentEngineState(String newState) {
_sharedState = newState;
notifyListeners();
}
}
六、實踐案例
七、測試與調(diào)試
為多引擎路由方案測試時,你需要考慮如何模擬多引擎場景、如何測試路由邏輯的正確性、以及如何確保各個引擎之間的狀態(tài)一致性。
多引擎測試的問題
在多引擎場景下,測試主要關(guān)注以下幾個方面:
引擎的初始化與管理:需要確保每個測試用例中都能正確地初始化和銷毀多個引擎。
路由的正確性:測試多個引擎之間的路由操作,確保路由能按照預期進行跳轉(zhuǎn)。
狀態(tài)的共享與同步:在多個引擎之間進行狀態(tài)的同步測試,確保狀態(tài)能在不同引擎中保持一致。
資源的管理:確保資源在多個引擎之間不會沖突或泄漏。
測試策略
模擬多引擎場景
測試引擎之間的狀態(tài)共享
測試資源管理
八、總結(jié)與展望
8.1 總結(jié)
使用 Flutter 多引擎方案有以下幾個主要優(yōu)點:
8.1.1 性能提升
負載分擔:通過分配不同的任務(wù)給不同的引擎實例,避免了單一引擎的過度負載,提升了應(yīng)用的響應(yīng)速度和流暢度。
獨立資源管理:每個引擎實例獨立管理資源,減少了資源競爭,從而提高了性能。
8.1.2 更好的原生集成
原生與Flutter的平滑切換:多引擎模式下,每個引擎可以獨立處理自己的視圖和生命周期,使得在Flutter頁面和原生頁面之間的切換更加自然和高效。
定制化路由管理:允許在原生頁面和Flutter頁面之間靈活定義和管理路由,滿足復雜的導航需求。
8.1.3 提高穩(wěn)定性
錯誤隔離:一個引擎中的異常不會影響其他引擎的正常運行,增強了應(yīng)用的穩(wěn)定性。
獨立調(diào)試:可以分別對不同引擎進行調(diào)試,定位問題更加直接和快速。
8.1.4 適應(yīng)復雜場景
多任務(wù)并行:在需要同時處理多個復雜任務(wù)或顯示多個Flutter視圖時,多引擎模式提供了更好的并行處理能力。
靈活的UI展示:可以在不同引擎實例上顯示不同的Flutter UI,滿足多窗口或多屏幕應(yīng)用的需求。
8.2 展望
8.2.1 進一步提升性能:
引擎間通信優(yōu)化: 探索低延遲的引擎間通信機制,減少跨引擎狀態(tài)同步和數(shù)據(jù)共享的延遲,提升應(yīng)用響應(yīng)速度和用戶體驗。
資源預加載與緩存: 實現(xiàn)智能化的資源預加載和緩存機制,根據(jù)用戶行為預測和預加載即將使用的資源,避免資源加載的卡頓問題,從而提升性能。
8.2.2 擴展功能與兼容性:
跨引擎路由的統(tǒng)一管理: 開發(fā)更智能的全局路由管理器,支持復雜的跨引擎路由規(guī)則和動態(tài)路由調(diào)整,增強多引擎方案的靈活性和兼容性。支持深度鏈接(Deep Link)和全局導航的處理,確保用戶可以從任意入口進入并導航至目標頁面。
更細粒度的路由管理:應(yīng)該進一步精細化,不同的模塊對應(yīng)的引擎維護自己的路由;
支持更多應(yīng)用場景: 研究如何在多引擎模式下支持多窗口、多用戶的應(yīng)用場景,確保在復雜交互和多任務(wù)處理中的應(yīng)用表現(xiàn)穩(wěn)定。
更細粒度的狀態(tài)管理: 通過細化狀態(tài)管理的粒度,進一步隔離引擎間的狀態(tài),支持復雜應(yīng)用中的多態(tài)性和高可擴展性,適應(yīng)更復雜的業(yè)務(wù)場景。
8.2.3 體驗優(yōu)化:
導航操作的流暢性改進:
當前在實現(xiàn)諸如pushReplacementNamed、pushNamedAndRemoveUntil、popUntil等原生導航操作時,用戶體驗尚未達到最佳效果,需要進一步優(yōu)化,提升頁面跳轉(zhuǎn)和返回的流暢性。初次引擎加載的速度優(yōu)化:
目前第一個引擎啟動時存在加載較慢的問題,用戶可能會經(jīng)歷一段白屏時間。未來可以通過預加載等技術(shù)手段進行優(yōu)化,加快引擎的啟動速度,從而改善用戶的初次加載體驗。
8.2.4 模塊化開發(fā):
獨立開發(fā):不同的模塊可以使用各自的引擎進行開發(fā),互不干擾,提升開發(fā)效率和代碼復用性。
靈活的代碼組織:通過多引擎,可以根據(jù)不同業(yè)務(wù)需求將應(yīng)用分解為多個獨立的部分,方便后期的維護和擴展。
隨著多引擎應(yīng)用場景的拓展和技術(shù)的不斷發(fā)展,未來的優(yōu)化方向和擴展功能將更加注重性能提升、兼容性擴展、開發(fā)者體驗優(yōu)化。通過這些優(yōu)化措施,多引擎頁面路由方案將更加成熟,能夠更好地滿足復雜應(yīng)用的需求,并為業(yè)務(wù)提供更穩(wěn)健的保障。