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

 

 本文由用戶 Chante15Y 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!