Android Fragment在ViewPager中到底经历了什么?

2017年05月30

最后的懒加载写的不好,推荐请叫我大苏同学写的Fragment懒加载博客,
【Android】再来一篇Fragment的懒加载(只加载一次哦)

在大苏同学的博客评论里,看到了另一个实现思路,有提到可以使用okHttp的缓存机制,有空也再学一下

1. 一直以来的疑问<p>

Fragment在ViewPager到底经历了哪些生命周期方法?到底发生了什么?

常会TabLayout和ViewPager配合起来使用,针对这套组合,就想也做一些学习了解。在一个ViewPager中经常会存在多个Fragment,Fragemnt在ViewPager中的生命周期一直没有闹明白。这周正好在做一个测试的时候又用到了TabLayout和ViewPager组合。ViewPager中的Fragment并想做到延迟加载,在Fragment可见的时候再进行网络请求。在敲代码的时候想到到几个问题:

1. 在ViewPager中,滑动时,Fragment会经历哪些生命周期?
2. ViewPager的setOffscreenPageLimit()方法对Fragment有哪些影响?
3. Fragment的setUserVisibleHint()对Fragment的生命周期有哪些影响?
4. 点击TabLayout的Tab时,Fragment经历的生命周期和滑动ViewPager有啥不一样?

现在有个明确的需求:在TabLayout和ViewPager这个组合下,实现Fragment的延迟加载。


2. Tablayout、ViewPager组合 <p>

代码很简单,就是新建一个Android Stuido工程,一个Activity里面有一个TabLayout和ViewPager,ViewPager中四个Fragment。

效果

2.1 布局文件 <p>

Activity的布局文件:

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.fragmentlifeinviewpager.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:title="英勇青铜5"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/AppTheme.PopupOverlay" />
            <android.support.design.widget.TabLayout
                android:id="@+id/tab_main_activity"
                android:layout_width="match_parent"
                android:layout_height="?actionBarSize"
                app:tabMode="fixed"
                app:tabSelectedTextColor="@color/colorAccent"
                app:tabTextColor="@android:color/white"
                />
    </android.support.design.widget.AppBarLayout>

   <android.support.v4.view.ViewPager
       app:layout_behavior="@string/appbar_scrolling_view_behavior"
       android:id="@+id/vp_main_activity"
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>

</android.support.design.widget.CoordinatorLayout>

主要是CoordinatorLayout、AppBarLayout和Toolabr的使用。如果基础的用法不知道的话可以看看CoordinatorLayout、Tablayout、Toolbar简单组合使用,我写的很基础的用法。啊哈哈 :)


Fragment的布局:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textColor="@color/colorPrimary"
            android:textSize="150sp"
            android:text="1"
            android:gravity="center"
            />
    </android.support.v4.widget.NestedScrollView>
</FrameLayout>

Fragmentde的布局更加简单,主要就一个TextView。4个Fragment布局一样,就贴出一个。


2.2 Actiity代码 <p>

public class MainActivity extends AppCompatActivity {
    private TabLayout tabLayout;
    private ViewPager viewPager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        initView();
    }

    private void initView() {
        tabLayout = (TabLayout) findViewById(R.id.tab_main_activity);
        viewPager = (ViewPager) findViewById(R.id.vp_main_activity);

        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);

        List <Fragment> data = new ArrayList<>();
        data.add(new Fragment_1());
        data.add(new Fragment_2());
        data.add(new Fragment_3());
        data.add(new Fragment_4());

        adapter.setFragmentList(data);
        tabLayout.setupWithViewPager(viewPager);
        for (int i = 0 ; i < adapter.getCount() ; i ++){
             tabLayout.getTabAt(i).setText("Tab_"+(i+1));
        }
    }
}


主要就是initView()这个方法。主要就是把4个Fragment加入到ViewPager中。


2.3 Fragment代码 <p>

Fragemnt的生命周期方法共有11个,为了下面能够更加清晰记住这些生命周期,我给这些生命周期方法从1到11定了编号。如下:

1_onAttach()           -->  2_onCreate()      --> 3_onCreateView()    --> 4_onCreateActivity() 
--> 5_onStart()        -->  6_ onResume()     --> 7_onPause()         --> 8_onStop() 
--> 9_onDestroyView()  -->  10_onDestroy()    --> 11_onDetach()

Fragment原本打算偷懒,只想写一套,不想写4个。但写一个的话onAttach()不好做区分展示,就又老老实实写了四个。

public class Fragment_1 extends Fragment {

    private final String TAG = "Fragment_1";


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        log("   1-->onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        log("    2-->onCreate");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        log("    3-->onCreateView");
        return inflater.inflate(R.layout.fragment_layout_1,container,false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        log("   4-->onActivityCreated");
    }

    @Override
    public void onStart() {
        super.onStart();
        log("   5-->onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        log("   6-->onResume");
    }

    @Override
    public void onPause() {
        super.onPause();
        log("   7-->onPause");
    }

    @Override
    public void onStop() {
        super.onStop();
        log("   8-->onStop");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        log("   9-->onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        log("   10-->onDestroy");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        log("   11-->onDetach");
    }

    private void log (String methodName){
        Log.e(TAG,"-------->"+methodName);
    }
}

代码全部贴完。都很简单。前面一个说了4个问题,到了思考第2,3个问题时,代码需要小改动。


3. 开始分析问题 1 <p>

  • 在ViewPager中,滑动时,Fragment会经历哪些生命周期?

开始前,这里没有图,就简单交代一下,每个Fragment有一个对应的数字。TAB-1 对应Fragment-1,以此类推,4个TAB对应于4个Fragment。默认显示Fragment1。


3.1 滑动ViewPager,切换Fragment <p>

这部分所有的切换Fragment都是通过滑动ViewPager。不是点击Tab。在Activity中,ViewPager此时啥都没有设置。


3.1.1 加载Fragment_1 <p>

当Activity中ViewPager加载完成时,手机屏幕显示默认的Fragment1。此时,打印的Log信息:


默认Fragment_1加载完成时

此时打印的Log信息与预想的有很大不同。我以为Fragment_1会从 1 onAttach() 开始直到 6 onResume()后,Fragment_2才会开始从 1onAttach()开始,到了3 onCreateView()会停止。然而,并不是预想的这样。

首先,Fragment_1 经历 1_onAttach() 和 2_onCreate() 后, Fragment_2也开始走了 1_onAttach()和 2_onCreate()方法。

接着,Fragment_1依次经历 3_onCreateView(), 4_onCreateActivity(),5 _onStart ,6_onResume()。此时,Fragment_1获得焦点,已经展示在手机屏幕。Fragment_2也接着从 3_onCreate()开始直到也执行到 6_onResume()方法。疑问就在这,我个人感觉既然Fragment_2没有在屏幕显示,就不会执行到 6_onResume()方法,然而Log信息却显示执行到了 6_onResume()。这里记录一下。


3.1.2 Fragment_1向右滑动到Fragment_2 <p>

滑动一下屏幕,ViewPager中由Fragment_1切换到Fragment_2,屏幕显示Fragment_2时,下面是Log信息:

由Fragment_1滑到Fragment_2后

哎,我擦,咋就只有Fragment_3的信息,Fragment_1和Fragment_2的呢?这Fragment_2没有Log信息可以理解,可Fragment_1应该会有啊。这里又和预想的大不一样。预想中Fragment_1会走 7__onPause(),8__onStop(),9__onDestroyView()和10__onDestroy()。而实际却没有走,开始还以为搞错了,又反复测试,发现就是只有Fragment_3的Log信息。

根据Log信息,可以看出,当滑到Fragment_2后,Fragment_3经历了从 1__onCreate()方法到6__onResume()方法。Fragment_1却是没有走任何生命周期方法。这里考虑了一下,感觉也蛮合理。ViewPager展示的时候,用户在实际使用的时候,经常会从一个Fragment滑到另一个Fragment后又切换回来。这时如果按照我预想的,Fragment_1中的View已经被销毁,再次切换回来又需要重新绘制,这样频繁请求内存空间也不好。

这时,Fragment_3已经时刻准备好,如果在onCreateView()方法中有网络请求的话,网络请求在滑动到Fragment_2后就会被调用。就等着滑动ViewPager后,来展示内容。

这里先预留问题:为什么滑到Fragment_2后,Fragment_1没有走任何生命周期方法?Fragment_1的生命周期方法会在啥时候走?

此时,可向左滑可向右滑

当Fragment_2在屏幕时,这时候就有了两个方向可以选择,左滑到Fragment_1 或者 右滑到 Fragment_3。同理,当屏幕显示Fragment_3的时候,也是有两个方向可选择。


3.1.3 由Fragment_2向右滑到Fragment_3 <p>

前面已经提到,当由Fragment_1滑到Frament_2后,Fragment_3的生命周期方法已经从 1_onCreate()走到了 6_onResume()。当滑到Fragment_3后,看下此时的Log信息。

由Fragment2滑到到Fragment3后

这次,Fragment_4先走了1_onAttach(),2_onCreate()后,Fragment_1走 7_onPause,8_onStop,9_onDestoryView()。Fragment_1的生命周期终于开始走。这里也可以解答3.1.2最后预留的那个问题。

看到了这次的Log信息,终于感觉看出了点ViewPager中嵌套Fragment后一些特点。

当ViewPager中的Fragment大于等于3个的时候,除去展示开头和结尾两个Fragment的情况,ViewPager会保留一个Fragment左右两侧以及自身3个Fragment的信息。例如,当滑到Fragment_2的时候,Fragment_3也已经走到了6_onResume()这个生命周期方法,而此时,Fragemnt_1没有走任何的生命周期方法,还在ViewPager中保留着。此外还有,当加载本身或者预加载下一个Frgment时,只是先走1_onAttach()和2_onCreate()两个生命周期方法后,才会根据当前情况确定继续走相应的Fragment的生命周期方法。


3.1.4 由Fragment_2向左滑到Fragment_1 <p>

当由Frament_1滑到Fragment_2时,并没有走Fragment_1和Fragment_2的生命周期,而是走了Fragment从1_onCreate()到6_onResume()。接下来,屏幕向左滑,由Fragment_2滑到Fragment_1。
Log信息:


由Fragment_2滑到Fragment_1后

由Fragment_1滑到Fragment_2只是走了Fragment_3的生命周期方法,而由Fragment_2滑到Fragment_1时,也是只走了Fragment的生命周期方法。在3.1.2中,当滑到Fragment_2后,Fragment_3已经走到了6_onResume()方法。再滑到Fragment_1后,Fragment_3走了7_onPause(),8_onStop(),9_onDestoryView()。到了这里发现,在ViewPager中,相邻的3个Fragment之间来回切换,都没有走10_onDestroy()和11_onDetach()。


到了这里,四个Fragment滑动的情况再分析查看下面的3种情况。其实,下面的这些情况和前面的情况重复了,本质是一样的。

  1. 由Fragment_3向右滑到Fragment_4
  2. 由Fragment_3向左滑到Fragment_3
  3. 由Fragment_4向左滑到Fragment_3

3.1.5 由Fragment_3向右滑到Fragment_4 <p>

滑动前,当屏幕显示Fragment_3时,此时,由3.1.3知道,Fragment_1走到了9_onDestroyiew()。Fragment_4走到了6_onResume()。
滑到Fragment_4后:

由Fragment_3滑到Fragment_4后

Fragment_4是ViewPager中最后一个Fragment所以也就没了下一个Fragment预加载。只是不相邻的Fragment_2走了7_pause(),8_onStop,9_onDestroyView()方法。此时,就可以结合3.1.3情况来看。实际的情况是一样的,只是区别在于Fragment_4已经是最后一个Fragment了。


3.1.6 由Fragment_3向左滑到Fragment_2 <p>

这里结合3.1.4,很容易就理解了。
滑动前,当屏幕显示Fragment_3时,此时,由3.1.3知道,Fragment_1走到了9_onDestroyiew()。Fragment_4走到了6_onResume()。
滑动到Fragment_2后:


由Fragment3滑到Fragment2后

根据3.1.3中的Log信息,Frgment_1在由Fragment_2滑到Fragment_3的时候,生命周期已经走到了9_onDestroyView(),并没有走到10_onDestroy()。当由Fragment_3滑到Fragment_2后,ViewPager再次预加载Fragment_1时,是从3_onCreateView()开始的,走到4_onCreateAcitivty()后,开始走Fragment_4的生命周期方法。


3.1.7 由Fragment_4向左滑到Fragment_3 <p>

滑动前,当显示Fragment_4时,根据3.1.5的Log信息,Fragment_2走到了9_onDestroyView()生命周期方法。而Fragment_4此时处于6_onResume()这个生命周期方法。
滑动到Fragment_3后:

由Fragment4滑到Fragment3后

到了这里,就比较容易理解此时的Log信息。Fragment_2从3_onCreateView()走到了6_onResume()方法。到了此时,Fragment_2、Fragment_3和Fragment_4此时3个Fragment都处于6_onResume()这个生命周期方法。


到了这里,ViewPager中Fragment滑动的情况就差不多分析完了。其他都是些重复的情况了。根据3.1.1到3.1.7的Log来看,ViewPager中嵌套Fragment默认不设置其他方法时,若Fragment的数量大于等于3时,ViewPager会保留包括一个Fragment在内左右两侧3个Fragment的信息。然而这里也留下了个问题,Fragment的10_onDestroy和11_onDetach()什么时候会走?


3.1.8 点击回退键finish掉Acitivty时 <p>

根据3.1.7的信息,此时,Fragment_1处于9_onDestroyView()这个生命周期方法,Fragment_2、Fragment_3和Fragment_4此时3个Fragment都处于6_onResume()这个生命周期方法。
此时,点击back键结束当前的Acitivty,我这里整个测试的App就一个Activity,点击了back键就会退出应用。看下此时的Log信息:

点击Back键时

根据Log信息,Fragment_2,3,4先走了7_onPause()后,走了8_onStop()。接着,便是Fragment_1走10_onDestroy(),11_onDetach()。之后便是Fragment_2,3,4依次走9_onDestroyView(),10_onDestroy(),11_onDetach()。

到了这里,3.1.7的问题也就有了答案。ViewPager中,Fragment的10_onDestroy()以及11_onDetach()会在ViewPager所在的Activity结束后被调用。

这里还有一点需要说的是,点击back键后,图(点击Back键时)中的Log信息并不是一次打印出来的。一开始我以为是我是看错了,又测试了几次,发现,Fragment_2,3,4走了7_onPause()这个方法后,确实会短暂停一下,非常快的又打印出下面的Log信息。


3.1.9 当屏幕显示Fragment_3时,点击电源键关闭屏幕 <p>

当屏幕正在展示某个Fragment时,点击了电源键或者手机自动息屏时,这种情况下,会和以上的几种情况有所不同,下面还是以Fragment_3为例来尝试分析一下。

根据3.1.7的信息,此时,Fragment_1处于9_onDestroyView()这个生命周期方法,Fragment_2,3,4此时3个Fragment都处于6_onResume()这个生命周期方法。

点击电源键之后:

点击电源键后

Fragment_2,3,4依次走了7_onPause(),8_onStop()。接下来,再点亮屏幕,显示Fragment_3:

再次点亮屏幕后

Fragment_2,3,4依次走了5_onStart(),6_onResume()。


到了此时,ViewPager中Fragment滑动的情况就结束了。这些情况,写一个很简单的测试demo就可以搞的比较清楚了。要一次就记得清楚也不算特别现实,多想几次就可以了。


4. 尝试分析问题2<p>

  • ViewPager的setOffscreenPageLimit()方法对Fragment有哪些影响?
    直白翻译就是设置幕后页面限制 :) 。其实就是设置缓存在ViewPager中的Fragment的数量。
 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();
        }
    }

通过ViewPager中源码可以看出,传入的参数如果小于DEFAULT_OFFSCREEN_PAGES(就是1)这个值是无效的。所以尝试使用setOffscreenPageLimit(0)来关闭ViewPager的预加载是无效。也就是说,limit只有大于等于2时才会有效。Ps:ViewPager调用这个方法后,里面populate()这个方法又做了啥,我目前想关心也关心不了,大概看了下,ViewPager共有3000多行代码,我目前的代码阅读能力还很低,即使想做到只是阅读相关代码也困难,目前想读通比较困难,以后代码阅读能力提升再来看了。


接下来,在ViewPager中,加入viewPager.setOffscreenPageLimit(2)这行代码,再次运行,看看Log信息

setOffscreenLimit(2)后

根据问题1的分析,看到这个Log信息就很好理解了,多了Fragment_3的Log信息,而且走的方法和Frgment_1和2是一样的。后面的情况的Log信息便不再贴出来了,本质是一样的,只是多预加载了一个Fragment。但此时ViewPager依然只是保留3个Fragment的信息。当滑到Fragment_4的时候,Fragment_1走了7_onPause(),8_onStop(),9_onDestroyView()。Fragment_2,3,4则处于6_onResume()。

这个方法对Fragment生命周期方法的调用顺序上并没有什么影响,只是预加载的Fragment的数量受设置的limit参数影响。


5. 尝试分析问题3 <p>

  • 在ViewPager中,Fragment的setUserVisibleHint()对Fragment的生命周期有哪些影响?

再次顾名思义:设置用户可见暗示 。:) 这个方法可以用来判断Fragment是否可见。这里也不再贴源码了。


5.1 Fragment加入setUserVisibleHint() <p>

修改代码前,我把在分析问题2时加入的viewPager.setOffscreenPageLimit(2)注释掉。这样Log信息会少一些。在每个Fragment中代码做了一些修改加入了一个setUserVisibleHint()

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        
        log("setUserVisibleHint");
    }

再次运行Demo,查看Log信息:

加入setUserVisibleHint()方法后

和预想的大不一样。setUserVisibleHint()竟然最先被调用。预想的是1_onAttach()执行后,才会走setUserVisibleHint()这个方法。而且,Fragment_1走了两次setUserVisibleHint()这个方法。其他的Log信息倒是比较熟悉了。
滑动屏幕,滑动到Fragment_2,看下此时的Log信息:

加入setUserVisibleHint()后,滑到Fragment_2.png

这时不仅先执行了Fragment_3的setUserVisibleHint(),连Fragment_1,2的setUserVisibleHint()方法也比Fragment_3的1_onAttach()。此时,Fragment_1,2,3都处于6_onResume()。再由Fragment_2滑到Fragment_1,看下Log:

加入setUserVisibleHint()后,由Fragment_2滑到Fragment_1

此时,Fragment_1,2的setUserVisibleHint()方法优先被调用,Fragment_3走到9_onDestroyView()方法。

到了这里,感觉setUserVisibleHint()这个方法每次滑动都会被调用,而且最先被调用,缓存在ViewPager中的Fragment都会被调用。

接下来模拟一下延迟加载网络请求。


5.2 延迟加载,模拟网络请求 <p>

在每个Fragment中,加入一个Boolean值,并将代码做一些改动。

 @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        isCreate = true;
        log("   2__onCreate");
    }
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isCreate && isVisibleToUser){
            log("开始进行网络请求了");
            isCreate = false;
        }
    }

当Fragment可见时,进行网络请求。网络请求加上了前提条件。除了isVisibleToUser为true外还有一个isCreate。Fragment走了2_onCreate()iSCreate设为了true。我通常会用Fragment.newInstrance()这个方法进行初始化Fragment时来传值,在2_onCreate()方法中通过getArguments()来接收值,所以这里加了一个条件isCreate为true。


再次运行Demo,看Log信息:

加入模拟网络请求后,加载Fragment_1后

我擦,我的"开始进行网络请求了"呢?最关键的Log信息却没有看到。根据5.1,每次滑动时,肯定会调用setUserVisibleHint()这个方法的啊,可Fragment_1明明都已经可见了,怎么没有出现"开始进行网络请求了"?于是,看了下源码,...,白看,没瞧出啥。好吧,我再滑下屏幕,Fragment_2显示,看下Log:

加入模拟网络请求后,由Fragment_1滑到Frgment_2后

滑到Fragment_2后,第一行倒是打印出来了"开始进行网络请求了"。继续滑,Fragment_3,4也都打印出来了。然后我又依次从Fragment_4滑到了Fragment_1后,Log信息也打出了"E/Fragment_1: -------->开始进行网络请求了"

可为啥Fragment_1第一次显示时,没有打印呢?

这里结合5.1想了一下,setUserVisibleHint()这个方法在1_onAttach()方法之前。Fragment_1第一次加载时,2_onCreate()方法还没走呢,此时isCreate的值还是fasle,就不会打印"开始进行网络请求了"。根据5.1的Log信息,Fragment_1两次调用setUserVisibleHint()时,isCreate都为false。而isCreate值变为true后,却不在执行setUserVisibleHint()方法了。

暂时的解决思路在 6.2 给出


6. 尝试分析问题4<p>

  • 点击TabLayout的Tab时,Fragment经历的生命周期和滑动ViewPager有啥不一样?

问题1,2,3都是通过滑动来分析的,没有通过点击Tab。现在来看看点击Tab。


6.1点击Tab时,Fragment所走的生命周期 <p>

开始前,把每个Fragment中的setUserVisibleHint()先注释掉,之后运行Demo。这里点击Tab_2是体现不出啥效果的,因为Fragment_2已经预加载了。我这里点击Tab_3,看Log信息:

点击Tab_3后

到了这里,这个Log信息还是比较容易理解了。此时的Fragment_2,3,4处于6_onResume()这个方法。Fragment_1走到9_onDestroyView()。和滑动时其实没有本质的区别。就是点击可以从Fragment_1越过Fragment_2而直接到Fragment_3这种交互上的区别而造成的少了滑动时对Fragment_3的预加载。如果是从Fragment_1滑到Fragment_3,在滑到Fragment_2时,就已经完成了对Fragment_3的预加载。而通过点击的方式时,点击Tab_3后,Fragment_3是从1_onAttach()这个生命周期方法开始的。


6.2 点击Tab的方式和Fragment延迟加载遇到的问题 <p>

在5.2中遇到的一个问题就是,Fragment_1用了我写的那种延迟加载的方法时,第一次加载不会进行网络请求,只有再次滑到Fragment_1后才会进行网络请求。到了这里,同样也会引起这个问题。少了预加载,例如6.1中,直接点击Tab_3时,此时Fragment_3并没有预加载。setUesrVisibleHint()的方法又比1_onAttach()方法调用的早。会有和5.2中遇到的问题一样。第一次点击Tab_3后,并不会进行网络请求,只有再次滑动到Fragment_3后才会进行网络请求。


修改每个Fragment的代码如下:

@Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        isCreate = true;
        log("   2__onCreate");
    }
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        load();
    }

    private void load() {
        if (isCreate && getUserVisibleHint() && !isHasLaodOnce){
            log("开始进行网络请求了");
            isCreate = false;
            isHasLaodOnce = true;
        }
    }

     @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        log("   4__onActivityCreated");
        load();
    }

load()方法有3个前提条件,isCreate,可见,没有进行网络请求过。

根据5.1,setUserVisibleHint()这个方法会在预加载Fragment时,会在Fragment的1_onAttach()前调用。此时,在setUserVisibleHint()里面调用也不用害怕空指针的问题,因为有3个前条件。当通过滑动ViewPger时,根据5.1知道,每次滑动都会调用setUserVisibleHint()这个方法,进行预加载后,当滑到Fragment_2,3,4时,就会调用里面的load()方法。

针对5.2和6.2的问题。解决办法就是在Fragment的4_onCreateActivity()中调用load。我个人习惯把网络请求放在这个生命周期。在Fragment_1和点击Tab时,引起问题的原因就是setUserVisibleHint()先于1_onAttach()调用,不能满足前提条件中的isCreate,所以load方法不会被调用。而4_onCreateActivity()中会再次调用load()方法,此时还满足3个前提条件。这样,遗留的问题也解决了。ViewPager中Fragment延迟加载这个需求也可以实现了。如果此时Fragment中有一个轮播图的话,也可以通过getUserVisibleHint()这个方法来选择关闭轮播图线程的时机。


当再次滑回已经加载过的Fragment,空白时,是由于AdapterdestroyItem()将之前的Fragment已经清除

粗暴的做法是ViewPager 的 Adapter 的destroyItem()方法,需要重写,将super()方法注释掉

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
//        super.destroyItem(container, position, object);
    }

但这比较适合Fragment比较少的情况,有3,4个Fragment,因为可能会比较占用内存,这其实和使用setOffscreenPageLimit()来保存Fragment个数的效果是一样

一旦Fragment多了,除了单纯的利用ViewPager的特征外,还可以考虑结合使用 OkHttp的缓存机制来做。把两者结合,效果应该比单纯使用ViewPager要好


7. 总结 <p>

这个周末用了一天半,写好了这篇博客,用来记录下我的学习过程。写的内容也是比较浅显,水平太菜,如果能有能力阅读源码,对于问题2,3,4会有更深入的了解,会有好的办法,而不是这篇博客中折中的方法。如果有啥错误,请赶紧指出。: )

如果你看到了这里,真爱啊。十分感谢。

共勉 :)

推荐阅读更多精彩内容

  • 为什么写这个 在网上也有很多这个例子,但是感觉讲的都不很清楚,于是想自己跑一遍来看看整个过程,话不多说,下面就直接...
    sakurajiang阅读 2,784评论 0 32
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 122,689评论 15 534
  • 在各种Android项目中,我们不可避免要使用到Fragment,但很多地方其实我们只是习惯性或copy代码来使用...
    HolenZhou阅读 1,017评论 1 14
  • 某局办公室主任老张岁数到了,今年就要退休。局里准备在办公室人员中选出一个接替老张。人们议论纷纷,都说肯定在小李与...
    邱春秋阅读 69评论 0 2
  • (1) 今天的办公室,异常安静。 “真像黎明前的黑暗。”吴庸狐疑地想着。 他敲了敲经理助理王芸芸的桌子,眼峰往经理...
    画心师安语嫣阅读 580评论 21 43