FragmentManager實際上是用鏈表來管理Fragment的
之前一直有一個誤解,認為FragmentManager是用棧來管理Fragment的,直到今天深扒了Framework源碼后,才發現一直搞錯了。可能也有人跟我有一樣的誤解,希望這篇文章能讓你樹立正確的觀點。
一、我是怎么開始懷疑自己原來的觀點的
今天在復習Fragment相關知識的時候,突然想到一個有意思的話題:假設在Activity的界面上有一個FrameLayout,它的id是container1,那么,能不能在這個container1中添加多個Fragment呢?
于是果斷建立一個Demo項目進行實驗(文末有源碼地址),在container1中添加了一個Fragment1,然后又添加了一個Fragment2,發現沒有報錯!當然,此時還不能確定兩個Fragment都添加到了container1中了。
緊接著,我調用FragmentManager的findFragmentById(R.id.container1)方法,測試發現,返回的是Fragment2。
從結果來看,貌似Fragment1沒有在container1中,于是我又做了一個實驗,那就是在添加Fragment1時為它添加了一個Tag,即"fragment1",然后,我再次調用FragmentManager的findFragmentByTag("fragment1"),果然,查找到了Fragment1,這說明Fragment1和Fragment2都添加成功了。
那么問題來了!!
很多文章和書上都說,FragmentManager是靠container的id來區分Fragment的,現在Fragment1和Fragment2是同一個container id 。FragmentManager是怎么管理它們的呢?
到這一步,我還是覺得用棧可以解釋通,后添加的Fragment在棧頂,之前添加的在下面,只讓棧頂的Fragment顯示出來,在調用findFragmentById時也只返回棧頂的Fragment。
于是,我又做了一個實驗,上面的container1不是一個FrameLayout嗎,我把它改成vertical的LinearLayout,再次運行Demo項目,WORD 天,Fragment1和Fragment2的界面都顯示出來了,如下所示:
two_fragment_in_one_container.PNG
到這一步,我已經很懷疑FragmentManager會用棧來管理Fragment了。
然后,我進一步想,一個Activity的界面上可以有許多個FrameLayout,它們可以作為container2,container3......,每一個container中都可以添加許多的Fragment,如果FragmentManager使用一個棧來管理這么多的Fragment,遇到remove一個非棧頂的Fragment時,豈不費勁死!
至此,我已經不相信自己之前的觀點了,所謂一言不和,就扒源碼,我開始了自己的探索路程。
二、先找到FragmentManager
因為在Activitty中是通過getSupportFragmentM這個方法獲取FragmentManager的實例的,我毫不留情地在這個方法上點擊了。這種感覺就像潛水,現在的深度是5米,感覺棒棒噠!
眼前的景象是:我進入了FragmentActivity內部,并且看到了這個方法:
/**
* Return the FragmentManager for interacting with fragments associated
* with this activity.
*/
public FragmentManager getSupportFragmentManager() {
return mFragments.getSupportFragmentManager();
}
我二話不說,繼續點,再點,再點,終于潛到了一個完全沒有光的深度,號黑啊,我打開了頭頂上的探照燈,發現自己到了FragmentManager的一個內部類:FragmentManagerImpl
哦,終于見到了FragmentManager的真身啦!
三、再找到 FragmentTransaction
稍加停留后,我就繼續往下潛了,我找到FragmentManagerImpl的beginTransaction方法,勇敢地點擊了進去,經過幾次點擊,終于找到了FragmentTransaction的真身,原來是一個叫做BackStackRecord的類。
我找到它的add()方法,繼續往下點,來到了一個私有方法處,該方法的核心代碼如下:
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd){
...
fragment.mTag = tag;
...
fragment.mContainerId = fragment.mFragmentId = containerViewId;
...
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
}
看到這幾行代碼,我已經覺得不虛此潛了!但對真理的崇高追求讓我勇敢地在addOp(op)上點了進去......
四、發現真相!!
進到addOp方法內部,我看到這樣幾行閃閃發光的代碼:
//這里的op就是Fragment的載體
void addOp(Op op) {
if (mHead == null) {
mHead = mTail = op;
} else {
op.prev = mTail;
mTail.next = op;
mTail = op;
}
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
mNumOp++;
}
看到mHead、mTail字樣,終于確定了,FragmentManager是用鏈表來管理Fragment的。
不是用棧
不是用棧
不是用棧
五、進一步探索findFragmentById方法
文章開頭的例子中,我們看到findFragmentById返回了Fragment2,于是我很好奇,這個方法是怎么實現的,潛一次水不容易,我決定一并弄清它的真相。
這個方法的代碼是這樣的:
@Override
public Fragment findFragmentById(int id) {
/*mAdded是一個ArrayList<Fragment>,里面存的是該Activity界面上所有
的container中最新添加的Fragment*/
if (mAdded != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) {
Fragment f = mAdded.get(i);
if (f != null && f.mFragmentId == id) {
return f;
}
}
}
/* mActive也是一個ArrayList<Fragment>,里面保存該Activity上所有添加
過的Fragment,在我們上文提到的Fragment1和Fragment2同時顯示在一個
LinearLayout的例子中,兩個Fragment在mActive中,但是只有Fragment2
在mAdded,這也就解釋了為什么findFragmentById會返回Fragment2啦,
因為是先從mAdded查找的,而且是倒著查的*/
if (mActive != null) {
// Now for any known fragment.
for (int i=mActive.size()-1; i>=0; i--) {
Fragment f = mActive.get(i);
if (f != null && f.mFragmentId == id) {
return f;
}
}
}
return null;
}
至此,我的潛水過程完全結束了。有句話講:太陽底下沒有新鮮事,現在我覺得源碼面前沒有秘密。
來自:http://www.jianshu.com/p/2412fca60cfe