Fragmentation框架堆棧跳轉(zhuǎn)和回退解析

一、Fragmentation是什么?


Fragmentation是一款以解決多Activity+多Fragment或單Activity+多Fragment復(fù)雜嵌套問(wèn)題岔激,快速開(kāi)發(fā)出基于Fragment的app的框架让歼。包括狀態(tài)保存恢復(fù)、生命周期監(jiān)聽(tīng)复凳、轉(zhuǎn)場(chǎng)動(dòng)畫(huà)批销、啟動(dòng)模式洒闸、滑動(dòng)返回、懶加載以及startForResult等功能均芽。

以下是原文介紹:

為"單Activity + 多Fragment的架構(gòu)","多模塊Activity + 多Fragment的架構(gòu)"而生丘逸,幫你簡(jiǎn)化使用過(guò)程,輕松解決各種復(fù)雜嵌套等問(wèn)題掀宋,修復(fù)了官方Fragment庫(kù)存在的一些BUG深纲。

原文地址:

Fragment之我的解決方案:Fragmentation - 簡(jiǎn)書(shū)

本文將著重介紹其中Fragment啟動(dòng)模式功能劲妙、startForResult湃鹊、懶加載以及回退功能。

之所以介紹該框架镣奋,主要在實(shí)際工作中項(xiàng)目的架構(gòu)也是采用類(lèi)似Fragment自定義View構(gòu)成的UI框架形式如單Activity+多Fragment币呵,只是這里Fragment是自定義View,其中堆棧跳轉(zhuǎn)有值得優(yōu)化的部分侨颈,而Fragmentation給了我一些啟發(fā)余赢,所以本文重點(diǎn)介紹上述功能。

二哈垢、啟動(dòng)模式妻柒。

Activity啟動(dòng)模式分為四種:

1.standard:

每次啟動(dòng)都會(huì)創(chuàng)建一個(gè)新的實(shí)例,無(wú)論這個(gè)實(shí)例是否已經(jīng)存在耘分,在這種模式下誰(shuí)啟動(dòng)了Activity A,那這個(gè)A就會(huì)運(yùn)行在啟動(dòng)它的那個(gè)Activity所在的任務(wù)棧里举塔。(圖片引用:https://www.cnblogs.com/claireyuancy/p/7387696.html)


standard

2.singleTop

和standard模式類(lèi)似,不會(huì)創(chuàng)建新的task求泰,都是在原任務(wù)棧中創(chuàng)建央渣,也就是該Activity實(shí)例在當(dāng)前棧頂,那么不會(huì)創(chuàng)建新的實(shí)例渴频,并調(diào)用onNewIntent方法痹屹。但是如果存在該實(shí)例卻不在棧頂或不存在該實(shí)例都會(huì)新建一個(gè)新的實(shí)例出來(lái)。


singleTop

3.singleTask

singleTask相對(duì)比較復(fù)雜枉氮,先介紹最簡(jiǎn)單的情形:

在任務(wù)棧T3里志衍,存在ADBC三個(gè)Activity,這個(gè)時(shí)候如果啟動(dòng)D聊替,且所需任務(wù)棧是T3楼肪,根據(jù)棧內(nèi)復(fù)用原則D不會(huì)重新創(chuàng)建,而是將D之上的Activity彈出棧惹悄,將D置為棧頂春叫,表示為AD,B,C被彈出。(圖片來(lái)源:http://www.reibang.com/p/2a9fcf3c11e4)


再比如如果啟動(dòng)D暂殖,且所需堆棧是T2价匠,由于D和T2都不存在,系統(tǒng)會(huì)創(chuàng)建T2和D呛每,并將D壓入T2.


還有一種情況啟動(dòng)D且所需堆棧T1踩窖,由于D不存在則創(chuàng)建新的實(shí)例并壓入T1.


相對(duì)復(fù)雜的情形:

前后臺(tái)任務(wù)棧,前臺(tái)任務(wù)棧包含AB兩個(gè)activity實(shí)例晨横,后臺(tái)任務(wù)棧包含CD兩個(gè)activity洋腮,如果這個(gè)時(shí)候前臺(tái)任務(wù)棧啟動(dòng)D那么整個(gè)后臺(tái)任務(wù)棧都會(huì)放到前臺(tái),前臺(tái)的堆棧順序表示為ABCD手形,D在頂層啥供。



如果啟動(dòng)的是C,那么前臺(tái)堆棧順序?yàn)锳BC库糠,D被直接出棧了伙狐。

最后一種情況,A,B兩個(gè)應(yīng)用瞬欧,C是B的一個(gè)Activity贷屎,且C的allowtaskReparening屬性為true,那么當(dāng)A應(yīng)用啟動(dòng)B應(yīng)用的C時(shí)黍判,就會(huì)將C放在B的任務(wù)棧頂層,再描述的清楚一些就是篙梢,啟動(dòng)C以后按home鍵回到桌面顷帖,再點(diǎn)擊B應(yīng)用圖標(biāo),這個(gè)時(shí)候展示的不是B應(yīng)用的主Activity而是C渤滞。


4singleinstance贬墩。

就是加強(qiáng)版singleTask模式,使用該模式就是啟動(dòng)一個(gè)新的任務(wù)棧妄呕,且這個(gè)任務(wù)棧里只能存在一個(gè)Activity實(shí)例陶舞。

那么了解了Activity的啟動(dòng)模式以后我們?cè)賮?lái)看Fragmentation是如何實(shí)現(xiàn)Fragment的啟動(dòng)模式的。

Fragmentation只實(shí)現(xiàn)了singleTop和singleTask绪励,我們來(lái)關(guān)注下他是如何實(shí)現(xiàn)的肿孵。


FragmentManager管理堆棧原理

2.1Fragmentation singleTop模式

@Override

public void start(finalSupportFragment toFragment,@LaunchModefinal int launchMode) {

mFragmentationDelegate.dispatchStartTransaction(getFragmentManager(), this,toFragment,0,launchMode,FragmentationDelegate.TYPE_ADD);

}

首先在supportFragment(備注:基礎(chǔ)庫(kù)中的需要被繼承實(shí)現(xiàn)的基類(lèi))中實(shí)現(xiàn)了start方法用于啟動(dòng)新的fragment,內(nèi)部實(shí)現(xiàn)是由FragmentationDelegate代理實(shí)現(xiàn),進(jìn)一步看:

if(handleLaunchMode(fragmentManager,to,toFragmentTag,launchMode))return;

在dispatchStartTransaction方法中有這么一段代碼疏魏,用于處理啟動(dòng)模式的停做,我們?cè)龠M(jìn)一步進(jìn)去看

SupportFragment topFragment = getTopFragment(fragmentManager);//找到棧頂Fragment

Fragment stackToFragment = findStackFragment(toFragment.getClass(),toFragmentTag,fragmentManager);//再?gòu)亩褩V姓业絫oFragment

if(launchMode == SupportFragment.SINGLETOP) {

// 在棧頂

if(toFragment == topFragment || toFragment.getClass().getName().equals(topFragment.getClass().getName())) {

handleNewBundle(toFragment,stackToFragment);

return true;

}

}

可以看到當(dāng)從堆棧中找到所要跳轉(zhuǎn)的toFragment以后且toFragment也是TopFragment那么就會(huì)開(kāi)始處理newBundle,并在handleNewBundle中使用stackToFragment調(diào)用onNewBundle方法

private void handleNewBundle(SupportFragment toFragment,Fragment stackToFragment) {

Bundle argsNewBundle = toFragment.getNewBundle();

Bundle args = toFragment.getArguments();

if(args.containsKey(FRAGMENTATION_ARG_CONTAINER)) {

args.remove(FRAGMENTATION_ARG_CONTAINER);

}

if(argsNewBundle !=null) {

args.putAll(argsNewBundle);

}

((SupportFragment) stackToFragment).onNewBundle(args);

}

2.2 Fragmentation singTask模式


在handleLanuchMode方法執(zhí)行以前都一樣大莫,我們來(lái)看該方法完整部分

private boolean handleLaunchMode(FragmentManager fragmentManager,SupportFragment toFragment,String toFragmentTag, intlaunchMode) {

SupportFragment topFragment = getTopFragment(fragmentManager);

if(topFragment ==null)return false;

Fragment stackToFragment = findStackFragment(toFragment.getClass(),toFragmentTag,fragmentManager);

if(stackToFragment ==null)return false;

if(launchMode == SupportFragment.SINGLETOP) {

// 在棧頂

if(toFragment == topFragment || toFragment.getClass().getName().equals(topFragment.getClass().getName())) {

handleNewBundle(toFragment,stackToFragment);

return true;

}

}else if(launchMode == SupportFragment.SINGLETASK) {

popToFix(toFragmentTag,0,fragmentManager);//這里是singleTask方法執(zhí)行關(guān)鍵

handleNewBundle(toFragment,stackToFragment);

return true;

}

return false;

}

它與singleTop不同的地方在于會(huì)執(zhí)行popstack操作蛉腌。具體我們來(lái)看popToFix方法。

private void popToFix(String fragmentTag, intflag, finalFragmentManager fragmentManager) {

if(fragmentManager.getFragments() ==null)return;

mActivity.preparePopMultiple();

fragmentManager.popBackStackImmediate(fragmentTag,flag);//執(zhí)行回退棧立即執(zhí)行

mActivity.popFinish();

mHandler.post(newRunnable() {

@Override

public voidrun() {

FragmentTransactionBugFixHack.reorderIndices(fragmentManager);

}

});

}


當(dāng)執(zhí)行完pop操作以后,后續(xù)的handleNewBundle方法操作一致烙丛。


2.3 Fragmentation startForResult

使用startForResult啟動(dòng)Fragment時(shí)會(huì)在dispatchStartTransaction存儲(chǔ)請(qǐng)求requestcode舅巷,用于回調(diào)

/**

* save requestCode

*/

private void saveRequestCode(Fragment to,? int requestCode) {

Bundle bundle = to.getArguments();

if(bundle ==null) {

bundle =newBundle();

to.setArguments(bundle);

}

ResultRecord resultRecord =newResultRecord();

resultRecord.requestCode= requestCode;

bundle.putParcelable(FRAGMENTATION_ARG_RESULT_RECORD,resultRecord);

}

啟動(dòng)后在需要返回的響應(yīng)碼的fragment里存儲(chǔ)返回?cái)?shù)據(jù)并返回resultcode,舉個(gè)例子:

mBtnModify.setOnClickListener(newView.OnClickListener() {

@Override

public voidonClick(View v) {

Bundle bundle =newBundle();

bundle.putString(DetailFragment.KEY_RESULT_TITLE,mEtModiyTitle.getText().toString());

setFragmentResult(RESULT_OK,bundle);

Toast.makeText(_mActivity,"修改成功!",Toast.LENGTH_SHORT).show();

}

});

當(dāng)該Fragment銷(xiāo)毀時(shí)河咽,在supportFragment的onDestroy方法中會(huì)處理返回結(jié)果數(shù)據(jù),當(dāng)然還是由代理類(lèi)FragmentationDelegate處理

void handleResultRecord(Fragment from) {

SupportFragment preFragment = getPreFragment(from);

if(preFragment ==null)return;

Bundle args = from.getArguments();

if(args ==null|| !args.containsKey(FRAGMENTATION_ARG_RESULT_RECORD))return;

ResultRecord resultRecord = args.getParcelable(FRAGMENTATION_ARG_RESULT_RECORD);

if(resultRecord ==null)return;

preFragment.onFragmentResult(resultRecord.requestCode,resultRecord.resultCode,resultRecord.resultBundle);

}

通過(guò)當(dāng)前的fragment找到前一個(gè)fragment也就是啟動(dòng)它的fragment钠右,注意startForResult使用的是standard模式,然后調(diào)用preFragmentResult的onFragmentResult方法將序列化的bundle數(shù)據(jù)傳回操作即可库北。


2.4Fragmentation懶加載

supportFragment的onLazyInitView方法主要用于數(shù)據(jù)的懶加載爬舰,view的初始化還是在createView時(shí)候完成。之所以采用懶加載是因?yàn)楫?dāng)類(lèi)似微信這種多tab切換的fragment每個(gè)頁(yè)面都有可能請(qǐng)求大量數(shù)據(jù)寒瓦,而用戶沒(méi)有點(diǎn)擊顯示卻要耗費(fèi)資源去請(qǐng)求情屹,用戶體驗(yàn)會(huì)比較差。

onLazyInitView在當(dāng)前view可視的時(shí)候開(kāi)始加載杂腰。具體來(lái)看源碼:

private void dispatchSupportVisible(boolean visible) {

.....//不重要代碼

if(visible) {

mSupportFragment.onSupportVisible();

if(mIsFirstVisible) {

mIsFirstVisible=false;

mSupportFragment.onLazyInitView(mSaveInstanceState);

}

}

.....//不重要代碼

}

上面的方法是VisibleDelegate中分發(fā)可視事件的方法垃你,其中當(dāng)?shù)谝淮慰梢晻r(shí)會(huì)回調(diào)懶加載,接下來(lái)喂很,

而VisibleDelegate會(huì)代理所有的生命周期方法惜颇,在相應(yīng)的生命周期代理中會(huì)處理分發(fā)相應(yīng)的可視性,最終調(diào)用dispatchSupportVisible方法少辣。

隨便舉個(gè)例子:

public void onResume() {

if(!mIsFirstVisible) {

if(!mIsSupportVisible&& !mInvisibleWhenLeave&& isFragmentVisible(mSupportFragment)) {

mNeedDispatch=false;

dispatchSupportVisible(true);

}

}

}

VisibleDelegate的onResume方法里對(duì)dispatchSupportVisible方法的調(diào)用凌摄,而onResume又會(huì)在supportFragment的onResume方法中被調(diào)用。

2.5FragmentManager對(duì)回退棧管理漓帅。

FragmentManager在新的Fragment創(chuàng)建時(shí)會(huì)分配給一個(gè)Fragment下標(biāo)锨亏,同時(shí)由mActive管理當(dāng)前所有激活狀態(tài)的Fragment。不過(guò)回退時(shí)mActive是對(duì)相應(yīng)index置空而不是remove忙干,需要注意的是

void makeInactive(Fragment f) {

????if (f.mIndex < 0) { return; }

????if (DEBUG) Log.v(TAG, "Freeing fragment index " + f);

????mActive.set(f.mIndex, null);

????if (mAvailIndices == null) {

????????mAvailIndices = new ArrayList();

????}

????mAvailIndices.add(f.mIndex);

????mHost.inactivateFragment(f.mWho);

????f.initState();

}

添加新的fragment源碼

void makeActive(Fragment f) {

????if (f.mIndex >= 0) { return; }

????if (mAvailIndices == null || mAvailIndices.size() <= 0) {

????????if (mActive == null) { mActive = new ArrayList();

????}

????f.setIndex(mActive.size(), mParent); mActive.add(f);

????} else {

????????f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);

????????mActive.set(f.mIndex, f);

????}

????if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);

}

需要注意的是mAvailIndices在pop多個(gè)Fragment時(shí)候器予,有可能會(huì)出現(xiàn)如下情況:

比如:正常堆棧順序?yàn)锳,B,C,D,現(xiàn)在Pop出C和D捐迫,堆棧變更為A,B,null,null乾翔,這個(gè)時(shí)候如果push新的Fragment,有可能會(huì)出現(xiàn)A,B,null,C的情況施戴,原因是由于mAvailIndices在記錄popFragment的時(shí)候反浓,有可能先保存的較小的index后再保存較大index,也就是mAvailIndices里的Fragment的index順序無(wú)法保證赞哗,這個(gè)時(shí)候需要做一次降序排列以解決問(wèn)題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末勾习,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子懈玻,更是在濱河造成了極大的恐慌巧婶,老刑警劉巖乾颁,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異艺栈,居然都是意外死亡英岭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)湿右,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)诅妹,“玉大人,你說(shuō)我怎么就攤上這事毅人】越疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵丈莺,是天一觀的道長(zhǎng)划煮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)缔俄,這世上最難降的妖魔是什么弛秋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮俐载,結(jié)果婚禮上蟹略,老公的妹妹穿的比我還像新娘。我一直安慰自己遏佣,他們只是感情好挖炬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著状婶,像睡著了一般意敛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上太抓,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天空闲,我揣著相機(jī)與錄音令杈,去河邊找鬼走敌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逗噩,可吹牛的內(nèi)容都是我干的掉丽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼异雁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼捶障!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起纲刀,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤项炼,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體锭部,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暂论,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拌禾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片取胎。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖湃窍,靈堂內(nèi)的尸體忽然破棺而出闻蛀,到底是詐尸還是另有隱情,我是刑警寧澤您市,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布觉痛,位于F島的核電站,受9級(jí)特大地震影響墨坚,放射性物質(zhì)發(fā)生泄漏秧饮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一泽篮、第九天 我趴在偏房一處隱蔽的房頂上張望盗尸。 院中可真熱鬧,春花似錦帽撑、人聲如沸泼各。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扣蜻。三九已至,卻和暖如春及塘,著一層夾襖步出監(jiān)牢的瞬間莽使,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工笙僚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芳肌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓肋层,卻偏偏與公主長(zhǎng)得像亿笤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栋猖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354