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的效果咪啡。
下面是對上述源碼及步驟的圖形化表示:
注意事項:
- 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