Fragment重疊異常

一、什么是Fragment重疊洪燥?
二欠拾、什么情況下會發(fā)生Fragment重疊错蝴?
三门扇、為什么會發(fā)生Fragment重疊啃奴?
  1.重復replace/add Fragment
  2.使用show()或hide()控制Fragment
四梢褐、跟"重疊"輕松Say Goodbye
  1.不重復replace/add Fragment
  2.show()或hide() Fragment不重疊


什么是Fragment重疊挑社?

在使用Fragment過程中蚌斩,在某些情況下可能會發(fā)現(xiàn)一直表現(xiàn)正常的Fragment铆惑,突然重疊了,其表現(xiàn)為幾個Fragment的界面混合重疊在一起了送膳。下面就是一種Fragment重疊異常表現(xiàn):
(1)正常情況下的效果圖:


正常情況

(2)Fragment重疊:


Fragment重疊

由以下打印出的生命周期也可以看出员魏,發(fā)生Fragment重疊時,F(xiàn)ragment產(chǎn)生了2個實例叠聋,導致彈了兩個ToastDialog:

10-20 10:52:23.577 1616-1616/com.sankuai.meituan D/Toast: FlightSubmitOrderActivity onCreate()
10-20 10:52:23.781 1616-1616/com.sankuai.meituan D/Toast: FlightSubmitOrderFragment onViewCreated()
10-20 10:52:23.786 1616-1616/com.sankuai.meituan D/Toast: FlightSubmitOrderFragment showToastDialog()
10-20 10:52:23.864 1616-1616/com.sankuai.meituan D/Toast: FlightSubmitOrderFragment onViewCreated()
10-20 10:52:23.864 1616-1616/com.sankuai.meituan D/Toast: FlightSubmitOrderFragment showToastDialog()

什么情況下會發(fā)生Fragment重疊撕阎?

在“內(nèi)存重啟”后回到前臺,頁面發(fā)生銷毀重建(旋轉(zhuǎn)屏幕碌补、內(nèi)存不足等情況被強殺重啟)虏束,如果沒對頁面重啟后的Fragment狀態(tài)做好處理,就容易發(fā)生Fragment重疊厦章。
我們知道Activity中有個onSaveInstanceState()方法镇匀,該方法在app進入后臺、屏幕旋轉(zhuǎn)前袜啃、跳轉(zhuǎn)下一個Activity等情況下會被調(diào)用汗侵,此時系統(tǒng)幫我們保存一個Bundle類型的數(shù)據(jù),我們可以根據(jù)自己的需求,手動保存一些例如播放進度等數(shù)據(jù)晰韵,而后如果發(fā)生了頁面重啟发乔,我們可以在onRestoreInstanceState()onCreate()里獲取該數(shù)據(jù),從而恢復播放進度等狀態(tài)雪猪。下面是FragmentActivity的相關(guān)源碼


public class FragmentActivity extends ... {
    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...省略代碼
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState();
        ...省略代碼
    }
}

從上可以看出栏尚,F(xiàn)ragmentActivity確實是幫我們保存了Fragment的狀態(tài),并且在頁面重啟后會幫我們恢復只恨。其中的mFragments是FragmentController译仗,它是一個Controller,內(nèi)部通過FragmentHostCallback間接控制FragmentManagerImpl官觅。由于FragmentController是間接控制古劲,沒有詳細保存Fragment狀態(tài)的內(nèi)容,所以我們直接看FragmentManagerImpl中的實現(xiàn)


final class FragmentManagerImpl extends FragmentManager {
    Parcelable saveAllState() {
        ...省略 詳細保存過程
        FragmentManagerState fms = new FragmentManagerState();
        fms.mActive = active;
        fms.mAdded = added;
        fms.mBackStack = backStack;
        return fms;
    }

    void restoreAllState(Parcelable state, List<Fragment> nonConfig) {
        // 恢復核心代碼
        FragmentManagerState fms = (FragmentManagerState)state;
        FragmentState fs = fms.mActive[i];
        if (fs != null) {
            Fragment f = fs.instantiate(mHost, mParent);
        }
    }
}

我們通過saveAllState()看到了關(guān)鍵的保存代碼缰猴,原來是通過FragmentManagerState來保存Fragment的狀態(tài)、所處Fragment棧下標疤剑、回退棧狀態(tài)等滑绒,而在restoreAllState()恢復時,通過FragmentManagerState里的FragmentState的instantiate()方法恢復了Fragment隘膘。我們重點看FragmentState疑故,F(xiàn)ragment的狀態(tài),類名弯菊、下標纵势、id、Tag管钳、ContainerId以及Arguments等數(shù)據(jù)就保存在里面钦铁。Fragment重疊的原因就與這個保存狀態(tài)的機制有關(guān)!

為什么會發(fā)生Fragment重疊才漆?

1.重復replace/add Fragment

我們知道加載Fragment有2種方式:replace()和add()牛曹。當發(fā)生內(nèi)存重啟時,比如屏幕發(fā)生旋轉(zhuǎn)醇滥,Activity會重新啟動黎比,默認Activity中的Fragment也會跟著Activity重新創(chuàng)建,這樣就造成了同一個Fragment會重復加載2次:

  • 通過onSaveInstanceState()保存的Fragment會重新啟動鸳玩;
  • 當執(zhí)行Activity的onCreate()時阅虫,又會再次實例化一個新的Fragment這就是出現(xiàn)重疊的原因。

由以上分析可知不跟,一般情況下颓帝,我們會在Activity的onCreate()里或者Fragment的onCreateView()里加載根Fragment,如果在這里沒有進行頁面重啟的判斷的話,就可能導致重復加載Fragment引起重疊躲履。

2.使用show()或hide()控制Fragment(源碼support -v4 24.0.0以下)

由前面介紹可知见间,發(fā)生內(nèi)存重啟時,F(xiàn)ragment的狀態(tài)會被保存在FragmentState中工猜,但是在源碼support-v4 24.0.0以下米诉,F(xiàn)ragmentState里沒有mHidden字段,默認情況下mHidden = false篷帅。也就是說發(fā)生內(nèi)存重啟時史侣,沒有保存Fragment的顯示狀態(tài),導致頁面銷毀重建后魏身,F(xiàn)ragment就是默認情況下的show狀態(tài)惊橱,F(xiàn)ragment一次性從棧底向棧頂順序恢復時發(fā)生重疊。support-v424.0.0以下的FragmentState類源碼如下箭昵,它實現(xiàn)了Parcelable税朴,保存了Fragment的類名、下標家制、id正林、Tag、ContainerId以及Arguments等數(shù)據(jù)颤殴,但沒有mHidden字段(24.0.0及以上有該字段):

 final class FragmentState implements Parcelable {
    final String mClassName;
    final int mIndex;
    final boolean mFromLayout;
    final int mFragmentId;
    final int mContainerId;
    final String mTag;
    final boolean mRetainInstance;
    final boolean mDetached;
    final Bundle mArguments;
    ...

    //  在FragmentManagerImpl的restoreAllState()里被調(diào)用
    public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
        ...省略
        mInstance = Fragment.instantiate(context, mClassName, mArguments);
    }
}

注:show()觅廓、hide()最終是讓Fragment的ViewsetVisibility(true或false),不會調(diào)用生命周期涵但;當使用add()+show()杈绸,hide()跳轉(zhuǎn)新的Fragment時,舊的Fragment回調(diào)onHiddenChanged()矮瘟,不會回調(diào)onStop()等生命周期方法瞳脓,而新的Fragment在創(chuàng)建時是不會回調(diào)onHiddenChanged()

跟"重疊"輕松Say Goodbye

1.不重復replace/add Fragment
public class MyActivity ... {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        // 這里一定要在save為null時才加載Fragment澈侠,F(xiàn)ragment中onCreateView等生命周里加載根子Fragment同理
        if(saveInstanceState == null){
              // 正常情況下去 加載根Fragment 
        }
    }
}

注:replace情況下篡殷,如果沒有加入回退棧,則不判斷也不會造成重疊埋涧;若加入回退棧板辽,則也會造成重疊現(xiàn)象,建議統(tǒng)一判斷下

2.show()hide() Fragment不重疊(源碼support -v4 24.0.0及以上不用考慮)

從源碼角度的解決方案:從上面分析的原因棘催,我們知道Fragment重疊的根本原因在于FragmentState沒有保存Fragment的顯示狀態(tài)劲弦,即mHidden,那我們就自己手動在Fragment中維護一個mSupportHidden醇坝,在頁面重啟后邑跪,我們自己來決定Fragment是否顯示次坡。只需9行代碼!(摘自:9行代碼解決App內(nèi)的Fragment重疊


public class BaseFragment extends Fragment {
    private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    if (savedInstanceState != null) {
        boolean isSupportHidden = savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN);

        FragmentTransaction ft = getFragmentManager().beginTransaction();
        if (isSupportHidden) {
            ft.hide(this);
        } else {
            ft.show(this);
        }
        ft.commit();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        ...
        outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden());
    }
}

注:在使用show()画畅、hide()對多個Fragment的顯示進行控制時砸琅,在不同場景下如何選擇,用findFragmentByTag()還是用getFragments()恢復Fragment時(同時防止Fragment重疊)轴踱,詳細分析見Fragment全解析系列(二):正確的使用姿勢症脂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市淫僻,隨后出現(xiàn)的幾起案子诱篷,更是在濱河造成了極大的恐慌,老刑警劉巖雳灵,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棕所,死亡現(xiàn)場離奇詭異,居然都是意外死亡悯辙,警方通過查閱死者的電腦和手機琳省,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躲撰,“玉大人岛啸,你說我怎么就攤上這事≤罘剩” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵荡灾,是天一觀的道長瓤狐。 經(jīng)常有香客問我,道長批幌,這世上最難降的妖魔是什么础锐? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮荧缘,結(jié)果婚禮上皆警,老公的妹妹穿的比我還像新娘。我一直安慰自己截粗,他們只是感情好信姓,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绸罗,像睡著了一般意推。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上珊蟀,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天菊值,我揣著相機與錄音,去河邊找鬼。 笑死腻窒,一個胖子當著我的面吹牛昵宇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播儿子,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼瓦哎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了典徊?” 一聲冷哼從身側(cè)響起杭煎,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卒落,沒想到半個月后羡铲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡儡毕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年也切,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腰湾。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡雷恃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出费坊,到底是詐尸還是另有隱情倒槐,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布附井,位于F島的核電站讨越,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏永毅。R本人自食惡果不足惜把跨,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沼死。 院中可真熱鬧着逐,春花似錦、人聲如沸意蛀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽县钥。三九已至太雨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間魁蒜,已是汗流浹背囊扳。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工吩翻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锥咸。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓狭瞎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搏予。 傳聞我的和親對象是個殘疾皇子熊锭,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

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