現在市面的應用介面大多是透過一個Fragment容器+底部導航欄框架來實現頁面切換的,而當我們想要去搭建一個這樣的框架時,上層的Fragment容器是可選的,常見的有FragmentContanerView、ViewPager、ViewPager2。我們應該如何選擇呢?這時就需要考慮這三者的自身自帶的一些特性區別:
在我目前寫的專案中這三者都有使用到,一開始並沒有覺得有什麼區別,因為寫的都是一些很簡單的專案。但在一次使用FragmentContainerView的過程中,我明顯的感覺到了其與ViewPager2的一個顯著區別:切換Fragment時生命週期方法的呼叫。
在這個使用FragmentContainerView+BottomNavigationView的專案中,我在其中一個頁面加入了自定義的自動輪播圖控制元件,而當我透過底部導航欄切換到另一介面再切回來時,我發現我的輪播圖控制元件總是會自動從第一張圖開始載入,而在我之前使用ViewPager2+BottomNaviagtionView的專案中我也使用了自動輪播圖控制元件,但是並沒有出現這個問題:即透過底部導航欄切換到另一個頁面再切回來並不會使輪播圖控制元件重新輪播。
我開始思考:是什麼導致了自動輪播圖控制元件每次切回來都會從頭開始輪播?
以下是我的排查過程:
一開始,我懷疑我的輪播圖自動輪播的邏輯寫的有問題,返回檢視,發現與另一個專案的是一樣的,那就可以排除這個原因
進而,我開始思考輪播圖的載入時機。我的輪播圖資料是網路資料,而請求輪播圖網路資料是在Fragment的onViewCreated()生命週期方法中執行的。也就是說與Fragment的生命週期方法呼叫時機有關。
1.FragmentContainerView+BottomNavigationView切換Fragment時的生命週期呼叫情況:
我開始在Fragment的各個生命週期方法中打Log,然後發現,當我透過底部導航欄切換FragmentContainerView中的fragment時,被切換的Fragment除了首頁的Fragment只會立即執行onPause()->onStop()->onDestroyView(),其他的都會立即執行onPause()->onStop()->onDestroyView(onDestory(),當我再切換回來之後,會執行onCreatedView()->onViewCreated()。也就是說每當我切換回去之後輪播圖都會再重新請求一次資料,從而導致了每次切換回輪播圖介面輪播圖都會重新開始輪播。
首頁Fragment被切換後的生命週期呼叫情況
重新切回首頁Fragment後的生命週期呼叫情況
其他Fragment被切換後的生命週期呼叫情況
重新切回其他Fragment後的生命週期呼叫情況
2.ViewPager2+BottomNavigationView切換Fragment的生命週期呼叫情況:
而後,我又返回去檢視另一個專案(即使用ViewPager2+BottomNavigationView的)的介面切換導致的Fragment生命週期方法的變化。發現:當透過BottomNavigationView切換fragment時,被切換的fragment只會執行onPause(),但並不會立即執行onStop(),而是等待一段時間後或當使用者退出應用才會呼叫onStop()方法;且當切回去時,也不會重新執行onCreatedView()->onViewCreated(),onCreatedView()->onViewCreated()只有當Fragment首次被展示時才會呼叫。故使用ViewPager2的專案中切換Fragment不會導致輪播圖資料重新載入而導致輪播圖重新輪播。
Fragment被切換後生命週期呼叫情況
Fragment被切回來後生命週期呼叫情況
3.ViewPager+BottomNaviagtionView切換Fragment的生命週期呼叫情況:
而後,我又研究了一下ViewPager+BottomNaviagtionView切換fragment的生命週期呼叫情況。ViewPager有預載入功能,比較複雜一點
同樣是只有三個頁面(Title,Leaderboard,Register),由BottomNavigationView控制切換:
初次載入時,ViewPager會預載入其臨近的一個Fragment,在這個例子中,就是顯示首頁Title頁面時也預載入了Leaderboard頁面,生命週期呼叫如下:
當我從Title切換到臨近的Leaderboard頁面時,被切換的Title頁面的生命週期沒有任何變化,切換後的Leaderboard頁面也沒有任何變化(因為其已經被預載入過了),而此時,與Leaderboard臨近的Register頁面也會被預載入,生命週期呼叫如下:
而當我從Leaderboard頁面切換到第三個頁面Register時,Leaderboard頁面和Register頁面的生命週期都不會發生變化,但第一個頁面Title會被銷燬,生命週期呼叫如下:
同樣的,當我從第三個頁面Register切換到第二個頁面Leaderboard時,第一個頁面Title又會被重新載入,生命週期呼叫如下:
當我再從第二個頁面Leaderboard切換到第一個介面時,第三個頁面Register又會被銷燬,生命週期方法呼叫如下:
由上述測試我們可以看到ViewPager的預載入機制和頁面銷燬機制,且當介面上只有三個頁面時,除非使用者退出介面,否則第二個頁面是永遠不會銷燬的。
4.總結:
(1)FragmentContainerView+BottomNavigationView:
既沒有預載入機制也沒有快取機制,每切換一次,被切換的Fragment就會被銷燬,下次切回來要重新建立View:onCreatedView()->onViewCreated()
(2)ViewPager2+BottomNavigationView:
預設是沒有預載入機制的,除非我們手動呼叫viewPager.offscreenPageLimit 屬性進行設定,否則預設是沒有預載入(其預載入與ViewPager的預載入機制還不太一樣,預載入的介面不會呼叫onResume()渲染介面,而ViewPager的預載入介面是直接渲染好的),但是預設有快取機制,當頁面被載入到介面上後不會銷燬(除非使用者退出介面),只會暫停onPause,再次回來時直接onResume()
(3)ViewPager+BottomNavigationView:
預設有預載入機制,會預載入臨近的左右頁面。也有預設的快取機制,但與ViewPager2的不同,當從本頁面切換到間隔一個的頁面,本頁面就會被銷燬(如從1切換到3時,1就會被銷燬)。