Activity恢復(fù)時如何獲取Fragment

Activity 在重建的時候會恢復(fù)其包含的 FragmentManager 极祸,F(xiàn)ragmentManager 又會恢復(fù)其管理的 Fragment 否纬,同理 Fragment 也會恢復(fù)其包含的 FragmentManager吕晌,層層遞進(jìn),直到全部恢復(fù)临燃。
本文主要討論Activity重建的時候如何獲取Fragment的睛驳。即如下情況:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState)
    FragmentManager fm = getSupportFragmentManager();
    if (savedInstanceState != null) {            // 本文討論的情況

    } else {
                                                 // 非本文討論的情況
    }
}

具體通過四種方法來獲取復(fù)用Fragment。

  • fm.getFragments
  • fm.findFragmentById
  • fm.findFragmentByTag
  • fm.putFragment與getFragment

下面將對這四種情況分別加以分析說明谬俄。

fm.getFragments

首先來一個總結(jié)柏靶,不建議使用該方法獲取Fragment。理由有如下三點(diǎn):

1. 得到的Fragment可能包含你不需要的Fragment

getFragments方法獲取的是所有已經(jīng)添加到FragmentManager中的Fragment溃论。但是這個FragmentManager中保存的不只是我們定義的Fragment屎蜓,還有可能會包含其他用途的Fragment。
Fragment不僅僅是界面的載體钥勋,同時它可以用來實現(xiàn)生命周期的監(jiān)聽炬转,因為它的生命周期和Activity是一致的,當(dāng)我們不好監(jiān)聽Activity的生命周期的時候算灸,就可以使用Fragment來輔助監(jiān)聽扼劈。圖片加載庫Glide和Android Jet Pack中的ViewModel都使用了這種模式。

2. 得到的Fragment列表中Fragment順序是不可控的

前段時間就遇到過這樣的bug

FragmentA fragmentA = (FragmentA) fm.getFragments().get(0)
FragmentB FragmentB = (FragmentB) fm.getFragments().get(1)

然后就出現(xiàn)了類型轉(zhuǎn)換異常菲驴,F(xiàn)ragmentA不能被強(qiáng)制轉(zhuǎn)換成FragmentB荐吵。
當(dāng)時我百思不得其解,為什么會出現(xiàn)這個問題啊赊瞬,我明明是按照Fragment添加到FragmentManager的順序去獲取Fragment的啊先煎。
直到我將getFragments獲取到的Fragment列表打印出來才發(fā)現(xiàn),其中的Fragment順序和我添加到FragmentManager中的順序是不一致的巧涧。
也就是說因為getFragments中獲取到的Fragment包含了你不想要的Fragment薯蝎,而這些Fragment的初始化時機(jī)又是不可預(yù)料的,所以就不能通過Fragment列表準(zhǔn)確定位你需要的Fragment谤绳。

3. 26.x.y版本中返回值發(fā)生變更

這個情況我沒有遇到過占锯,參考文章的作者怪盜kidou遇到過,就順便寫上了缩筛。
在版本25中Activity是新建的請款下消略,getFragments返回的是null,然后到了26版本瞎抛,getFragments返回的就是Collectiions.EmpytList()疑俭。。婿失。
這就導(dǎo)致原來基于null判斷的程序出現(xiàn)bug钞艇。

fm.findFragmentById()

這個方法是通過Fragment中所在的ViewGroup的Android:id定義的id來查找,適合一個ViewGroup中只存在一個Fragment的情況豪硅。

<FrameLayout
    android:id="@+id/fragment_container"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android" />
 FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        //將fragment放入activity中
        if (fragment == null) {
            fragment = ArticleDetailFragment.newInstance(detailData);
            fm.beginTransaction()
                    .add(R.id.fragment_container, fragment)
                    .commit();
        }

當(dāng)然哩照,如果一個ViewGroup中有多個Fragment的情況下也是可以使用的,不過這個時候獲取的就是最后添加到ViewGroup中的Fragment了懒浮。

fm.findFragmentByTag()

該方法就是用來處理findFragmentById不適用的情況的飘弧。因為是通過Tag來查找Fragment,所以ViewGroup的id也就沒用了砚著。

if (savedInstanceState != null) {
        mainFragment = (MainFragment) fm.findFragmentByTag(MainFragment.TAG);
    } else {
        mainFragment = new MainFragment();
        fm.beginTransaction()
                .add(android.R.id.content, mainFragment, MainFragment.TAG)
                .commit();
    }

需要注意的地方:

  • tag是可以重復(fù)的次伶,因為這個參數(shù)只是Fragment的一個成員變量,只是我們無法訪問(訪問權(quán)限default)稽穆。
  • 如果有多個Fragment的tag是一樣的冠王,那么返回的則是最后一個添加的tag相同的fragment。
  • 不要為同一個Fragment實例對象在不同的操作中指定不同的tag舌镶,否則會出現(xiàn)異常柱彻。當(dāng)然這種情況一般是出現(xiàn)在重復(fù)添加的情況下。

fm.getFragment與fm.putFragment

有一種情況下是不能夠使用getFragmentById和getFragmentByTag,那就是使用ViewPager管理Fragment的情況餐胀。

  • 因為ViewPager在布局文件中使用的Android:id是不變的哟楷,但是其容納的Fragment是可以不斷變化的,這種情況下就不能通過id來查找Fragment否灾。
  • 使用ViewPager來添加移除Fragment的時候卖擅,我們是不能夠在其中添加Tag的,所以getFragmentByTag也是不能使用的墨技。
    以前查找ViewPager中查找Fragment的時候的確發(fā)現(xiàn)了如下通過在FragmentPagerAdapter中強(qiáng)行設(shè)置tag來查找Fragment的情況惩阶。
// FragmentPagerAdapter.java
private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

這種方法是可用的,但是太過麻煩健提,可以使用fm.putFragment和fm.getFragment來處理這種情況琳猫。
這兩個方法的使用如下:

private MainFragment mainFragment;
private SecondaryFragment secondaryFragment;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        mainFragment = (MainFragment) fm.getFragment(savedInstanceState, MainFragment.TAG);
        secondaryFragment = (SecondaryFragment) fm.getFragment(savedInstanceState, SecondaryFragment.TAG);
    }
    if (mainFragment == null) {
        mainFragment = new MainFragment();
    }
    if(secondaryFragment == null){
        secondaryFragment = new SecondaryFragment()
    }
    // ViewPager 的相關(guān)操作
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (mainFragment.isAdded()) {
        fm.putFragment(outState, MainFragment.TAG, mainFragment);
    }
    if (secondaryFragment.isAdded()) {
        fm.putFragment(outState, SecondaryFragment.TAG, secondaryFragment);
    }
}

可能有朋友會感覺這個和getFragmentByTag那么像呢,好像沒什么區(qū)別私痹。
重點(diǎn)來了脐嫂,因為在ViewPager中添加和移除Fragment是由ViewPager控制的,所以像是

fm.beginTransaction()
                .add(android.R.id.content, mainFragment, MainFragment.TAG)
                .commit();

這種方法就不能被使用了紊遵,既然無法在添加fragment的時候設(shè)置tag账千,那我們就不能夠通過tag直接從FragmentManager中的Fragment列表中獲取了。
那putFragment和getFragment方法是怎么做到這一點(diǎn)的呢暗膜?
我們查看下這兩個方法的源碼:

// FragmentManager.java,摘自版本 27.1.1
@Override
public void putFragment(Bundle bundle, String key, Fragment fragment) {
    // 沒有被添加到 FragmentManager
    if (fragment.mIndex < 0) { 
        throwException(new IllegalStateException("Fragment " + fragment
                + " is not currently in the FragmentManager"));
    }
    //注意T茸唷!学搜!此處是把Tag和Fragment的mIndex作為一組數(shù)據(jù)存儲
    bundle.putInt(key, fragment.mIndex);
}

@Override
public Fragment getFragment(Bundle bundle, String key) {
    int index = bundle.getInt(key, -1);
    //當(dāng)bundle中不存在該Tag時
    if (index == -1) {
        return null;
    }
    //從mActive中獲取Fragment
    Fragment f = mActive.get(index);
    if (f == null) {
        throwException(new IllegalStateException("Fragment no longer exists for key "
                + key + ": index " + index));
    }
    return f;
}

通過上述源碼娃善,我們可以看出论衍,putFragment將待存儲的Fragment的Tag和mIndex作為一組數(shù)據(jù)存儲在bundle中,然后在getFragment方法內(nèi)先從bundle中取出對應(yīng)Tag的mIndex聚磺,最后根據(jù)這個mIndex從mActive中取出對應(yīng)的Fragment坯台。
mActive是真正存儲Fragment的對象,但是我們不能夠直接使用Tag從中取出瘫寝,因為ViewPager是使用mIndex來作為key值存儲Fragment的蜒蕾。所以我們只能夠退而求其次,將Tag和mIndex聯(lián)系起來焕阿,達(dá)到間接使用Tag取出Fragment的效果咪啡。
下面是對上述源碼及步驟的圖形化表示:


Fragment存儲形式.png
Fragment獲取步驟.jpg

注意事項:

  • getFragment和putFragment方法必須成對使用。
  • 在調(diào)用putFragment之前暮屡,必須保證該Fragment已經(jīng)被添加到了FragmentManager中撤摸,即其mIndex >= 0,否則會拋出異常。
if (fragment.mIndex < 0) { 
        throwException(new IllegalStateException("Fragment " + fragment
                + " is not currently in the FragmentManager"));
    }

總結(jié)

  • 在使用Activity和Fragment的時候栽惶,應(yīng)該分清楚恢復(fù)和新建的作用愁溜,如果能夠從FragmentManager中恢復(fù),就優(yōu)先使用恢復(fù)的Fragment外厂,不能夠恢復(fù)再新建冕象。
  • 不要使用getFragments方法來獲取Fragment,可能會導(dǎo)致各種意想不到的錯誤汁蝶。
  • 在ViewGroup中只有一個Fragment的時候渐扮,優(yōu)先使用getFragmentById方法。如果該ViewGroup中有多個Fragment掖棉,則返回最后一個添加到FragmentManager的Fragment墓律。
  • 在ViewGroup中有多個Fragment的時候,應(yīng)該考慮使用getFragmentByTag方法幔亥,當(dāng)有多個Fragment的Tag相同的時候耻讽,則返回最后一個添加到FragmentManager中的Fragment。
  • 在使用ViewPager的時候帕棉,應(yīng)該使用putFragment和getFragment這一對方法针肥。
    最后上一張比較形象的查找步驟圖。


    查找步驟圖.jpg

參考:你真的會用Fragment嗎香伴?Fragment復(fù)用的那些事兒

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末慰枕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子即纲,更是在濱河造成了極大的恐慌具帮,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蜂厅,居然都是意外死亡匪凡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門葛峻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锹雏,“玉大人,你說我怎么就攤上這事术奖。” “怎么了轻绞?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵采记,是天一觀的道長。 經(jīng)常有香客問我政勃,道長唧龄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任奸远,我火速辦了婚禮既棺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懒叛。我一直安慰自己丸冕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布薛窥。 她就那樣靜靜地躺著胖烛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诅迷。 梳的紋絲不亂的頭發(fā)上佩番,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音罢杉,去河邊找鬼趟畏。 笑死,一個胖子當(dāng)著我的面吹牛滩租,可吹牛的內(nèi)容都是我干的赋秀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼持际,長吁一口氣:“原來是場噩夢啊……” “哼沃琅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜘欲,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤益眉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體郭脂,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡年碘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了展鸡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屿衅。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖莹弊,靈堂內(nèi)的尸體忽然破棺而出涤久,到底是詐尸還是另有隱情,我是刑警寧澤忍弛,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布响迂,位于F島的核電站,受9級特大地震影響细疚,放射性物質(zhì)發(fā)生泄漏蔗彤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一疯兼、第九天 我趴在偏房一處隱蔽的房頂上張望然遏。 院中可真熱鬧,春花似錦吧彪、人聲如沸待侵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诫给。三九已至,卻和暖如春啦扬,著一層夾襖步出監(jiān)牢的瞬間中狂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工扑毡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胃榕,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓瞄摊,卻偏偏與公主長得像勋又,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子换帜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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