1. 程式人生 > >Fragment的原理和優化

Fragment的原理和優化

 fragment 管理框架升級到了 0.1.0, 添加了一個新的介面 startFragmentAndDestroyCurrent, 可以方便的完成類似 startActivity 之後 finish 的效果

FragmentTransition#addToBackStack 的誤用

之前也有人在 issue 中問道如何實現類似 Activity#finish() 的效果,我不加思索的回答到:

FragmentTransition 有提供 addToBackStack(boolean) 方法, 啟動前一個 fragment (不是新的那個)時, addToBackStack 傳參為 false 就行。

這樣初看也確實解決了問題,一切看似完美,直到另一個 issue 的出現。在閱讀原始碼來解決這個問題時,我發現了 addToBackStack(false) 是存在問題的,這篇文章會指出問題所在,然後給出 QMUI 的解決方案。

BackStack 的工作機制

在之前的文章我也有提到, BackStack 並不是將 fragment 儲存到堆疊,而是將操作(add, remove等)儲存到堆疊,然後在返回時將操作逆著來就行。

在開始之前,先介紹 Fragment 的一個私有成員變數:

Fragment 存在一個成員變數 mBackStackNesting,它是標誌 fragment 是否存在於 BackStack 的重要變數,每個 fragment 的每次操作都會影響到它, 只有它的值小於等於0時,fragment 才會走到 onDestroy,從而得到釋放

一般情況下,我們切換 fragment 時 BackStack 的變更行為為: 

現在,讓我們來看看使用 addToBackStack(false) 時會發生什麼: 

回到之前提到的 issue。其出錯的原因是 findCurrentFragment 出錯,而 findCurrentFragment 會先在被新增到 FragmentManager 中的 fragment 佇列中去尋找,根據上面的描述,addToBackStack(false)會導致頁面存在多個 fragment, 所以猜測可能是它導致的問題,但我也不能確定我的猜測 100% 正確,因為我並沒能在開發環境中重現。

QMUI 的 fix 方案。

現在來說說 QMUI 的 fix 方案。其實也很簡單,我們直接對 BackStack 的最後一個 Entity 做一些修正(以上圖為例):

  1. 修改 Op[addB] 替換為 Op[addC]
  2. 將 B.mBackStackNesting 賦值給 C,完成 BackStack 中對 B 的替換
  3. B.mBackStackNesting減一 或者將 B.mBackStackNesting 置為 0, 使B在開啟新介面時得到釋放。

做完上述操作,才算真正的將 B destroy 掉而且保證堆疊的正確性。 除此,startFragmentAndDestroyCurrent 還提供了第二個引數, 這個引數是做什麼的呢?是用來控制轉場動畫的。假設 A->B 使用 startFragment 和轉場動畫 a,B->C 使用 startFragmentAndDestroyCurrent和轉場動畫 b,那麼從 C 返回到 A 時,該使用轉場動畫 a 還是 轉場動畫 b 呢?這就取決於你第二個引數傳什麼了。