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閱讀 4,809評論 0 32
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,813評論 25 708
  • 在各種Android項目中,我們不可避免要使用到Fragment,但很多地方其實我們只是習慣性或copy代碼來使用...
    HolenZhou閱讀 2,120評論 1 15
  • 某局辦公室主任老張歲數到了,今年就要退休。局里準備在辦公室人員中選出一個接替老張。人們議論紛紛,都說肯定在小李與...
    邱春秋閱讀 621評論 0 2
  • (1) 今天的辦公室,異常安靜。 “真像黎明前的黑暗。”吳庸狐疑地想著。 他敲了敲經理助理王蕓蕓的桌子,眼峰往經理...
    畫心師安語嫣閱讀 1,316評論 21 43