相信小伙伴們會遇到過Fragment重疊的問題,不要慌
這里先針對需要救火的小伙伴給出解決方案,如果想知道原理,可以繼續(xù)看下面的解釋和源碼分析。
解決方案:
在Fragment所在的Activity中,重寫onSaveInstanceState方法,并添加以下兩句話:
outState.putParcelable("android:support:fragments", null);
outState.putParcelable("android:fragments", null);
實際上一句就可以了,具體要看你使用的是什么包下的Fragment。
解釋:
這行代碼的含義很簡單,就是activity執(zhí)行onSaveInstanceState方法時清空里面已有的fragment變量,當新的fragment創(chuàng)建時,activity就不會存在新舊兩套fragment,避免了產(chǎn)生Fragment重疊的現(xiàn)象。
那什么情況onSaveInstanceState會被調(diào)用呢?
有以下5種情況被調(diào)用:
1、按下home鍵的時候。因為按下home鍵后,系統(tǒng)不知道用戶還要進行哪些操作,如果操作過多。應用很有可能被殺死。
2、長按home鍵或者菜單鍵(切換到其它應用)。
3、手機息屏時。
4、A Activity啟動B Activity,A Activity就會調(diào)用,也就是說打開新Activity時,原Activity就會調(diào)用。
5、橫豎屏切換時。
一句話總結(jié)就是當系統(tǒng)不知道這個activity(應用)還會使用多久,面臨著被殺死回收的風險時就會調(diào)用這個方法,其設(shè)計目的是在應用可能被銷毀時(非用戶主動銷毀,如back),提供用戶進行數(shù)據(jù)的保存操作,這里就包括已添加過的fragment信息。
注意:onSaveInstanceState本身只是保存一些UI控件的狀態(tài)數(shù)據(jù)(視圖層),不適合做關(guān)鍵數(shù)據(jù)和持久化數(shù)據(jù)的保存工作。
為了模擬應用被回收重建的現(xiàn)象,有兩個辦法:
1.開發(fā)者選項-不保留活動:運行app,按home鍵退到桌面(回收),再點擊app icon進入(重建)
2.旋轉(zhuǎn)屏幕:前提是清單文件中沒有設(shè)置 android:configChanges="keyboardHidden|orientation|screenSize"
源碼分析:
現(xiàn)象重現(xiàn)了,大概原理也知道了,現(xiàn)在我們就要從源碼入手,探究為什么要這樣重寫onSaveInstanceState方法。
我們將分析源碼分為兩大部分
第一部分,先看看onSaveInstanceState方法里做了什么
我有個習慣,看方法或者看類時,先看注釋,一般注釋里會解釋此方法的作用及參數(shù),可以幫我們更好的理解
這里的注釋較多,我截取兩段比較重要的:
?????*This method is called before an activity may be killed so that when it
?????* comes back some time in the future it can restore its state. ?
該方法在活動可能被殺死之前被調(diào)用,以便當它被殺死時在未來的某個時間回來,可以恢復它的狀態(tài)。
? ? ?* The default implementation takes care of most of the UI per-instance
?????* state for you by calling {@link android.view.View#onSaveInstanceState()} on each
?????* view in the hierarchy that has an id
默認實現(xiàn)會為你處理大部分UI每個實例的狀態(tài),在每個有id的view視圖上
通過注釋可以了解到onSaveInstanceState主要是用來在應用被殺死時保存視圖的狀態(tài)。
Step1. Activity 的?onSaveInstanceState(Bundle outState)方法

看方法里的實現(xiàn),可以看到紅框處,fragments.saveallState() 賦給 Parcelable 類型的變量p,p不為null時,把它當做value存放到outState中,其key為 FRAGMENTS_TAG,查看其定義:
static final String FRAGMENTS_TAG = "android:fragments";(重點)
這里先記住這個 FRAGMENTS_TAG ,后面會用到。
我們來看下,saveAllState里做了什么,點擊saveAllState方法,跳轉(zhuǎn)到了FragmentcController類的 saveAllState方法里。
Step2.FragmentcController的saveAllState()

這里可以看到是調(diào)用FragmentManager的saveAllState方法,跳轉(zhuǎn)到FragmentManager來查看。
Step3.FragmentManager的saveAllState()方法

中間部分省略。。。

1:新建一個FragmentState類型的數(shù)組active
2:從成員變量mActive里取出Fragment, mActive的聲明為:ArrayList<Fragment> mActive;
里面存放的是當前活動的Fragment列表。
3:把fragment包裝成FragmentState類型的對象存放到active數(shù)組中
4.如果沒有fragment,則返回null。回看Step1中,當p等于null時,則不會保存
5.最終,new 一個FragmentManagerState,把active數(shù)組賦給其成員變量mActive,并返回。
也就是說step1里要保存的p實際上就是這個FragmentManagerState。那么我們再看下FragmentManagerState里面有什么。
Step4.FragmentManager的內(nèi)部類FragmentManagerState
FragmentManagerState是FragmentManager中的內(nèi)部類,其聲明如下
final class FragmentManagerState implements Parcelable,可以看出其目的為序列化的數(shù)據(jù)存儲

FragmentManagerState 里很簡單,主要是幾個數(shù)組型的成員變量,這里我們主要來看FragmentState類型的數(shù)組mActive, 進入到FragmentState里。
Step5.Fragment的內(nèi)部類FragmentState
FragmentState是Fragment的內(nèi)部類,聲明了如下的屬性

可以看到這里記錄了fragment的一些信息,并且還持有fragment的引用。在其構(gòu)造方法中,可以看到把fragment同名屬性的值賦值了過來,也就是說我們在Fragment類里面也能找到一一對應的屬性,并且都有相關(guān)的注釋說明:

具體的含義大家可以自行翻譯,我就不做過多的介紹了??傊趕tep1中保存的p底層就是這些信息。
到這里,activity的onSaveInstanceState方法我們就大概清楚了,功能之一就是把fragment的一些狀態(tài)進行保存。
接著,就是第二部分了,我們再看看activity創(chuàng)建的過程
Step6.Activity的onCreate(Bundle saveInstanceState)方法
直接進入到activity的onCreate方法

1:從bundle中取出名為FRAGMENT_TAG的p對象,沒錯就是在step1中的存入的那個key
2:從restoreAllState方法名就可以看出,恢復fragment所有狀態(tài)
3:進入創(chuàng)建fragment流程
來看restoreAllState方法,mFragments是FragmentController類型的,進入。
Step7.FragmentController的restoreAllState(Parcelable state, List<Fragment> nonConfigList)方法

這里又調(diào)用了FragmentManager的restoreAllState的方法。
Step8.FragmentManager的restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig)

紅框處拿到了之前保存的P,并獲取到里面的數(shù)組fms.mActive進行遍歷
注意紫紅色的注釋:
Build the full list of active fragments, instantiating them from their saved state
構(gòu)建活動Fragment的完整列表,從它們保存的狀態(tài)實例化它們。
通過這句話進行實例化:Fragment f = fs.instantiate(mHost,mParent,childNonConfig)。
實例化后的fragment加到FragmentManagerImpl(FragmentManager的內(nèi)部類)的成員變量ArrayList<Fragment> mActive中
所以,在restoreAllState方法中,主要是把保存的fragment實例化。
接著,我們看Step6的第3步,mFragments.dispatchCreate()方法,這里最終是調(diào)用FragmentManager的dispatchCreate方法

注意看,第一個參數(shù)傳入了Fragment.CREATED常量(Fragment一共定義了5個,這里的常量int值在后面會用來做各種狀態(tài)的大小判斷)

很明顯這里代表創(chuàng)建一個新的fragment。第二個參數(shù)為false。
再進入moveToState方法
Step9.FragmentManager.moveToState(...)

可以看到這個方法主要是做各種判斷,根據(jù)fragment的狀態(tài)來做下一步的處理(代碼略長,我做了折疊處理),紅框處將p里的fragment的state和newState值相比,newState就是之前第一個參數(shù)的CREATE。
我們在其中的一個分支看到 如下代碼

這就是我們熟悉的將fragment view放到container中的流程了。
到這里,第二步Activity的onCreate流程就可以告一段落了,我們可以發(fā)現(xiàn),onCreate里就會重建fragment,那本身程序里還有新建fragment的流程,這樣相當于fragment創(chuàng)建了兩次,當然就會重疊了?,F(xiàn)在再回過頭來看看我們的解決方案:
重寫onSaveInstanceState方法,并添加以下代碼:outState.putParcelable("android:fragments", null);
相當于bundle 的FRAGMENTS_TAG 值為空,在step8 restoreAllState方法中開頭直接就return了,就沒有接下來一系列的取出、實例化、創(chuàng)建等操作了,應用重新創(chuàng)建后,只有一套fragment,自然不會出現(xiàn)重疊現(xiàn)象。
看的累了吧,快緩緩(文中描述的也不一定100%正確,但大致流程應該是沒問題的)
總結(jié):
正常back鍵退出應用時(主動銷毀),Activity及Fragment對象都會被銷毀,因此再次進入時會創(chuàng)建新的Fragment對象。但是當非主動銷毀(退到后臺被回收等),Activity雖然被回收,但Fragment對象仍然保持,再次進入應用時,系統(tǒng)會恢復之前保存的Fragment,加上原有的fragment,就造成了重疊現(xiàn)象。
當然,使用一些其它的方法也是可以解決重疊問題的,比如判斷新的fragment和舊的fragment是否是同一個,如果不是那么就將舊的賦值給新的fragment。這里還要看實際的業(yè)務(wù),,原理清楚了,解決辦法就多了。