Flutter多引擎路由開發(fā)實踐

一、引言

背景

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多引擎頁面路由方案時,主要的目標和解決的問題包括以下幾個方面:

  1. 性能優(yōu)化
  • 目標:確保應(yīng)用在復雜的頁面切換和渲染過程中保持高性能,減少卡頓和延遲。

  • 問題:Flutter多引擎方案可能會帶來額外的資源消耗,如內(nèi)存和CPU的占用增加,需要平衡各個引擎的資源分配。

  1. 頁面狀態(tài)管理
  • 目標:在多個引擎之間實現(xiàn)一致的狀態(tài)管理,確保頁面切換時狀態(tài)保持正確。

  • 問題:多個引擎之間可能會存在狀態(tài)同步的問題,如何在不同引擎間高效地共享和傳遞狀態(tài)是一個關(guān)鍵挑戰(zhàn)。

  1. 路由的靈活性與擴展性
  • 目標:設(shè)計一個能夠適應(yīng)不同需求、具有高度靈活性和可擴展性的路由方案,以支持復雜的應(yīng)用場景。

  • 問題:需要考慮如何處理復雜的頁面跳轉(zhuǎn)邏輯,包括在原生頁面與Flutter頁面之間的雙向路由跳轉(zhuǎn),以及多引擎之間的跨引擎路由跳轉(zhuǎn)。

  1. 模塊化設(shè)計
  • 目標:將應(yīng)用拆分為多個模塊,每個模塊可以獨立開發(fā)、測試和部署,并且可以輕松地在不同引擎間集成。

  • 問題:如何設(shè)計一個合理的模塊化架構(gòu),使得各個模塊之間的依賴關(guān)系明確,同時保持良好的解耦性。

  1. 原生與Flutter的無縫集成
  • 目標:實現(xiàn)原生頁面與Flutter頁面之間的無縫切換,用戶體驗流暢。

  • 問題:在頁面切換時,如何確保過渡的平滑性和一致性,以及如何處理原生與Flutter之間的通信和數(shù)據(jù)共享。

  1. 導航歷史的管理
  • 目標:維護用戶的導航歷史,支持返回、前進等操作。

  • 問題:在多引擎場景下,如何有效管理導航歷史,確保用戶能夠按預期行為返回到之前的頁面。

這些目標和問題構(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 提供了一套完整的路由機制來管理頁面導航,主要包括 NavigatorRoute 這兩個核心概念。

Navigator:

  • Navigator 是一個堆棧管理器,用于管理應(yīng)用中的路由棧。每次頁面切換時,新的 Route 會被壓入棧頂,而當頁面返回時,棧頂?shù)?Route 會被彈出。

  • 可以通過 Navigator.pushNavigator.pop 等方法來在路由棧上進行頁面的添加和移除。

Route:

  • Route 表示導航堆棧中的一個頁面。Flutter 提供了幾種不同類型的 Route,如 MaterialPageRoute、CupertinoPageRoute 和自定義的 PageRoute,用于不同風格的頁面切換動畫和行為。

管理頁面路由:

  • 在單引擎的情況下,頁面路由的管理主要依賴于 NavigatorRoute 的組合。通過 Navigator.push 可以將新的頁面壓入路由棧,而通過 Navigator.pop 則可以返回到前一個頁面。

  • 另外,F(xiàn)lutter 還支持命名路由(Named Routes),通過 Navigator.pushNamedNavigator.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、EventChannelBasicMessageChannel 等平臺通道,將狀態(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ū)域(如 SharedPreferencesUserDefaults)。當一個引擎需要獲取其他引擎的狀態(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é)合 EventBusSharedMemoryCache 實現(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)

截屏2024-09-03 17.44.46.png

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、SharedCacheChangeNotifier 實現(xiàn)跨平臺、跨引擎的狀態(tài)同步。通過結(jié)合 Provider,可以在 Flutter 端實現(xiàn) Widget 狀態(tài)的同步。

Channel層:

  • NavigatorChannelNavigatorChannel 是一個 MethodChannel 對象,用于在原生和 Flutter 之間處理頁面導航。

  • EventBusChannelEventBusChannel 是一個 BasicMessageChannel 對象,負責在原生與 Flutter 之間,以及 Flutter 不同引擎之間發(fā)送和接收消息。

  • SharedCacheChannelSharedCacheChannel 是一個 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)存占用和啟動時間。

摘自multiple-flutters

在 Android 和 iOS 上添加多個 Flutter 實例的主要 API 基于新的 FlutterEngineGroup 類(Android API,iOS API)來構(gòu)建 FlutterEngines,而不是以前使用的 FlutterEngine 構(gòu)造器。

盡管 FlutterEngine API 是直接的且更容易使用,但從相同的 FlutterEngineGroup 中產(chǎn)生的 FlutterEngine 具有性能優(yōu)勢,可以共享許多常見的、可重復使用的資源,例如 GPU 上下文、字體度量和隔離組快照,從而實現(xiàn)更快的初始渲染延遲和更低的內(nèi)存占用。

FlutterEngineGroup 中產(chǎn)生的 FlutterEngines 可以像通常構(gòu)建的緩存 FlutterEngines 一樣連接到 UI 類,如 FlutterActivityFlutterViewController。

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框架提供的NavigatorRoute進行管理。這種方式是最常見的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)完全由原生代碼處理。通過標準的原生導航機制,如UINavigationControllerFragmentManager,可以實現(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)示例:

截屏2024-09-03 17.45.04.png

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)邏輯示例:

截屏2024-09-03 17.45.15.png

5.3 事件總線(EventBus)

5.3.1 EventBus工作流程

截屏2024-09-03 17.45.24.png

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ū)域(如SharedPreferencesUserDefaults)實現(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)示例圖

截屏2024-09-03 17.45.48.png

如圖所示緩存在原生端實現(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、SharedMemoryCacheChangeNotifier 的組合,實現(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)流程

截屏2024-09-03 17.49.57.png

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();
  }
}

六、實踐案例

案例分析。
demo示例。

七、測試與調(diào)試

為多引擎路由方案測試時,你需要考慮如何模擬多引擎場景、如何測試路由邏輯的正確性、以及如何確保各個引擎之間的狀態(tài)一致性。

多引擎測試的問題

在多引擎場景下,測試主要關(guān)注以下幾個方面:

  • 引擎的初始化與管理:需要確保每個測試用例中都能正確地初始化和銷毀多個引擎。

  • 路由的正確性:測試多個引擎之間的路由操作,確保路由能按照預期進行跳轉(zhuǎn)。

  • 狀態(tài)的共享與同步:在多個引擎之間進行狀態(tài)的同步測試,確保狀態(tài)能在不同引擎中保持一致。

  • 資源的管理:確保資源在多個引擎之間不會沖突或泄漏。

測試策略

  1. 模擬多引擎場景

  2. 測試引擎之間的狀態(tài)共享

  3. 測試資源管理

八、總結(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)健的保障。

九、參考文獻與資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容