【Flutter】熊孩子拆元件系列之拆ListView(九)—— AutomaticKeepAlive和KeepAlive

語言: CN / TW / HK

theme: condensed-night-purple

「這是我參與11月更文挑戰的第6天,活動詳情檢視:2021最後一次更文挑戰」。

前言

Item這塊,前面看了一下貌似沒什麼太大作用的 KeyedSubtree ,接下來看下這兩個貌似跟KeepAlive有關係的東西,他倆又有什麼區別;

註釋部分

image.png

總結一下,AutoMaticKeepAlive是通過監聽KeepAliveNotification,來構建child,處理KeepAlive的一個widget;

image.png

而 KeepAlive 是一個通過給定的數值,來處理KeepAlive的Widget,他並不會自動處理事件;一般用於SliverList和SliverGrid;

執行原理

KeepAlive

首先從簡單的KeepAlive開始:

image.png

其所做的事也不復雜:

當被呼叫ApplyParentData的時候,檢查KeepAlive設定,如果不需要KeepAlive,那麼觸發重繪。否則就不用處理;

當然會更新ParentData中的KeepAlive屬性,而那個是在SlverList中,用來判斷是否需要放到KeepAliveBucket持久化的判斷依據,比如說之前提到的 _destroyOrCacheChild 方法:

image.png

再次拿的時候就會從這個bucket中拿:

image.png

而KeepAlive被觸發呼叫的地方就在AutomaticKeepAlive這塊;

AutomaticKeepAlive

首先是AutomaticKeepAlive的build方法:

image.png

只是簡單的構造了一個K呃呃篇Alive,並將_child 作為其child;

而_child 是這麼構造的:

image.png

結合註釋所述,那麼核心邏輯就是這個_addClient方法了;

image.png

在這段程式碼中,所做的事就三件: - 通過_createCallback(handler)方法,來建立KeppAliveNotification 的回撥; - 更新快取的handle及其回撥; - 判斷更新parentData的時機;

建立callback

這段程式碼中,註釋反而是比程式碼長,去掉註釋部分,程式碼是這樣的:

VoidCallback _createCallback(Listenable handle) { return () { assert(() { if (!mounted) { throw FlutterError( 'AutomaticKeepAlive handle triggered after AutomaticKeepAlive was disposed.\n' 'Widgets should always trigger their KeepAliveNotification handle when they are ' 'deactivated, so that they (or their handle) do not send spurious events later ' 'when they are no longer in the tree.', ); } return true; }()); _handles!.remove(handle); if (_handles!.isEmpty) { if (SchedulerBinding.instance!.schedulerPhase.index < SchedulerPhase.persistentCallbacks.index) { setState(() { _keepingAlive = false; }); } else { _keepingAlive = false; scheduleMicrotask(() { if (mounted && _handles!.isEmpty) { setState(() { assert(!_keepingAlive); }); } }); } } }; } 拋開前面的assert部分,其所做的事其實簡單的來說就是:

  • 移除快取的回撥;
  • 如果沒有快取的回調了:
  • 如果當前構建過程處於build、layout、paint等方法之前,那麼打髒,下一幀再更新;
  • 如果已經開始構建了,那麼將打髒操作加入到一個微佇列中(也就是說放到下下幀中?);

關於為什麼這麼做;註釋中是這麼解釋的:

// We were probably notified by a descendant when they were yanked out // of our subtree somehow. We're probably in the middle of build or // layout, so there's really nothing we can do to clean up this mess // short of just scheduling another build to do the cleanup. This is // very unfortunate, and means (for instance) that garbage collection // of these resources won't happen for another 16ms. // // The problem is there's really no way for us to distinguish these // cases: // // * We haven't built yet (or missed out chance to build), but // someone above us notified our descendant and our descendant is // disconnecting from us. If we could mark ourselves dirty we would // be able to clean everything this frame. (This is a pretty // unlikely scenario in practice. Usually things change before // build/layout, not during build/layout.) // // * Our child changed, and as our old child went away, it notified // us. We can't setState, since we _just_ built. We can't apply the // parent data information to our child because we don't _have_ a // child at this instant. We really want to be able to change our // mind about how we built, so we can give the KeepAlive widget a // new value, but it's too late. // // * A deep descendant in another build scope just got yanked, and in // the process notified us. We could apply new parent data // information, but it may or may not get applied this frame, // depending on whether said child is in the same layout scope. // // * A descendant is being moved from one position under us to // another position under us. They just notified us of the removal, // at some point in the future they will notify us of the addition. // We don't want to do anything. (This is why we check that // _handles is still empty below.) // // * We're being notified in the paint phase, or even in a post-frame // callback. Either way it is far too late for us to make our // parent lay out again this frame, so the garbage won't get // collected this frame. // // * We are being torn out of the tree ourselves, as is our // descendant, and it notified us while it was being deactivated. // We don't need to do anything, but we don't know yet because we // haven't been deactivated yet. (This is why we check mounted // below before calling setState.) // // Long story short, we have to schedule a new frame and request a // frame there, but this is generally a bad practice, and you should // avoid it if possible.

簡單的來說就是,整個構建過程,應該是一個原子性的操作;所以如果在構建過程中被子View或者其他方式通知了重構,那麼只能再安排一次構建來做這個操作;

判斷更新parentData的時機

在這一步所做的事: - 如果child不為空,那麼直接呼叫 _updateParentDataOfChild 方法; - 如果為空,那麼就按註釋中說的那樣,放到結尾再更新ParentData;

而這個 _updateParentDataOfChild 方法所做的事,就是字面意思:

image.png

最終會呼叫到 KeepAlive 自己的applyParentData方法:

image.png

總結:

這兩個Widget所做的事,無非就是通過監聽KeepAliveNotification,來更新SliverList中存放的ParentData,進而實現SliverList在銷燬的時候不呼叫Item本身的銷燬方法,而是放到一個Map中快取起來;

「其他文章」