Android Fragment在ViewPager中到底經(jīng)歷了什么?

2017年05月30

最后的懶加載寫的不好雅采,推薦請(qǐng)叫我大蘇同學(xué)寫的Fragment懶加載博客痕囱,
【Android】再來一篇Fragment的懶加載(只加載一次哦)

在大蘇同學(xué)的博客評(píng)論里,看到了另一個(gè)實(shí)現(xiàn)思路纬傲,有提到可以使用okHttp的緩存機(jī)制棍掐,有空也再學(xué)一下

1. 一直以來的疑問<p>

Fragment在ViewPager到底經(jīng)歷了哪些生命周期方法楔壤?到底發(fā)生了什么?

常會(huì)TabLayout和ViewPager配合起來使用肌稻,針對(duì)這套組合清蚀,就想也做一些學(xué)習(xí)了解。在一個(gè)ViewPager中經(jīng)常會(huì)存在多個(gè)Fragment爹谭,F(xiàn)ragemnt在ViewPager中的生命周期一直沒有鬧明白枷邪。這周正好在做一個(gè)測(cè)試的時(shí)候又用到了TabLayout和ViewPager組合。ViewPager中的Fragment并想做到延遲加載诺凡,在Fragment可見的時(shí)候再進(jìn)行網(wǎng)絡(luò)請(qǐng)求东揣。在敲代碼的時(shí)候想到到幾個(gè)問題:

1. 在ViewPager中,滑動(dòng)時(shí)腹泌,F(xiàn)ragment會(huì)經(jīng)歷哪些生命周期嘶卧?
2. ViewPager的setOffscreenPageLimit()方法對(duì)Fragment有哪些影響?
3. Fragment的setUserVisibleHint()對(duì)Fragment的生命周期有哪些影響凉袱?
4. 點(diǎn)擊TabLayout的Tab時(shí)芥吟,F(xiàn)ragment經(jīng)歷的生命周期和滑動(dòng)ViewPager有啥不一樣侦铜?

現(xiàn)在有個(gè)明確的需求:在TabLayout和ViewPager這個(gè)組合下,實(shí)現(xiàn)Fragment的延遲加載钟鸵。


2. Tablayout钉稍、ViewPager組合 <p>

代碼很簡(jiǎn)單,就是新建一個(gè)Android Stuido工程棺耍,一個(gè)Activity里面有一個(gè)TabLayout和ViewPager贡未,ViewPager中四個(gè)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的使用羞秤。如果基礎(chǔ)的用法不知道的話可以看看CoordinatorLayout、Tablayout左敌、Toolbar簡(jiǎn)單組合使用,我寫的很基礎(chǔ)的用法瘾蛋。啊哈哈 :)


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的布局更加簡(jiǎn)單,主要就一個(gè)TextView矫限。4個(gè)Fragment布局一樣哺哼,就貼出一個(gè)。


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()這個(gè)方法叼风。主要就是把4個(gè)Fragment加入到ViewPager中取董。


2.3 Fragment代碼 <p>

Fragemnt的生命周期方法共有11個(gè),為了下面能夠更加清晰記住這些生命周期无宿,我給這些生命周期方法從1到11定了編號(hào)茵汰。如下:

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

Fragment原本打算偷懶,只想寫一套孽鸡,不想寫4個(gè)蹂午。但寫一個(gè)的話onAttach()不好做區(qū)分展示,就又老老實(shí)實(shí)寫了四個(gè)彬碱。

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);
    }
}

代碼全部貼完豆胸。都很簡(jiǎn)單。前面一個(gè)說了4個(gè)問題巷疼,到了思考第2,3個(gè)問題時(shí)晚胡,代碼需要小改動(dòng)。


3. 開始分析問題 1 <p>

  • 在ViewPager中嚼沿,滑動(dòng)時(shí)估盘,F(xiàn)ragment會(huì)經(jīng)歷哪些生命周期?

開始前骡尽,這里沒有圖忿檩,就簡(jiǎn)單交代一下,每個(gè)Fragment有一個(gè)對(duì)應(yīng)的數(shù)字爆阶。TAB-1 對(duì)應(yīng)Fragment-1燥透,以此類推沙咏,4個(gè)TAB對(duì)應(yīng)于4個(gè)Fragment。默認(rèn)顯示Fragment1班套。


3.1 滑動(dòng)ViewPager肢藐,切換Fragment <p>

這部分所有的切換Fragment都是通過滑動(dòng)ViewPager。不是點(diǎn)擊Tab吱韭。在Activity中吆豹,ViewPager此時(shí)啥都沒有設(shè)置。


3.1.1 加載Fragment_1 <p>

當(dāng)Activity中ViewPager加載完成時(shí)理盆,手機(jī)屏幕顯示默認(rèn)的Fragment1痘煤。此時(shí),打印的Log信息:


默認(rèn)Fragment_1加載完成時(shí)

此時(shí)打印的Log信息與預(yù)想的有很大不同猿规。我以為Fragment_1會(huì)從 1 onAttach() 開始直到 6 onResume()后衷快,F(xiàn)ragment_2才會(huì)開始從 1onAttach()開始,到了3 onCreateView()會(huì)停止姨俩。然而蘸拔,并不是預(yù)想的這樣。

首先环葵,F(xiàn)ragment_1 經(jīng)歷 1_onAttach() 和 2_onCreate() 后调窍, Fragment_2也開始走了 1_onAttach()和 2_onCreate()方法。

接著张遭,F(xiàn)ragment_1依次經(jīng)歷 3_onCreateView(), 4_onCreateActivity(),5 _onStart ,6_onResume()邓萨。此時(shí),F(xiàn)ragment_1獲得焦點(diǎn)菊卷,已經(jīng)展示在手機(jī)屏幕缔恳。Fragment_2也接著從 3_onCreate()開始直到也執(zhí)行到 6_onResume()方法。疑問就在這的烁,我個(gè)人感覺既然Fragment_2沒有在屏幕顯示褐耳,就不會(huì)執(zhí)行到 6_onResume()方法,然而Log信息卻顯示執(zhí)行到了 6_onResume()诈闺。這里記錄一下渴庆。


3.1.2 Fragment_1向右滑動(dòng)到Fragment_2 <p>

滑動(dòng)一下屏幕,ViewPager中由Fragment_1切換到Fragment_2雅镊,屏幕顯示Fragment_2時(shí)襟雷,下面是Log信息:

由Fragment_1滑到Fragment_2后

哎,我擦仁烹,咋就只有Fragment_3的信息耸弄,F(xiàn)ragment_1和Fragment_2的呢?這Fragment_2沒有Log信息可以理解卓缰,可Fragment_1應(yīng)該會(huì)有啊计呈。這里又和預(yù)想的大不一樣砰诵。預(yù)想中Fragment_1會(huì)走 7__onPause(),8__onStop(),9__onDestroyView()和10__onDestroy()。而實(shí)際卻沒有走捌显,開始還以為搞錯(cuò)了茁彭,又反復(fù)測(cè)試,發(fā)現(xiàn)就是只有Fragment_3的Log信息扶歪。

根據(jù)Log信息理肺,可以看出,當(dāng)滑到Fragment_2后善镰,F(xiàn)ragment_3經(jīng)歷了從 1__onCreate()方法到6__onResume()方法妹萨。Fragment_1卻是沒有走任何生命周期方法。這里考慮了一下炫欺,感覺也蠻合理乎完。ViewPager展示的時(shí)候,用戶在實(shí)際使用的時(shí)候竣稽,經(jīng)常會(huì)從一個(gè)Fragment滑到另一個(gè)Fragment后又切換回來囱怕。這時(shí)如果按照我預(yù)想的,F(xiàn)ragment_1中的View已經(jīng)被銷毀毫别,再次切換回來又需要重新繪制娃弓,這樣頻繁請(qǐng)求內(nèi)存空間也不好。

這時(shí)岛宦,F(xiàn)ragment_3已經(jīng)時(shí)刻準(zhǔn)備好台丛,如果在onCreateView()方法中有網(wǎng)絡(luò)請(qǐng)求的話,網(wǎng)絡(luò)請(qǐng)求在滑動(dòng)到Fragment_2后就會(huì)被調(diào)用砾肺。就等著滑動(dòng)ViewPager后挽霉,來展示內(nèi)容。

這里先預(yù)留問題:為什么滑到Fragment_2后变汪,F(xiàn)ragment_1沒有走任何生命周期方法侠坎?Fragment_1的生命周期方法會(huì)在啥時(shí)候走?

此時(shí)裙盾,可向左滑可向右滑

當(dāng)Fragment_2在屏幕時(shí)实胸,這時(shí)候就有了兩個(gè)方向可以選擇,左滑到Fragment_1 或者 右滑到 Fragment_3番官。同理庐完,當(dāng)屏幕顯示Fragment_3的時(shí)候,也是有兩個(gè)方向可選擇徘熔。


3.1.3 由Fragment_2向右滑到Fragment_3 <p>

前面已經(jīng)提到门躯,當(dāng)由Fragment_1滑到Frament_2后,F(xiàn)ragment_3的生命周期方法已經(jīng)從 1_onCreate()走到了 6_onResume()酷师。當(dāng)滑到Fragment_3后讶凉,看下此時(shí)的Log信息染乌。

由Fragment2滑到到Fragment3后

這次,F(xiàn)ragment_4先走了1_onAttach(),2_onCreate()后懂讯,F(xiàn)ragment_1走 7_onPause,8_onStop,9_onDestoryView()慕匠。Fragment_1的生命周期終于開始走。這里也可以解答3.1.2最后預(yù)留的那個(gè)問題。

看到了這次的Log信息,終于感覺看出了點(diǎn)ViewPager中嵌套Fragment后一些特點(diǎn)粗仓。

當(dāng)ViewPager中的Fragment大于等于3個(gè)的時(shí)候,除去展示開頭和結(jié)尾兩個(gè)Fragment的情況锅铅,ViewPager會(huì)保留一個(gè)Fragment左右兩側(cè)以及自身3個(gè)Fragment的信息。例如减宣,當(dāng)滑到Fragment_2的時(shí)候盐须,F(xiàn)ragment_3也已經(jīng)走到了6_onResume()這個(gè)生命周期方法,而此時(shí)漆腌,F(xiàn)ragemnt_1沒有走任何的生命周期方法贼邓,還在ViewPager中保留著。此外還有闷尿,當(dāng)加載本身或者預(yù)加載下一個(gè)Frgment時(shí)塑径,只是先走1_onAttach()和2_onCreate()兩個(gè)生命周期方法后,才會(huì)根據(jù)當(dāng)前情況確定繼續(xù)走相應(yīng)的Fragment的生命周期方法填具。


3.1.4 由Fragment_2向左滑到Fragment_1 <p>

當(dāng)由Frament_1滑到Fragment_2時(shí)统舀,并沒有走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時(shí),也是只走了Fragment的生命周期方法筋量。在3.1.2中烹吵,當(dāng)滑到Fragment_2后,F(xiàn)ragment_3已經(jīng)走到了6_onResume()方法毛甲。再滑到Fragment_1后年叮,F(xiàn)ragment_3走了7_onPause(),8_onStop(),9_onDestoryView()具被。到了這里發(fā)現(xiàn)玻募,在ViewPager中,相鄰的3個(gè)Fragment之間來回切換一姿,都沒有走10_onDestroy()和11_onDetach()七咧。


到了這里跃惫,四個(gè)Fragment滑動(dòng)的情況再分析查看下面的3種情況。其實(shí)艾栋,下面的這些情況和前面的情況重復(fù)了爆存,本質(zhì)是一樣的。

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

3.1.5 由Fragment_3向右滑到Fragment_4 <p>

滑動(dòng)前蝗砾,當(dāng)屏幕顯示Fragment_3時(shí)先较,此時(shí),由3.1.3知道悼粮,F(xiàn)ragment_1走到了9_onDestroyiew()闲勺。Fragment_4走到了6_onResume()。
滑到Fragment_4后:

由Fragment_3滑到Fragment_4后

Fragment_4是ViewPager中最后一個(gè)Fragment所以也就沒了下一個(gè)Fragment預(yù)加載扣猫。只是不相鄰的Fragment_2走了7_pause(),8_onStop,9_onDestroyView()方法菜循。此時(shí),就可以結(jié)合3.1.3情況來看申尤。實(shí)際的情況是一樣的癌幕,只是區(qū)別在于Fragment_4已經(jīng)是最后一個(gè)Fragment了。


3.1.6 由Fragment_3向左滑到Fragment_2 <p>

這里結(jié)合3.1.4昧穿,很容易就理解了勺远。
滑動(dòng)前,當(dāng)屏幕顯示Fragment_3時(shí)时鸵,此時(shí)谚中,由3.1.3知道,F(xiàn)ragment_1走到了9_onDestroyiew()寥枝。Fragment_4走到了6_onResume()宪塔。
滑動(dòng)到Fragment_2后:


由Fragment3滑到Fragment2后

根據(jù)3.1.3中的Log信息,F(xiàn)rgment_1在由Fragment_2滑到Fragment_3的時(shí)候囊拜,生命周期已經(jīng)走到了9_onDestroyView()某筐,并沒有走到10_onDestroy()。當(dāng)由Fragment_3滑到Fragment_2后冠跷,ViewPager再次預(yù)加載Fragment_1時(shí)南誊,是從3_onCreateView()開始的,走到4_onCreateAcitivty()后蜜托,開始走Fragment_4的生命周期方法抄囚。


3.1.7 由Fragment_4向左滑到Fragment_3 <p>

滑動(dòng)前,當(dāng)顯示Fragment_4時(shí)橄务,根據(jù)3.1.5的Log信息幔托,F(xiàn)ragment_2走到了9_onDestroyView()生命周期方法。而Fragment_4此時(shí)處于6_onResume()這個(gè)生命周期方法。
滑動(dòng)到Fragment_3后:

由Fragment4滑到Fragment3后

到了這里重挑,就比較容易理解此時(shí)的Log信息嗓化。Fragment_2從3_onCreateView()走到了6_onResume()方法。到了此時(shí)谬哀,F(xiàn)ragment_2刺覆、Fragment_3和Fragment_4此時(shí)3個(gè)Fragment都處于6_onResume()這個(gè)生命周期方法。


到了這里史煎,ViewPager中Fragment滑動(dòng)的情況就差不多分析完了谦屑。其他都是些重復(fù)的情況了。根據(jù)3.1.1到3.1.7的Log來看篇梭,ViewPager中嵌套Fragment默認(rèn)不設(shè)置其他方法時(shí)伦仍,若Fragment的數(shù)量大于等于3時(shí),ViewPager會(huì)保留包括一個(gè)Fragment在內(nèi)左右兩側(cè)3個(gè)Fragment的信息很洋。然而這里也留下了個(gè)問題充蓝,Fragment的10_onDestroy和11_onDetach()什么時(shí)候會(huì)走?


3.1.8 點(diǎn)擊回退鍵finish掉Acitivty時(shí) <p>

根據(jù)3.1.7的信息喉磁,此時(shí)谓苟,F(xiàn)ragment_1處于9_onDestroyView()這個(gè)生命周期方法,F(xiàn)ragment_2协怒、Fragment_3和Fragment_4此時(shí)3個(gè)Fragment都處于6_onResume()這個(gè)生命周期方法涝焙。
此時(shí),點(diǎn)擊back鍵結(jié)束當(dāng)前的Acitivty孕暇,我這里整個(gè)測(cè)試的App就一個(gè)Activity仑撞,點(diǎn)擊了back鍵就會(huì)退出應(yīng)用⊙希看下此時(shí)的Log信息:

點(diǎn)擊Back鍵時(shí)

根據(jù)Log信息隧哮,F(xiàn)ragment_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中承二,F(xiàn)ragment的10_onDestroy()以及11_onDetach()會(huì)在ViewPager所在的Activity結(jié)束后被調(diào)用榆鼠。

這里還有一點(diǎn)需要說的是,點(diǎn)擊back鍵后亥鸠,圖(點(diǎn)擊Back鍵時(shí))中的Log信息并不是一次打印出來的妆够。一開始我以為是我是看錯(cuò)了,又測(cè)試了幾次,發(fā)現(xiàn)责静,F(xiàn)ragment_2,3,4走了7_onPause()這個(gè)方法后,確實(shí)會(huì)短暫停一下盖桥,非吃煮Γ快的又打印出下面的Log信息。


3.1.9 當(dāng)屏幕顯示Fragment_3時(shí)揩徊,點(diǎn)擊電源鍵關(guān)閉屏幕 <p>

當(dāng)屏幕正在展示某個(gè)Fragment時(shí)腰鬼,點(diǎn)擊了電源鍵或者手機(jī)自動(dòng)息屏?xí)r,這種情況下塑荒,會(huì)和以上的幾種情況有所不同熄赡,下面還是以Fragment_3為例來嘗試分析一下。

根據(jù)3.1.7的信息齿税,此時(shí)彼硫,F(xiàn)ragment_1處于9_onDestroyView()這個(gè)生命周期方法,F(xiàn)ragment_2凌箕,3拧篮,4此時(shí)3個(gè)Fragment都處于6_onResume()這個(gè)生命周期方法。

點(diǎn)擊電源鍵之后:

點(diǎn)擊電源鍵后

Fragment_2,3,4依次走了7_onPause(),8_onStop()牵舱。接下來串绩,再點(diǎn)亮屏幕,顯示Fragment_3:

再次點(diǎn)亮屏幕后

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


到了此時(shí)礁凡,ViewPager中Fragment滑動(dòng)的情況就結(jié)束了。這些情況慧妄,寫一個(gè)很簡(jiǎn)單的測(cè)試demo就可以搞的比較清楚了顷牌。要一次就記得清楚也不算特別現(xiàn)實(shí),多想幾次就可以了塞淹。


4. 嘗試分析問題2<p>

  • ViewPager的setOffscreenPageLimit()方法對(duì)Fragment有哪些影響韧掩?
    直白翻譯就是設(shè)置幕后頁面限制 :) 。其實(shí)就是設(shè)置緩存在ViewPager中的Fragment的數(shù)量窖铡。
 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中源碼可以看出疗锐,傳入的參數(shù)如果小于DEFAULT_OFFSCREEN_PAGES(就是1)這個(gè)值是無效的。所以嘗試使用setOffscreenPageLimit(0)來關(guān)閉ViewPager的預(yù)加載是無效费彼。也就是說滑臊,limit只有大于等于2時(shí)才會(huì)有效。Ps:ViewPager調(diào)用這個(gè)方法后箍铲,里面populate()這個(gè)方法又做了啥雇卷,我目前想關(guān)心也關(guān)心不了,大概看了下,ViewPager共有3000多行代碼关划,我目前的代碼閱讀能力還很低小染,即使想做到只是閱讀相關(guān)代碼也困難,目前想讀通比較困難贮折,以后代碼閱讀能力提升再來看了裤翩。


接下來,在ViewPager中调榄,加入viewPager.setOffscreenPageLimit(2)這行代碼踊赠,再次運(yùn)行,看看Log信息

setOffscreenLimit(2)后

根據(jù)問題1的分析每庆,看到這個(gè)Log信息就很好理解了筐带,多了Fragment_3的Log信息,而且走的方法和Frgment_1和2是一樣的缤灵。后面的情況的Log信息便不再貼出來了伦籍,本質(zhì)是一樣的,只是多預(yù)加載了一個(gè)Fragment腮出。但此時(shí)ViewPager依然只是保留3個(gè)Fragment的信息鸽斟。當(dāng)滑到Fragment_4的時(shí)候,F(xiàn)ragment_1走了7_onPause(),8_onStop(),9_onDestroyView()利诺。Fragment_2,3,4則處于6_onResume()富蓄。

這個(gè)方法對(duì)Fragment生命周期方法的調(diào)用順序上并沒有什么影響,只是預(yù)加載的Fragment的數(shù)量受設(shè)置的limit參數(shù)影響慢逾。


5. 嘗試分析問題3 <p>

  • 在ViewPager中,Fragment的setUserVisibleHint()對(duì)Fragment的生命周期有哪些影響立倍?

再次顧名思義:設(shè)置用戶可見暗示 。:) 這個(gè)方法可以用來判斷Fragment是否可見侣滩。這里也不再貼源碼了口注。


5.1 Fragment加入setUserVisibleHint() <p>

修改代碼前,我把在分析問題2時(shí)加入的viewPager.setOffscreenPageLimit(2)注釋掉君珠。這樣Log信息會(huì)少一些寝志。在每個(gè)Fragment中代碼做了一些修改加入了一個(gè)setUserVisibleHint()

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

再次運(yùn)行Demo策添,查看Log信息:

加入setUserVisibleHint()方法后

和預(yù)想的大不一樣材部。setUserVisibleHint()竟然最先被調(diào)用。預(yù)想的是1_onAttach()執(zhí)行后唯竹,才會(huì)走setUserVisibleHint()這個(gè)方法乐导。而且,F(xiàn)ragment_1走了兩次setUserVisibleHint()這個(gè)方法浸颓。其他的Log信息倒是比較熟悉了物臂。
滑動(dòng)屏幕旺拉,滑動(dòng)到Fragment_2,看下此時(shí)的Log信息:

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

這時(shí)不僅先執(zhí)行了Fragment_3的setUserVisibleHint()棵磷,連Fragment_1,2的setUserVisibleHint()方法也比Fragment_3的1_onAttach()蛾狗。此時(shí),F(xiàn)ragment_1,2,3都處于6_onResume()仪媒。再由Fragment_2滑到Fragment_1沉桌,看下Log:

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

此時(shí)规丽,F(xiàn)ragment_1,2的setUserVisibleHint()方法優(yōu)先被調(diào)用蒲牧,F(xiàn)ragment_3走到9_onDestroyView()方法撇贺。

到了這里赌莺,感覺setUserVisibleHint()這個(gè)方法每次滑動(dòng)都會(huì)被調(diào)用,而且最先被調(diào)用松嘶,緩存在ViewPager中的Fragment都會(huì)被調(diào)用艘狭。

接下來模擬一下延遲加載網(wǎng)絡(luò)請(qǐng)求。


5.2 延遲加載翠订,模擬網(wǎng)絡(luò)請(qǐng)求 <p>

在每個(gè)Fragment中巢音,加入一個(gè)Boolean值,并將代碼做一些改動(dòng)尽超。

 @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("開始進(jìn)行網(wǎng)絡(luò)請(qǐng)求了");
            isCreate = false;
        }
    }

當(dāng)Fragment可見時(shí)官撼,進(jìn)行網(wǎng)絡(luò)請(qǐng)求。網(wǎng)絡(luò)請(qǐng)求加上了前提條件似谁。除了isVisibleToUser為true外還有一個(gè)isCreate傲绣。Fragment走了2_onCreate()iSCreate設(shè)為了true。我通常會(huì)用Fragment.newInstrance()這個(gè)方法進(jìn)行初始化Fragment時(shí)來傳值巩踏,在2_onCreate()方法中通過getArguments()來接收值秃诵,所以這里加了一個(gè)條件isCreate為true。


再次運(yùn)行Demo塞琼,看Log信息:

加入模擬網(wǎng)絡(luò)請(qǐng)求后菠净,加載Fragment_1后

我擦,我的"開始進(jìn)行網(wǎng)絡(luò)請(qǐng)求了"呢彪杉?最關(guān)鍵的Log信息卻沒有看到毅往。根據(jù)5.1,每次滑動(dòng)時(shí)派近,肯定會(huì)調(diào)用setUserVisibleHint()這個(gè)方法的啊煞抬,可Fragment_1明明都已經(jīng)可見了,怎么沒有出現(xiàn)"開始進(jìn)行網(wǎng)絡(luò)請(qǐng)求了"构哺?于是革答,看了下源碼战坤,...,白看残拐,沒瞧出啥途茫。好吧,我再滑下屏幕,Fragment_2顯示溪食,看下Log:

加入模擬網(wǎng)絡(luò)請(qǐng)求后囊卜,由Fragment_1滑到Frgment_2后

滑到Fragment_2后,第一行倒是打印出來了"開始進(jìn)行網(wǎng)絡(luò)請(qǐng)求了"错沃。繼續(xù)滑栅组,F(xiàn)ragment_3,4也都打印出來了。然后我又依次從Fragment_4滑到了Fragment_1后枢析,Log信息也打出了"E/Fragment_1: -------->開始進(jìn)行網(wǎng)絡(luò)請(qǐng)求了"

可為啥Fragment_1第一次顯示時(shí)玉掸,沒有打印呢?

這里結(jié)合5.1想了一下醒叁,setUserVisibleHint()這個(gè)方法在1_onAttach()方法之前司浪。Fragment_1第一次加載時(shí),2_onCreate()方法還沒走呢把沼,此時(shí)isCreate的值還是fasle啊易,就不會(huì)打印"開始進(jìn)行網(wǎng)絡(luò)請(qǐng)求了"。根據(jù)5.1的Log信息饮睬,F(xiàn)ragment_1兩次調(diào)用setUserVisibleHint()時(shí)租谈,isCreate都為false。而isCreate值變?yōu)閠rue后捆愁,卻不在執(zhí)行setUserVisibleHint()方法了割去。

暫時(shí)的解決思路在 6.2 給出


6. 嘗試分析問題4<p>

  • 點(diǎn)擊TabLayout的Tab時(shí),F(xiàn)ragment經(jīng)歷的生命周期和滑動(dòng)ViewPager有啥不一樣牙瓢?

問題1,2,3都是通過滑動(dòng)來分析的劫拗,沒有通過點(diǎn)擊Tab。現(xiàn)在來看看點(diǎn)擊Tab矾克。


6.1點(diǎn)擊Tab時(shí)页慷,F(xiàn)ragment所走的生命周期 <p>

開始前,把每個(gè)Fragment中的setUserVisibleHint()先注釋掉,之后運(yùn)行Demo胁附。這里點(diǎn)擊Tab_2是體現(xiàn)不出啥效果的酒繁,因?yàn)镕ragment_2已經(jīng)預(yù)加載了。我這里點(diǎn)擊Tab_3控妻,看Log信息:

點(diǎn)擊Tab_3后

到了這里州袒,這個(gè)Log信息還是比較容易理解了。此時(shí)的Fragment_2,3,4處于6_onResume()這個(gè)方法弓候。Fragment_1走到9_onDestroyView()郎哭。和滑動(dòng)時(shí)其實(shí)沒有本質(zhì)的區(qū)別他匪。就是點(diǎn)擊可以從Fragment_1越過Fragment_2而直接到Fragment_3這種交互上的區(qū)別而造成的少了滑動(dòng)時(shí)對(duì)Fragment_3的預(yù)加載。如果是從Fragment_1滑到Fragment_3夸研,在滑到Fragment_2時(shí)邦蜜,就已經(jīng)完成了對(duì)Fragment_3的預(yù)加載。而通過點(diǎn)擊的方式時(shí)亥至,點(diǎn)擊Tab_3后悼沈,F(xiàn)ragment_3是從1_onAttach()這個(gè)生命周期方法開始的。


6.2 點(diǎn)擊Tab的方式和Fragment延遲加載遇到的問題 <p>

在5.2中遇到的一個(gè)問題就是姐扮,F(xiàn)ragment_1用了我寫的那種延遲加載的方法時(shí)絮供,第一次加載不會(huì)進(jìn)行網(wǎng)絡(luò)請(qǐng)求,只有再次滑到Fragment_1后才會(huì)進(jìn)行網(wǎng)絡(luò)請(qǐng)求茶敏。到了這里壤靶,同樣也會(huì)引起這個(gè)問題。少了預(yù)加載睡榆,例如6.1中萍肆,直接點(diǎn)擊Tab_3時(shí)袍榆,此時(shí)Fragment_3并沒有預(yù)加載胀屿。setUesrVisibleHint()的方法又比1_onAttach()方法調(diào)用的早。會(huì)有和5.2中遇到的問題一樣包雀。第一次點(diǎn)擊Tab_3后宿崭,并不會(huì)進(jìn)行網(wǎng)絡(luò)請(qǐng)求,只有再次滑動(dòng)到Fragment_3后才會(huì)進(jìn)行網(wǎng)絡(luò)請(qǐng)求才写。


修改每個(gè)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("開始進(jìn)行網(wǎng)絡(luò)請(qǐng)求了");
            isCreate = false;
            isHasLaodOnce = true;
        }
    }

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

load()方法有3個(gè)前提條件葡兑,isCreate,可見赞草,沒有進(jìn)行網(wǎng)絡(luò)請(qǐng)求過讹堤。

根據(jù)5.1,setUserVisibleHint()這個(gè)方法會(huì)在預(yù)加載Fragment時(shí)厨疙,會(huì)在Fragment的1_onAttach()前調(diào)用洲守。此時(shí),在setUserVisibleHint()里面調(diào)用也不用害怕空指針的問題沾凄,因?yàn)橛?個(gè)前條件梗醇。當(dāng)通過滑動(dòng)ViewPger時(shí),根據(jù)5.1知道撒蟀,每次滑動(dòng)都會(huì)調(diào)用setUserVisibleHint()這個(gè)方法叙谨,進(jìn)行預(yù)加載后,當(dāng)滑到Fragment_2,3,4時(shí)保屯,就會(huì)調(diào)用里面的load()方法手负。

針對(duì)5.2和6.2的問題涤垫。解決辦法就是在Fragment的4_onCreateActivity()中調(diào)用load。我個(gè)人習(xí)慣把網(wǎng)絡(luò)請(qǐng)求放在這個(gè)生命周期竟终。在Fragment_1和點(diǎn)擊Tab時(shí)雹姊,引起問題的原因就是setUserVisibleHint()先于1_onAttach()調(diào)用,不能滿足前提條件中的isCreate衡楞,所以load方法不會(huì)被調(diào)用吱雏。而4_onCreateActivity()中會(huì)再次調(diào)用load()方法,此時(shí)還滿足3個(gè)前提條件瘾境。這樣歧杏,遺留的問題也解決了。ViewPager中Fragment延遲加載這個(gè)需求也可以實(shí)現(xiàn)了迷守。如果此時(shí)Fragment中有一個(gè)輪播圖的話犬绒,也可以通過getUserVisibleHint()這個(gè)方法來選擇關(guān)閉輪播圖線程的時(shí)機(jī)。


當(dāng)再次滑回已經(jīng)加載過的Fragment兑凿,空白時(shí)凯力,是由于AdapterdestroyItem()將之前的Fragment已經(jīng)清除

粗暴的做法是ViewPager 的 Adapter 的destroyItem()方法,需要重寫礼华,將super()方法注釋掉

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

但這比較適合Fragment比較少的情況咐鹤,有3,4個(gè)Fragment圣絮,因?yàn)榭赡軙?huì)比較占用內(nèi)存祈惶,這其實(shí)和使用setOffscreenPageLimit()來保存Fragment個(gè)數(shù)的效果是一樣

一旦Fragment多了,除了單純的利用ViewPager的特征外扮匠,還可以考慮結(jié)合使用 OkHttp的緩存機(jī)制來做捧请。把兩者結(jié)合,效果應(yīng)該比單純使用ViewPager要好


7. 總結(jié) <p>

這個(gè)周末用了一天半棒搜,寫好了這篇博客疹蛉,用來記錄下我的學(xué)習(xí)過程。寫的內(nèi)容也是比較淺顯力麸,水平太菜可款,如果能有能力閱讀源碼,對(duì)于問題2,3,4會(huì)有更深入的了解末盔,會(huì)有好的辦法筑舅,而不是這篇博客中折中的方法。如果有啥錯(cuò)誤陨舱,請(qǐng)趕緊指出翠拣。: )

如果你看到了這里,真愛啊游盲。十分感謝误墓。

共勉 :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛮粮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谜慌,更是在濱河造成了極大的恐慌然想,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欣范,死亡現(xiàn)場(chǎng)離奇詭異变泄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)恼琼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門妨蛹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晴竞,你說我怎么就攤上這事蛙卤。” “怎么了噩死?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵颤难,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我已维,道長(zhǎng)行嗤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任衣摩,我火速辦了婚禮昂验,結(jié)果婚禮上捂敌,老公的妹妹穿的比我還像新娘艾扮。我一直安慰自己,他們只是感情好占婉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布泡嘴。 她就那樣靜靜地躺著,像睡著了一般逆济。 火紅的嫁衣襯著肌膚如雪酌予。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天奖慌,我揣著相機(jī)與錄音抛虫,去河邊找鬼。 笑死简僧,一個(gè)胖子當(dāng)著我的面吹牛建椰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岛马,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼棉姐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼屠列!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伞矩,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤笛洛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乃坤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苛让,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年湿诊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝌诡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枫吧,死狀恐怖浦旱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情九杂,我是刑警寧澤颁湖,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站例隆,受9級(jí)特大地震影響甥捺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜镀层,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一镰禾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唱逢,春花似錦吴侦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痪枫,卻和暖如春织堂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奶陈。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工易阳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吃粒。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓潦俺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子黑竞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 為什么寫這個(gè) 在網(wǎng)上也有很多這個(gè)例子捕发,但是感覺講的都不很清楚,于是想自己跑一遍來看看整個(gè)過程很魂,話不多說扎酷,下面就直接...
    sakurajiang閱讀 4,737評(píng)論 0 32
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評(píng)論 25 707
  • 在各種Android項(xiàng)目中法挨,我們不可避免要使用到Fragment,但很多地方其實(shí)我們只是習(xí)慣性或copy代碼來使用...
    HolenZhou閱讀 2,072評(píng)論 1 15
  • 某局辦公室主任老張歲數(shù)到了,今年就要退休幅聘。局里準(zhǔn)備在辦公室人員中選出一個(gè)接替老張凡纳。人們議論紛紛,都說肯定在小李與...
    邱春秋閱讀 595評(píng)論 0 2
  • (1) 今天的辦公室帝蒿,異常安靜荐糜。 “真像黎明前的黑暗「鸪”吳庸狐疑地想著暴氏。 他敲了敲經(jīng)理助理王蕓蕓的桌子,眼峰往經(jīng)理...
    畫心師安語嫣閱讀 1,301評(píng)論 21 43