Bootstrap

ViewPager中Fragment的生命周期

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

;