切換ViewPager時Fragment的生命週期變化
這裏有一個MainActivity,MainActivity中有一個ViewPager,使用的適配器是FragmentPagerAdapter,ViewPager中有三個Fragment:FirstFragment,SecondFragment,ThirdFragment。
剛進入MainActivity,Log是這樣的:
可以看到,MainActivity按照常規順序調用後,FirstFragment和SecondFragment先後調用了onAttach()和onCreate()實例化了Fragment,並且在FirstFragment調用onResume()之後,SecondFragment也調用了onResume(),也就是是說SecondFragment已經加載好了,隨時準備變爲可見,接下來滑動到SecondFragment,是這樣的:
然後再滑到第三個ThirdFragment,可以看到:
爲什麼會這樣呢?
原來ViewPager默認會加載當前頁面和當前頁面左右的兩個頁面。ViewPager中有一個成員變量紀錄着加載的左右頁面的數量private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES
,默認值就是private static final int DEFAULT_OFFSCREEN_PAGES = 1
。
我們可以調用如下方法調整默認加載的數量:
/**
* Set the number of pages that should be retained to either side of the
* current page in the view hierarchy in an idle state. Pages beyond this
* limit will be recreated from the adapter when needed.
*
* <p>This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.</p>
*
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* @param limit How many pages will be kept offscreen in an idle state.
*/
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
我們可不可以只加載當前的頁面,把mOffscreenPageLimit設爲0呢?
顯然不能,我們發現源碼中有這麼一段:
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
所以最少加載當前頁面的左右各一頁,把mOffscreenPageLimit 設爲0,ViewPager也會替你修改成1。
當然超出mOffscreenPageLimit 的範圍,Fragment的視圖就會被銷燬。所以就有了上面打印的日誌。
實現懶加載
既然ViewPager默認給我們加載當前頁面和當前頁面左右的兩個頁面,那該如何實現只加載當前頁面呢?
第一個方法,自己複製一份ViewPager的代碼,改名爲LazyViewPager,然後修改代碼,再使用LazyViewPager替代ViewPager。
第二個方法,直接修改ViewPager源碼,自己打個v4包。
當然以上兩種方法不推薦。
數據的懶加載
其實一般爲了避免不必要的流量浪費,像微信一樣我們只要實現數據的懶加載即可。
利用setUserVisibleHint(),如下:
public class BaseFragment extends Fragment {
protected boolean isViewCreated = false;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
lazyLoadData();
}
}
/**
* 懶加載方法
*/
protected void lazyLoadData() {
}
}
爲什麼要判斷getUserVisibleHint()的值,因爲查看FragmentPagerAdapter的源碼會發現setUserVisibleHint()第一次調用是在onAttach()之前,這樣顯然不可以。我們只有等Fragment的視圖加載好,才能加載數據。
或者利用Fragment的onHiddenChanged()通過類似的思路也能實現數據的懶加載。
也可以利用ViewPager中OnPageChangeListener接口的onPageSelected(),或者TabLayout中TabLayoutOnPageChangeListener接口的onPageSelected()。
FragmentPagerAdapter和FragmentStatePagerAdapter的區別
之前的案例都是使用的FragmentPagerAdapter,這裏講講FragmentPagerAdapter和FragmentStatePagerAdapter的區別。
當ViewPager切換的時候,FragmentPagerAdapter會銷燬Fragment的視圖並且回調onDestroyView(),但是會保存內存中Fragment的實例不會執行onDestroy():
I/fragment: onPause()
I/fragment: onStop()
I/fragment: onDestroyView()
當ViewPager切換的時候,FragmentStatePagerAdapter不僅會銷燬Fragment的視圖,而且會刪除內存中Fragment的實例並且和Activity解綁:
I/fragment: onPause()
I/fragment: onStop()
I/fragment: onDestroyView()
I/fragment: onDestroy()
I/fragment: onDetach()
還有一點就是FragmentStatePagerAdapter會將onSaveInstanceState(Bundle outState)中的Bundle信息保存下來。
也就是說,你可以在onSaveInstanceState(Bundle outState)中保存一些數據,在onCreate()中進行恢復。
FragmentStatePagerAdapter相對於FragmentPagerAdapter節約內存,FragmentStatePagerAdapter類上有如下聲明:
* <p>This version of the pager is more useful when there are a large number
* of pages, working more like a list view. When pages are not visible to
* the user, their entire fragment may be destroyed, only keeping the saved
* state of that fragment. This allows the pager to hold on to much less
* memory associated with each visited page as compared to
* {@link FragmentPagerAdapter} at the cost of potentially more overhead when
* switching between pages.
參考:
1.Android Fragment 你應該知道的一切
2.源碼探索系列43—關於FragmentStatePagerAdapter和FragmentPagerAdapter
3.使用FragmentPagerAdapter和FragmentStatePagerAdapter時Fragment生命週期區別
4.ViewPager中Fragment的生命週期
5.ViewPager+Fragment生命週期方法(一)
6.死磕 Fragment 的生命週期
7.實現類似微信Viewpager-Fragment的惰性加載,lazy-loading