切换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