FlutterBoost3.0釋出preview版本

閒魚技術發表於2021-06-22

作者:閒魚技術——皓黯

​ 在經歷了近兩個月的開發以及內部測試與線上灰度,FlutterBoost3.0的preview版本終於與大家見面了,與beta版本相比,這個版本在不大動主體結構的基礎上,增加了以下能力:

1.重構生命週期,確保生命週期語義準確2.雙端一致性近一步對齊3.增加自定義的啟動引數4.實現頁面返回傳參方案5.支援頁面透明能力6.增加自定義事件傳送機制7.增加前置攔截器8.提供更完善的文件與例子

​ 由於篇幅有限,就不對以上能力一一展開了。如果對具體實現感興趣的話,可以通過原始碼和文件來了解相應的功能,我們今天先來聊一聊生命週期部分的設計與實現、文件與用例和社群建設。

1. 頁面生命週期設計

​ FlutterBoost3.0有兩個和頁面相關的概念,一個是BoostContainer,另一個是BoostPage。每個BoostContainer在Native層都會有一個與之對應的容器,比如在Android端,這個容器可能是FlutterBoostActivity或者FlutterBoostFragment,而在iOS端,這個容器則指的是FBFlutterViewContainer。每個BoostContainer都會有一個與之對應的Navigator,因而一個BoostContainer可以包含多個BoostPage,這個設計也被我們稱之為雙重棧。細心的同學可能已經發現了,當我們需要開啟一個Flutter頁面時,有一個額外的引數withContainer,這個引數設定為false時,表示開啟頁面時不需要開啟一個新容器,而這個引數如果被設定為true,則會在開啟頁面的同時,去開啟一個新容器。

image.png

​ 在開發業務的過程中,我們常常需要知道一個Flutter頁面的可見性,比如我們寫了一個Flutter頁面,裡面有一個VideoWidget,我們希望VideoWidget在頁面可見的時候播放,在頁面不可見的時候暫停。對頁面可見性變化的監聽是FlutterBoost需要提供的核心能力之一,因此我們提供了一個名為PageVisibilityObserver的API,用於專門監聽頁面可見性變化。

​ 在一開始的設計當中,我們是通過上層Dart程式碼來掌控頁面生命週期的。我們在FlutterBoostAppState裡,維護了一個List,而在每個BoostContainer中,則維護了一個List,所以顯示的頁面實際上就是頂部BoostContainer中的頂部BoostPage,所以每次頂部的BoostContainer發生變化,或者頂部BoostContainer中的頂部BoostPage發生變化,我們就認為發生了頁面可見性變化,並將相應事件分發下去。

​ 然而這個方案在實踐的過程中,發現了一些問題,由於Dart側只對Flutter的雙層棧有感知,而對Native側的頁面棧則是毫無感知的,所以哪怕一個頁面是頂部BoostContainer中的頂部BoostPage,也不見得這個頁面就處於顯示狀態。舉個例子,比如Flutter頁面A去開啟一個Native頁面B,那麼在開啟Native頁面B之後,Flutter頁面A還是在雙重棧頂部,因此如果不做任何處理,那麼FlutterBoost還是認為Flutter頁面A是可見的。我們也嘗試了一些手段去解覺這些Bad Case,比如在Native層也維護一個Flutter頁面棧用來感知頁面顯示隱藏等。但是在後續的驗證過程中,我們發現這個方案可維護性不高,最終放棄了。

​ 之後我們決定還是採用更穩妥的方案,BoostContainer的顯示和隱藏,讓Native容器自己來控制,當Native容器展示的時候發出onContainerShow事件通知對應的BoostContainer展示,Native容器隱藏時再發出onContainerHide事件通知對應的的BoostContainer隱藏,這樣生命週期難以維護的問題也就解決了,這個方案的可維護性相比上一個方案要穩定,但是這個方案也有一個不得不解決的問題,第一次onContainerShow可能會收不到。我們以Android為例,FlutterBoostActivity建立後的第一次onResume發出了onContainerShow事件,然而該事件並不一定會被收到,因為此時可能頁面對應的Widget還沒有完成建立。穩定的生命週期事件是一個強訴求,所以這個問題我們需要在FlutterBoost3.0徹底解決。那麼這個問題該如何解決呢?為了解決這個問題,我們將第一次收到onContainerShow事件單獨處理,具體邏輯如下

void containerDidShow(BoostContainer container) {
    final id = container.pageInfo.uniqueId;
    assert(id != null);
    if (!hasShownPageIds.contains(id)) {
        hasShownPageIds.add(id);

        // This case indicates it is the first time that this container show
        // So we should dispatch event using
        // PageVisibilityBinding.dispatchPageShowEventOnPageShowFirstTime
        // to ensure the page will receive callback
        PageVisibilityBinding.instance
            .dispatchPageShowEventOnPageShowFirstTime(container.topPage.route);
    } else {
        PageVisibilityBinding.instance
            .dispatchPageShowEvent(container.topPage.route);
    }
}
複製程式碼

​ 而在PageVisibilityBinding.dispatchPageShowEventOnPageShowFirstTime中,我們將事件分發延遲到這一幀渲染結束

///When page show first time,we should dispatch event in [FrameCallback]
///to avoid the page can't receive the show event
void dispatchPageShowEventOnPageShowFirstTime(Route<dynamic> route) {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        dispatchPageShowEvent(route);
    });
}
複製程式碼

​ 這樣我們就能確保第一次onContainerShow事件能夠被接收到。從最後的程式碼來看,這是一個微不足道的改動,但是從設計上看,這個改動明確定義了頁面生命週期事件,確保了生命週期事件一定被呼叫到。

2. Flutter應用生命週期設計

​ 對於純Flutter應用來說,一個Flutter應用實際上是跑在一個Activity或者ViewController上的,所以Flutter應用的生命週期實際上也和Activity或ViewController的生命週期繫結了,這也是Flutter目前的實現。但是對於單引擎多頁面的混合棧場景來說,這無疑是不適用的。如果我們不對Flutter應用生命週期進行任何處理,那麼就有可能出現Flutter應用生命週期混亂,最終導致介面無法刷幀。之前我們解決這個問題的思路,都是從Native層出發,通過控制生命週期事件的傳送,來校正Flutter應用的生命週期。比如Android端,就是在每次Flutter應用被pause的時候,補發一個resume事件,而iOS端則是繞過FlutterViewController生命週期方法來自行管理生命週期事件。這樣做雖然校正了Flutter應用的生命週期,但最終也導致了上層應用的生命週期非常混亂,可能會出現pause和resume連續多次被呼叫的情況。

​ 為了上層使用者能有一個穩定的Flutter應用生命週期,我們決定將這部分能力接管。我們提供了一個BoostFlutterBinding,用於接管生命週期,相關程式碼如下

mixin BoostFlutterBinding on WidgetsFlutterBinding {

  bool _appLifecycleStateLocked = true;

  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    changeAppLifecycleState(AppLifecycleState.resumed);
  }

  static BoostFlutterBinding get instance => _instance;
  static BoostFlutterBinding _instance;

  @override
  void handleAppLifecycleStateChanged(AppLifecycleState state) {
    if (_appLifecycleStateLocked) {
      return;
    }
    Logger.log('boost_flutter_binding: handleAppLifecycleStateChanged ${state.toString()}');
    super.handleAppLifecycleStateChanged(state);
  }

  void changeAppLifecycleState(AppLifecycleState state) {
    if (SchedulerBinding.instance.lifecycleState == state) {
      return;
    }
    _appLifecycleStateLocked = false;
    handleAppLifecycleStateChanged(state);
    _appLifecycleStateLocked = true;
  }
} 
複製程式碼

​ 程式碼非常簡單,就是將handleAppLifecycleStateChanged方法給hook掉,這樣無論Native層如何呼叫Flutter設定的生命週期事件,都不會影響到上層Flutter應用的生命週期。另外我們提供了一個changeAppLifecycleState方法,這個方法可以真正來改變上層Flutter應用的生命週期,目前這個方法的呼叫時機與Flutter容器個數相關,當容器大於等於1,Flutter應用的生命週期狀態為resumed,而容器個數為0時,Flutter應用的生命週期狀態則為paused。

3. 文件與用例完善

​ FlutterBoost在之前只有一份接入文件,這對使用者來說並不算友好。因此我們決定提供更完善的文件。我們梳理了大家最關心的幾個主題:

•整合詳細步驟•基本的路由API•頁面生命週期監測相關API•自定義傳送跨端事件API

​ 對於這些主題,我們這次都提供了相對應的文件,讓使用者接入FlutterBoost可以更輕鬆,在遇到問題的時候也能更容易排查。另外我們也正在編寫全新的example3.0,配合文件使用,在這個新的example3.0中,我們會把所有文件中提到的部分都以用例的方式呈現出來,希望這能幫助到大家。

4. 社群建設

​ 之後Boost所有的開發計劃工作,都會在project看板上體現出來,對應的地址為(  github.com/alibaba/flu… )。對我們工作感興趣的同學,可以在看板上看到我們的工作規劃,歡迎一起交流探討。另外如果有哪些想要我們支援的feature,也可以提issue給我們,如果我們評估合理,也會加入到看板上。

image.png

​ 於此同時,我們增加了一個AUTHORS檔案(github.com/alibaba/flu… ,記錄所有FlutterBoost的貢獻者(Contributors)。之後給我們提PR的同學,在提供PR的同時,也可以在這個檔案上,加上你的名字,感謝大家為Boost做出的貢獻。

5. 未來展望

​ FlutterBoost3.0作為AliFlutter的核心基礎設施,目前主要由閒魚團隊和UC Hummer團隊進行開發維護,主要開發者包括noborder、0xZOne、christyuj、ColdPaleLight、luckysmg,另外也要感謝seedotlee、CheungSKei、bktoky、jk等同學為FlutterBoost3.0做出的貢獻,目前集團內已有多個App接入了FlutterBoost3.0,包括夸克、淘寶聯盟、吃貨筆記等。

​ 之後FlutterBoost3.0的preview版本原則上不會再做Breaking Change,目前已經使用了beta版的同學,也可以陸續切到preview版本上了,下個階段我們會將工作重心放在issue收斂與文件用例補齊上。

​ FlutterBoost作為一個開源專案,還有很長的路要走,感謝大家一路支援和包容,我們也希望有更多同學能參與到這個專案中來。

相關文章