源碼分析Fragment的BackStack管理過程

1. Fragment基本用法

為了管理Activity中的fragments,需要調(diào)用Activity中的getFragmentManager()方法们豌。因?yàn)镕ragmentManager的API是在Android 3.0,也即API level 11開始引入的浅妆,所以對于之前的版本,需要使用support library v4中的FragmentActivity,并且使用getSupportFragmentManager()方法意荤。

用FragmentManager可以做的工作有:

得到Activity中存在的fragment:

使用findFragmentById()或findFragmentByTag()方法烟馅。

將fragment彈出back stack:

popBackStack():

將back stack中最后一次的fragment轉(zhuǎn)換彈出。如果沒有可以出棧的東西康辑,返回false摄欲。

這個(gè)函數(shù)是異步的:它將彈出棧的請求加入隊(duì)列,但是這個(gè)動(dòng)作直到應(yīng)用回到事件循環(huán)才會(huì)執(zhí)行疮薇。

為back stack加上監(jiān)聽器:

addOnBackStackChangedListener()

使用Fragment時(shí)胸墙,可以執(zhí)行一些動(dòng)作,比如增加按咒、移除迟隅、替換等。所有這些改變構(gòu)成一個(gè)集合励七,這個(gè)集合被叫做一個(gè)transaction智袭。

可以調(diào)用FragmentTransaction中的方法來處理這個(gè)transaction.

以這樣得到FragmentTransaction類的實(shí)例:

每個(gè)transaction是一組同時(shí)執(zhí)行的變化的集合。用add(), remove(), replace()方法掠抬,把所有需要的變化加進(jìn)去吼野,然后調(diào)用commit()方法,將這些變化應(yīng)用两波。在commit()方法之前瞳步,你可以調(diào)用addToBackStack()闷哆,把這個(gè)transaction加入back stack中去,這個(gè)back stack是由activity管理的谚攒,當(dāng)用戶按返回鍵時(shí)阳准,就會(huì)回到上一個(gè)fragment的狀態(tài)。下面的代碼非常典型馏臭,用一個(gè)新的fragment取代之前的fragment野蝇,并且將之前的狀態(tài)存儲(chǔ)在back stack中。

通過調(diào)用addToBackStack()括儒,commit()的一系列轉(zhuǎn)換作為一個(gè)transaction被存儲(chǔ)在back stack中绕沈,用戶按Back鍵可以返回上一個(gè)轉(zhuǎn)換前的狀態(tài)。

調(diào)用commit()方法并不能立即執(zhí)行transaction中包含的改變動(dòng)作帮寻,commit()方法把transaction加入activity的UI線程隊(duì)列中乍狐。

下面我們對上述代碼中出現(xiàn)的函數(shù)進(jìn)行分析,以此來逐步學(xué)習(xí)Fragment的管理機(jī)制固逗。

getSupportFragmentManager():

該函數(shù)返回類型是FragmentManager浅蚪,F(xiàn)ragmentManager是一個(gè)抽象類,其實(shí)現(xiàn)類是FragmentManager.FragmentManagerImpl

beginTransaction():

該函數(shù)在FragmentManagerIMpl中的源碼如下:

返回一個(gè)BackStackRecord對象烫罩,該對象是FragmentTranscation的一個(gè)子類惜傲。

BackStackRecord的聲明如下:

該類實(shí)現(xiàn)了一個(gè)重要的接口:FragmentManager.BackStackEntry, 該接口代表了fragment back stack的一個(gè)入口”丛埽可以用FragmentManager.getBackStackEntry()來檢索BackStackEntry盗誊。

接下來執(zhí)行transaction.replace(), 查看BackStackRecord,調(diào)用過程源碼如下:

我們發(fā)現(xiàn)隘弊,replace()最終調(diào)用的函數(shù)為doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd), 將Fragment和對Fragment所進(jìn)行的操作放到op鏈表中:

該函數(shù)首先設(shè)置fragment的mFragmentManager屬性哈踱,然后再設(shè)置其mContainerId和mFragmentId,最后創(chuàng)建Op對象梨熙,然設(shè)置相應(yīng)自段开镣,其中cmd自動(dòng)用來標(biāo)識事務(wù)的類型,分為如下幾類:

static final int OP_NULL = 0;

static final int OP_ADD = 1;

static final int OP_REPLACE = 2;

static final int OP_REMOVE = 3;

static final int OP_HIDE = 4;

static final int OP_SHOW = 5;

static final int OP_DETACH = 6;

static final int OP_ATTACH = 7;

每個(gè)字段的意思可直接通過英文名稱獲知咽扇。Op()類是BackStackRecord中聲明的結(jié)構(gòu)體哑子,本質(zhì)上是一個(gè)雙向鏈表的Node。addOp()如下:

該函數(shù)將Op對象添加到鏈表的末尾肌割,并將mNumOp的值增一卧蜓。

transaction.addToBackStack(null)設(shè)置了mAddToBackStack為true,源碼如下:

此函數(shù)將mAddToBackStack自段設(shè)置為true,并設(shè)置mName字段把敞。

最后調(diào)用transaction.commit()來執(zhí)行transaction弥奸。commit()的調(diào)用過程代碼如下:

由于mAddToBackStack為true,所以會(huì)用FragmentManager為BackstackRecorder也即FragmentTransaction分配一個(gè)index奋早,分配過程如下:

FragmentManager用mAvailBackStackIndices和mBackStackIndices兩個(gè)數(shù)組來為BackStackRecord分配Index盛霎。mAvailBackStackIndices用來存儲(chǔ)在mBackStackIndices中能夠分配的Index赠橙,mBackStackIndices則用來保存BackStackRecord。這利用兩個(gè)數(shù)組可以減少對mBackStackIndices的動(dòng)態(tài)分配大小的次數(shù)愤炸,是一個(gè)以空間換時(shí)間的策略期揪。上面的代碼首先判斷是否有可用的Index分配給BackStackRecord,若無則直接將BackStackRecord插入到mBackStackIndices;若存在的話則從mAvailBackStackIndices的隊(duì)尾取出一個(gè)index规个,然后設(shè)置mBackStackIndices中該index下的值凤薛。

讓我們回到commit()中,該函數(shù)最后執(zhí)行mManager.enqueAction(),源碼如下:

該函數(shù)首先進(jìn)行狀態(tài)監(jiān)測诞仓,查看該Fagment所在的Activity的生命周期是否處于Saving Activity之前缤苫,因?yàn)锳ctivity保存狀態(tài)往往是由用戶離開那個(gè)Activity所造成的,在此之后執(zhí)行commit會(huì)丟失一些狀態(tài)信息墅拭。針對這種情況活玲,可以使用commitAllowingStateLoss().最后將BackStackRecord加入到執(zhí)行隊(duì)列中。當(dāng)?shù)谝淮瓮鶊?zhí)行

隊(duì)列中添加消息時(shí)谍婉,首先會(huì)從消息隊(duì)列中所有callback屬性為mExecCommit的消息刪除舒憾,然后重新將mExecCommit添加到消息隊(duì)列。mExecCommit的定義如下:

execPendingActions()只能在主線程內(nèi)被調(diào)用穗熬,其內(nèi)部通過一個(gè)循環(huán)對mPendingActions中的Actions進(jìn)行執(zhí)行镀迂。值得注意的是,每執(zhí)行一次循環(huán)死陆,mPendingActions中的所有Action都會(huì)被添加到一個(gè)臨時(shí)數(shù)組中,然后這個(gè)數(shù)組被變量一遍以執(zhí)行數(shù)組中的每個(gè)Runnable唧瘾。同時(shí)措译,每個(gè)Runnable直接被調(diào)用了run,而不是開個(gè)線程執(zhí)行的饰序。當(dāng)這個(gè)Runnable在執(zhí)行的時(shí)候领虹,mPendingActions數(shù)組可能會(huì)被添加內(nèi)容。當(dāng)某一時(shí)刻mPendingActions中的內(nèi)容為空求豫,則while循環(huán)退出塌衰。此部分代碼如下:

由于BackstackRecorder實(shí)現(xiàn)了Runnable,我們來看看BackStackRecorder中的run(),如下所示:



addBackStackState()的源碼如下:


可以看到傳說中的BackStack就是在這里被創(chuàng)建的, FragmentManager中的BackStack主要是用來存儲(chǔ)FragmentTransaction的蝠嘉。

小結(jié):

FragmentTransaction中的Op鏈用來保存add最疆、remove、replace等action蚤告,在FragmentTransaction的run執(zhí)行時(shí)努酸,Op鏈會(huì)被變量以調(diào)整每個(gè)節(jié)點(diǎn)的內(nèi)容。

FragmentManager使用一個(gè)BackStack來管理FragmentTransaction杜恰;使用mAdded數(shù)組來添加被add的Fragment获诈,F(xiàn)ragment的創(chuàng)建仍源、顯示等行為都受FragmentManager的控制。

FragmentManager中的moveToState()是一個(gè)非常重要的函數(shù)舔涎,在FragmentTransaction run的時(shí)候被調(diào)用笼踩。下次我們將深入這個(gè)函數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亡嫌,一起剝皮案震驚了整個(gè)濱河市嚎于,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昼伴,老刑警劉巖匾旭,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異圃郊,居然都是意外死亡价涝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門持舆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來色瘩,“玉大人,你說我怎么就攤上這事逸寓【诱祝” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵竹伸,是天一觀的道長泥栖。 經(jīng)常有香客問我,道長勋篓,這世上最難降的妖魔是什么吧享? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮譬嚣,結(jié)果婚禮上钢颂,老公的妹妹穿的比我還像新娘。我一直安慰自己拜银,他們只是感情好殊鞭,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尼桶,像睡著了一般操灿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泵督,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天牲尺,我揣著相機(jī)與錄音,去河邊找鬼。 笑死谤碳,一個(gè)胖子當(dāng)著我的面吹牛溃卡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蜒简,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼瘸羡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了搓茬?” 一聲冷哼從身側(cè)響起犹赖,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卷仑,沒想到半個(gè)月后峻村,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锡凝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年粘昨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窜锯。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡张肾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锚扎,到底是詐尸還是另有隱情吞瞪,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布驾孔,位于F島的核電站芍秆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏翠勉。R本人自食惡果不足惜妖啥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眉菱。 院中可真熱鬧迹栓,春花似錦掉分、人聲如沸俭缓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽华坦。三九已至,卻和暖如春不从,著一層夾襖步出監(jiān)牢的瞬間惜姐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留歹袁,地道東北人坷衍。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像条舔,于是被迫代替她去往敵國和親枫耳。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

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