ViewPager下Fragment的生命周期

为什么写这个

在网上也有很多这个例子,但是感觉讲的都不很清楚,于是想自己跑一遍来看看整个过程,话不多说,下面就直接开始,我们知道ViewPager对fragment的管理其实是通过Adapter来管理的,下面我就分情况一一介绍,首先是Adapter继承FragmentPagerAdapter时的情况。

先吐槽一下

我之前其实已经快写好了博客,但是,后面突然发现我设置Log的位置不对,设置在了super之前,这个时候方法还没执行完,只是开始执行,这时就会产生一些偏差,就是一点不同,就是在启动程序的时候的setUserVisibleHint()方法的执行时间上不同,如下:

Log语句放在super之前

showFirstFragment&&limite1-2016116

Log语句放在super之后

showFirstFragment&&limite1-2016118

别的时候都是一样的。

以下的测试都是将Log语句放在super后面的时候的生命周期

当Adapter继承FragmentPagerAdapter

先上Adapter的代码

  public class MyAdapter extends FragmentPagerAdapter{
    public MyAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Fragment getItem(int position) {
        return list.get(position);
    }
}  

在我们使用的时候Adapter一般都是这么写的,测试的时候,我在viewpager中加了四个fragment,主要代码如下

    myAdapter=new MyAdapter(getSupportFragmentManager());
    viewPager.setOffscreenPageLimit(1);
    viewPager.setAdapter(myAdapter);
    viewPager.setCurrentItem(0);
    fragmentManager=getSupportFragmentManager();  

我们直接看结果:
当程序启动的时候,生命周期如下:

showFirstFragment&&limite1-2016118

当从第一个fragment滑到第二个fragment的时候

first- two-20161110

当从第二个滑到第三个的时候

two-three-2016116

当从第三个滑到第四个的时候

three-four-2016116

当从第四个滑到第三个的时候

four-three-2016116

当从第三个滑到第二个的时候

three-two-2016116

当从第二个滑到第一个的时候

two-first-2016116

由上图,我们可以得出,当程序执行的时候,viewpager加载fragment的顺序是,先onAttach当前的fragment,再向左遍历左边的fragment,遍历右完后,再向遍历,但这只是onAttach和onCreate的执行顺序,然后当OnCreateView和onActivityCreated的时候则是先遍历加载左边的,再遍历加载右边的,最后才是当前页。然后第一次的onStart、onResume的时候则是又按照onAttach和onCreate的顺序执行。为什么要说第一次的onStart、onResume,因为当屏幕关闭的时候,执行顺序还是按照OnCreateView和onActivityCreated的顺序,这里选的时间是从第一个fragment滑到第二个fragment之后,关闭屏幕,生命周期如下:

first-two&&onPause-2016116

看到这里,估计很多人就很乱了,就想问fragment的加载顺序到底是什么呢?这时,我通过如下代码得到fragment的list表,代码如下:

 getfragmentList=getSupportFragmentManager().getFragments();
    Log.e("log", "getfragmentlist"  + getfragmentList);
    if(getfragmentList!=null) {
        for (int i = 0; i <getfragmentList.size();i++)
            Log.e("log", "getfragmentlist" +i  + getfragmentList.get(i));
    }

这时,运行程序,加载完所有的fragment后的list顺序,如下:

first showfragmentlist-20161110

由于屏幕原因,list中的后面看不到,但是通过for循环中的输出,还是可以看出fragment加载顺序其实是
fragment2 fragment1 fragment3 fragment4,也就是onCreateView、onActivityCreated时的顺序,也就是说在onCreateView的时候建立了fragment的顺序,除了第一次,后面的生命周期都是按照这个顺序走的(除了滑动的时候),就像上面的关闭屏幕时候的生命周期一样,细心的朋友可能注意到了图片后面的1 0 2 3,那这个是什么呢?这是各个fragment对应的下标,这个是你在list中添加fragment的顺序,如代码:

public void addFragment(){
    list.add(myfragment1);
    list.add(myfragment2);
    list.add(myfragment3);
    list.add(myfragment4);
}

也就是fragemnt2对应1,fragemnt1对应0,fragemnt3对应2,fragemnt4对应3,这个顺序控制着fragment在页面中的顺序,也就是fragment1,fragment2,fragment3,fragment4。这里验证一下,修改代码,如下:

public void addFragment(){
    list.add(myfragment2);
    list.add(myfragment1);
    list.add(myfragment3);
    list.add(myfragment4);
}

运行程序,加载完所有的fragment后的list的顺序,如下:

showFragmentList&&first&&curr0,limit1, not order-20161110

这里就是fragment1对应1,fragment2对应0,fragment3对应2,fragment4对应3,页面中的fragment顺序是fragment2,fragment1,fragment3,fragment4。

还是拿之前的在list中添加fragment的顺序来进行下面的测试,也就是fragemnt2对应1,fragemnt1对应0,fragemnt3对应2,fragemnt4对应3,即fragment在页面中的顺序,也就是fragment1,fragment2,fragment3,fragment4来继续说,因为我们设置了** viewPager.setCurrentItem(0);,所以我们先onAttach ,onCreate fragemnt1,又设置了 viewPager.setOffscreenPageLimit(1);,所以再
onAttach ,onCreate fragemnt2,那么为什么先onCreateView onActivityCreated fragment2呢?这是因为viewpager的加载机制,会自动的将左右两边加载出来,所以,一下子就会加载两个,为了确保所有的view都创建,所以,将最开始要显示的view放在最后,即把fragment2的onCreateView,onActivityCreated放在fragment1的后面,那么,为什么fragment1的onStart,onResume先执行呢?这是为了用户体验,即将当前需要显示的先显示,因为这个时候是fragment1先显示。这里有一个奇怪的地方,就是
第二个滑到第三个和第三个滑到第二个的图片**,第二个滑到第三个的时候,fragment1是先执行onPause,onStop,onDestroy方法,再执行fragment4的onCreateView,onActivityCreated,onStart,onResume方法,而第三个滑到第二个的时候,则是先执行fragment1的onCreateView,onActivityCreated再执行fragemnt4的onPause,onStop,onDestroy方法,最后再执行onStart,onResume方法。为什么呢?这里先放一下,先验证上面的猜想,将代码修改一下

 myAdapter=new MyAdapter(getSupportFragmentManager());
    viewPager.setOffscreenPageLimit(2);
    viewPager.setAdapter(myAdapter);
    viewPager.setCurrentItem(1);
    fragmentManager=getSupportFragmentManager();

结果如下:

showFirstFragment&&curr1limit2-2016116

所以,这个顺序也验证了我们之前的想法,但是那个奇怪的地方还没解决,我们再修改代码:

  myAdapter=new MyAdapter(getSupportFragmentManager());
    viewPager.setOffscreenPageLimit(1);
    viewPager.setAdapter(myAdapter);
    viewPager.setCurrentItem(1);
    fragmentManager=getSupportFragmentManager();

程序运行结果如下:

firstShowFragmentcurr1limit1-2016116

因为我们显示的就是第二个页面,所以从第二个页面滑到第三个,效果如下:

two-three&&curr1limit1-2016116

从第三个滑到第二个,效果如下:

three-two&&curr1limit1-2016116

我们再加载完所有的fragment,再从第二个滑到第三个试试,就是不执行onAttach和onCreate,这样看的舒服一点,结果如下:
two-three&&curr1limit1loadallfragment-2016117

可以看到,执行顺序其实是类似的。也就是,当fragemnt3滑到fragemnt2的时候,fragemnt1的onCreateView和onActivityCreated会在fragemnt4的onPause,onStop,onDestroyView,先执行,然后再执行fragment1的onStart,onResume,为什么呢?我觉得,为了便以理解,可以将fragment的生命周期分成四部分,第一部分就是onAttach,onCreate。第二部分是onCreateView,onActivityCreated。第三部分是onStart,onResume。第四部分就是onPause,onStop,onDestroyView。因为,fragmentPagerAdapter只会销毁视图,所以后面的onDestroy,onDetach就不用管,当onAttach,onCreate之后,会先确定一个顺序,这里称为顺序2,也就是中左右,但是当onCreateView执行后,就确定了顺序3,这个顺序3就是getSupportFragmentManager().getFragments();得到的list的顺序,后面的生命周期(除了第一次启动以及左右滑动时的生命周期)都是按照这个顺序执行的,也就是左右中,为什么说除了第一次启动以及左右滑动时的生命周期,这里先说第一次启动,因为,在第一次启动后的onStart和onResume都是按照onAttach的顺序,而不是按照fragment的顺序,左右滑动的生命周期是按照你在viewPager中给添加fragment的顺序,这一点后面解释,确定了fragment顺序后,排在链表前面的fragment的方法肯定在排在后面的fragment方法之前,这一点很重要,举个例子,就比如上面的关闭屏幕那张生命周期图片,因为链表的顺序,所以生命周期会是这样。这里再解释上面的滑动顺序,这里贴上我添加fragment的代码

    list.add(myfragment1);
    list.add(myfragment2);
    list.add(myfragment3);
    list.add(myfragment4);

可以看出顺序是fragment1,2,3,4。这个顺序就是顺序1,顺序1是在滑动的情况会用到。为什么我会将之前的顺序命名为顺序2,因为这个顺序是最开始你在程序中通过add(fragment)确定的顺序,在滑动的时候,谁在前面谁先生成视图或者先销毁,也就是谁在前面谁先执行,这里需要明确一点就是,任何fragment的第一部分生命周期肯定运行在别的fragment的除第一部分生命周期的生命周期前面,这一点很重要,同时还有一点,当既有fragment视图生成又有fragment视图销毁的时候,其实就是滑动的时候,对于生成视图的那个fragment的第一部分生命周期和第二部分的生命周期不会同时进行,这里解释后面一点,修改代码:

myAdapter=new MyAdapter(getSupportFragmentManager());
    viewPager.setOffscreenPageLimit(1);
    viewPager.setAdapter(myAdapter);
    viewPager.setCurrentItem(3);

当第一次从3滑到2的时候生命周期如图:

thrid-second&& curr3,limit1-20161110

因为顺序1是fragment1,2,3,4。所以应该是fragment1先执行第一部分,再执行第二部分生成视图,再fragment4执行销毁视图,再fragment1执行第三部分。但是结果却是这个,说明当既有fragment视图生成又有fragment视图销毁的时候,其实就是滑动的时候,对于生成视图的那个fragment的第一部分生命周期和第二部分的生命周期不会同时进行。

看第二个滑到第三个,第三个滑到第二个这个情况,先看第一次第二个滑到第三个的情况,因为,任何fragment的第一部分生命周期肯定运行在别的fragment的除第一部分生命周期的生命周期前面,同时哦,对于生成视图的那个fragment的第一部分生命周期和第二部分的生命周期不会同时进行,又因为在顺序1中,fragment1在fragment4的前面,所以,fragment1先销毁,fragment4再生成和显示,所以,先执行fragment1的onAttach,onCreate,再执行fragment1的onPause,onStop,onDestroyView,再执行fragment4的onCreateView,onActivityCreated。而在第三个滑到第二个时,因为,不需要onAttach和onCreate,又因为在顺序1中,fragment1在fragment4的前面,所以,会先执行fragment1的onCreateView,onActivityCreated,再执行fragment4的onPause,onStop,onDestroyView。然后到显示的时候,再执行fragment1的onStart,onResume。为什么不会直接运行fragment1的onCreateView,onActivityCreated,onStart,onResume呢?因为viewPager.setOffscreenPageLimit(1);方法的限制,所以只有先将销毁视图的fragment的视图销毁,才能显示,所以接下来执行fragment4的onPause,onStop,onDestroyView,最后执行fragment1的onStart,onResume。然后,再一次从第二个滑到第三个的情况,因为,不需要onAttach和onCreate,又因为在顺序1中,fragment1在fragment4的前面,所以,fragment1先销毁,fragment4再生成和显示,所以,先执行fragment1的onPause,onStop,onDestroyView,再执行fragment4的onCreateView,onActivityCreated,onStart,onResume。这里可能有人会说,为什么不是按照顺序2来判断,因为在顺序2中,fragment1也是在fragment4之前呀,这里还是原来的代码:

  myAdapter=new MyAdapter(getSupportFragmentManager());
    viewPager.setOffscreenPageLimit(1);
    viewPager.setAdapter(myAdapter);
    viewPager.setCurrentItem(3);

这时,顺序1是fragment1,2,3,4.而顺序2变成fragment4,3,2,1。这两个顺序中fragment1和fragment4顺序不一样,看从2滑到3的生命周期,如图:


second-thrid&&first,curr3,limit1-2016118
second-thrid&&first,curr3,limit1-2016118

因为fragment1先销毁fragment4再生成显示,所以是按照顺序1,不是顺序2。为了验证我的猜想,然后我去翻源代码,找了好久,但是没有找到,原谅我是个彩笔,如果有大神找到,请告诉我。不胜感激。 同时,这里还有一点,那就是setUserVisibleHint()方法执行的时间,这个方法不是属于生命周期的范围,我觉得这个方法调用的时间是如果是都要生成视图,也就是第一次显示的时候,那么就和onAttach的顺序是一样的,如果显示后移动的情况,那么看移动到当前位置时是否需要生成新的fragment视图,如果需要先生成,即先执行生成页的setUserVisibleHint()方法,然后再执行先前页的这个方法,在执行当前页的这个方法就像当从第二页滑到第三页的时候setUserVisibleHint()方法执行的顺序一样,即先执行fragment4的setUserVisibleHint()方法,再执行fragment2的setUserVisibleHint()方法,最后再是fragment3的setUserVisibleHint()方法。
总的来说,为了便以理解,可以将fragment的生命周期分成四部分,第一部分就是onAttach,onCreate。第二部分是onCreateView,onActivityCreated。第三部分是onStart,onResume。第四部分就是onPause,onStop,onDestroyView。因为,fragmentPagerAdapter只会销毁视图,所以后面的onDestroy,onDetach就不用管,,**顺序1是由viewpager中的添加fragment时设置的,顺序2是根据顺序1和viewPager.setCurrentItem();,以及viewPager.setOffscreenPageLimit(1);共同决定的,顺序2就是在顺序1中先找出viewPager.setCurrentItem()的fragment,然后向左遍历fragment,然后向右遍历fragment。这就是onAttach,onCreate的顺序,举个例子,顺序1是1,2,3,4。viewPager.setCurrentItem(0),viewPager.setOffscreenPageLimit(1);,那么顺序2就是fragment2,1,3,4。如果viewPager.setCurrentItem(2),viewPager.setOffscreenPageLimit(1);不变,顺序2就是2,3,4,1。如果是viewPager.setCurrentItem(2),viewPager.setOffscreenPageLimit(2);,顺序2就是fragment2,1,4,3。顺序3也是顺序1和viewPager.setCurrentItem();,以及viewPager.setOffscreenPageLimit(1);共同决定的,只是先遍历viewPager.setCurrentItem();,再遍历viewPager.setCurrentItem();右边的,最后再 遍历viewPager.setCurrentItem();,顺序3使用最广泛,除了顺序1和顺序2,别的都是顺序3。
更简单的说,为了便以理解,可以将fragment的生命周期分成四部分,第一部分就是onAttach,onCreate。第二部分是onCreateView,onActivityCreated。第三部分是onStart,onResume。第四部分就是onPause,onStop,onDestroyView。因为,fragmentPagerAdapter只会销毁视图,所以后面的onDestroy,onDetach就不用管记住几个规则:

1.有三个顺序,分别是顺序1,顺序2,顺序3。顺序1就是添加fragment的顺序,顺序2就是中左右,顺序3就是左右中。

2.第一次启动的时候,生命周期的第一,三部分按照顺序2,第二部分按照顺序3

3.滑动的时候按照顺序1,并且谁在前面谁先生成视图或者先销毁,也就是谁在前面谁先执行,但是,生命周期的第一部分和第二部分不会在一起执行。也就是说当生成视图的fragment在销毁视图的fragment之前,但是同时,生成视图的fragment的第一部分要执行,这时,执行顺序就是,生成视图的fragment的第一部分,销毁视图的fragment第四部分,生成视图的fragment的第二部分,第三部分。如果,这时,生成视图的fragment的第一部分不用执行,那么就会先执行生成视图的fragment的第二部分,当要执行第三部分的之后,由于viewPager.setOffscreenPageLimit(1);方法的限制,所以只有先将销毁视图的fragment的视图销毁,才能显示,所以第三部分没有直接跟着第二部分执行,而是最后执行。

4.剩下的时候都是按照顺序3,比如关闭屏幕,重新显示之类的。

5.因为setUserVisibleHint()这个方法,不算生命周期,所以这个执行的时间就是谁先显示谁先调用,并且,当isVisibleToUser为false的时候执行在第一部分之前,isVisibleToUser为true的时候执行在第一部分之后

说的很啰嗦,其实看图片的结果就知道了,为了验证上面的结果,我去翻源码,找了好久,但是没找到,原谅我水平不够,如果有大神找到的话,请告诉我,不胜感激。**

当Adapter继承FragmentStatePagerAdapter

先上Adapter代码

 public class MyAdapter extends FragmentStatePagerAdapter{
    public MyAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Fragment getItem(int position) {
        return list.get(position);
    }
}

程序主要代码如下:

myAdapter=new MyAdapter(getSupportFragmentManager());
    viewPager.setOffscreenPageLimit(1);
    viewPager.setAdapter(myAdapter);
    viewPager.setCurrentItem(0);
    fragmentManager=getSupportFragmentManager();  

程序运行结果:

showSecondFragment&& curr0 limit1-2016118

第一个滑到第二个

first-second&&second,curr0,limit1-2016117

第二个滑到第三个

second-thrid&& second,curr0,limit1-20161110

第三个滑到第四个

thrid-fouth&&second,curr0,limit1-2016117

第四个滑到第三个

fouth-thrid&&second,curr0,limit1-2016117

第三个滑到第二个

thrid-second&&second,curr0,limit1-2016117

第二个滑到第一个

second-first&&second,curr0,limit1-2016117

第一个滑到第二个后按Pause

first-second&&second,curr0,limit1,pause-2016117

第一个滑到第二个后的fragemntList

first-secondShowFragmentList&&second,curr0,limit1,-2016117

第二个滑到第三个后的fragemntList

two-threeShowFragmentList&& second,curr0,limit1-20161110

第三个滑到第二个后的fragmentList

thrid-secondShowFragmentList&& Second,curr0,limit1-20161110

从上图可以看出,跟FragmentPagerAdapter相比,FragmentStatePagerAdapter每次销毁的时候是直接remove,所以会在销毁之后重新显示的话,就会执行fragment的所有周期函数来显示。

FragmentPagerAdapter的destroyItem()方法

 @Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    mCurTransaction.detach((Fragment)object);
}  

FragmentStatePagerAdapter的destroyItem()方法

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    while (mSavedState.size() <= position) {
        mSavedState.add(null);
    }
    mSavedState.set(position, fragment.isAdded()
            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
    mFragments.set(position, null);

    mCurTransaction.remove(fragment);
}

注意最后两张图片中的list,分别有一个是空的,这也就说明,这里的fragment为空,但是依然占着位置,下次生成这个的时候,直接插入到这个位置,所以顺序2还是不会改变。同时,也没有后面的1 0 2 3,同时,上面划分的Fragment生命周期四部分也不一样,最后一部分应该再加上onDestroy,onDetach。生命周期的差异就体现在当有销毁又有生成fragment的视图的时候,这个时候跟FragmentPagerAdapter不一样,都是生成视图的fragment的第一部分先执行,再销毁视图的fragment去销毁视图,再生成视图的fragment显示。其实这就是上面的规则3。

当使用懒加载的时候呢?

很明显生命周期会是一样的,因为设置懒加载的操作,并没有影响生命周期,所以生命周期是一样的,只是懒加载保证了显示哪页加载哪页的数据,有兴趣的朋友可以自己测试一下,毕竟纸上得来终觉浅,绝知此事要躬行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容