Fragment 原理

1. fragment 本質(zhì)

fragment 本質(zhì)上是 view 的容器和控制器,fragment 是 activity 的碎片散劫。

activity 是什么呢?activity 是四大組件之一,4大組件是 android 系統(tǒng)的組成部件,因?yàn)?LMK(Low Memery Killer)機(jī)制隧甚,4 大組件就是我們提供給用戶的功能的載體,4 大組件還是我們提供給用戶的功能的入口昼丑。Activity Service BroadcastReceiver ContentProvider呻逆,Activity 是帶用戶界面的這些功能的載體夸赫,Service 是不帶用戶界面的功能的載體菩帝。和 Service 比較,Activity 相當(dāng)于 MVC 中的 View茬腿。但我們把 Activity 剖析開來理解呼奢,Activity 承擔(dān)了 View 控件的容器和控制器功能。Activity 還承擔(dān)了 View 控件的數(shù)據(jù)的容器的功能切平。

fragment 除了不是系統(tǒng)組件外握础,擁有其他所有 Activity 的功能。fragment 的存在就是對(duì) activity 的功能進(jìn)行拆分悴品,降低 activity 的負(fù)擔(dān)禀综,減少 activity 中的代碼量简烘。

fragment 還有對(duì) fragment 中的 view 的狀態(tài)進(jìn)行保持的能力,需要保持的 view 必須設(shè)置 id定枷,否則不能保存 view 的狀態(tài)孤澎。

2. FragmentTransaction 操作

FragmentTransaction 的 add、remove欠窒、replace覆旭、hide、show 操作本質(zhì)上是對(duì) fragment 中的 view 樹進(jìn)行 add岖妄、remove型将、hide、show 操作

add: 是把 fragment 中的 view 樹添加到容器 viewgroup 中荐虐,相當(dāng)于 viewgroup.addview();
remove: 是把 fragment 中的 view 樹對(duì)象從容器 viewgroup 中移除七兜,相當(dāng)于 viewgroup.removeView();
replace: 是 add 操作和 remove 操作的合體,相當(dāng)于先 remove 掉 viewgroup 容器中所有的 fragment福扬,再添加新的
fragment 對(duì)象惊搏。
hide: 操作相當(dāng)于設(shè)置 fragment 對(duì)應(yīng)的 view 為 gone
show: 操作相當(dāng)于設(shè)置 fragment 對(duì)應(yīng)的 view 為 visible

3. FragmentTransaction 的 addToBackStack 功能

addToBackStack 是把該事務(wù)所有操作構(gòu)成的操作集合都添加到 FragmentManager 對(duì)象的后退任務(wù)棧中,作為任務(wù)棧中的一個(gè)元素忧换,當(dāng)我們按 back 鍵的時(shí)候進(jìn)行該操作集合構(gòu)成的元素進(jìn)行的逆向操作恬惯,一次彈出一個(gè)元素,主動(dòng)調(diào)用 FragmentManager.popBackStack 方法也可以進(jìn)行一個(gè)彈棧操作亚茬。

4. Fragment 操作原理

fragment 的 add 操作到底是怎么實(shí)現(xiàn)的酪耳?通過查看源碼,fragment 的 add 操作主要要做下面的事情:

  1. 通過 activity 獲取到 FragmentManager 對(duì)象刹缝,這里會(huì) new 一個(gè) FragmentManagerImpl 對(duì)象
  2. 通過 FragmentManager 開啟一個(gè)事務(wù)碗暗,這里會(huì) new 一個(gè) BackStackRecord 對(duì)象,一個(gè) BackStackRecord 也就是一個(gè)事務(wù)
  3. 通過 FragmentTransaction 執(zhí)行 add 操作梢夯,本質(zhì)上是 new 了一個(gè) Op 對(duì)象言疗,添加到了 BackStackRecord 內(nèi)部的隊(duì)列中(BackStackRecord 有一個(gè)隊(duì)列用來保存這次事務(wù)進(jìn)行的所有操作)
  4. 通過 FragmentTransaction 執(zhí)行 commit 方法,把 BackStackRecord 添加到 manager 的 Action 隊(duì)列中
  5. 主線程中處理 Action 中的 BackStackRecord颂砸,調(diào)用 BackStackRecord 的 run 方法
  6. BackStackRecord 的 run 方法處理 BackStackRecord 內(nèi)部的隊(duì)列中的 Op 對(duì)象噪奄,如果是 add 類型 Op,調(diào)用 Manager 的 addFragment
  7. 把 fragment 對(duì)象添加到 manager 的 mAdded 集合中人乓,修改 fragment 的狀態(tài)勤篮,執(zhí)行 fragment 的生命周期方法,把 fragment 中的 view 添加到 fragment 的容器 viewgroup 中
1. FragmentActivity.getSupportFragmentManager()

//FragmentActivity.java
final FragmentManagerImpl mFragments = new FragmentManagerImpl();

public FragmentManager getSupportFragmentManager() {
    return mFragments;
}
FragmentActivity 的 fragmentManager 就是 FragmentManagerImpl 對(duì)象
2. FragmentManager.beginTransaction()

//FragmentManagerImpl.java
@Override
public FragmentTransaction beginTransaction() {
    return new BackStackRecord(this);
}

開始事務(wù)就是創(chuàng)建一個(gè) BackStackRecord 對(duì)象色罚,該對(duì)象用來表示一個(gè)fragment事務(wù)
3. FragmentTransaction.add()

//BackStackRecord.java
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
    doAddOp(containerViewId, fragment, tag, OP_ADD);
    return this;
}

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    fragment.mFragmentManager = mManager;

    if (tag != null) {
        ...
        fragment.mTag = tag;
    }

    if (containerViewId != 0) {
        ...
        fragment.mContainerId = fragment.mFragmentId = containerViewId;
    }

    //新建一個(gè) Op,添加到隊(duì)列中
    Op op = new Op();
    op.cmd = opcmd;
    op.fragment = fragment;
    addOp(op);
}

//添加 Op 到鏈表中
void addOp(Op op) {
    if (mHead == null) {
        mHead = mTail = op;
    } else {
        op.prev = mTail;
        mTail.next = op;
        mTail = op;
    }
    ...
    mNumOp++;
}

添加 Fragment 就是給 fragment 事務(wù)對(duì)象中Op對(duì)象鏈表中添加一個(gè)Op對(duì)象
4. FragmentTransaction.commit()

public int commit() {
    return commitInternal(false);
}

int commitInternal(boolean allowStateLoss) {
    ...
    //添加 transaction 到 manager 的隊(duì)列中
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
}

提交事務(wù)就是把事務(wù)對(duì)象添加到 FragmentManager 隊(duì)列中
5. FragmentManagerImpl.enqueueAction()

public void enqueueAction(Runnable action, boolean allowStateLoss) {

    synchronized (this) {
        ...
        if (mPendingActions == null) {
            mPendingActions = new ArrayList<Runnable>();
        }
        mPendingActions.add(action);
        if (mPendingActions.size() == 1) {
            mActivity.mHandler.removeCallbacks(mExecCommit);
            mActivity.mHandler.post(mExecCommit);
        }
    }
}

Runnable mExecCommit = new Runnable() {
    @Override
    public void run() {
        execPendingActions();
}

/**
 * Only call from main thread!
 */
public boolean execPendingActions() {
    ...
    while (true) {
        int numActions;

        synchronized (this) {
            ...
            numActions = mPendingActions.size();
            if (mTmpActions == null || mTmpActions.length < numActions) {
                mTmpActions = new Runnable[numActions];
            }
            mPendingActions.toArray(mTmpActions);
            mPendingActions.clear();
            mActivity.mHandler.removeCallbacks(mExecCommit);
        }

        mExecutingActions = true;
        for (int i=0; i<numActions; i++) {
            //執(zhí)行 pendding 中的 action.run碰缔,就是執(zhí)行事務(wù)
            mTmpActions[i].run();
            mTmpActions[i] = null;
        }
        ...
    }

    ...
    return didSomething;
}
6. BackStackRecord.run()

public void run() {
    ...
    bumpBackStackNesting(1);

    Op op = mHead;
    while (op != null) {
        switch (op.cmd) {
            case OP_ADD: {
                Fragment f = op.fragment;
                f.mNextAnim = op.enterAnim;
                mManager.addFragment(f, false);
            } break;
            case OP_REPLACE: {
                Fragment f = op.fragment;
                if (mManager.mAdded != null) {
                    for (int i=0; i<mManager.mAdded.size(); i++) {
                        Fragment old = mManager.mAdded.get(i);
                        ...
                                mManager.removeFragment(old, mTransition, mTransitionStyle);
                            }
                        }
                    }
                }
                if (f != null) {
                    f.mNextAnim = op.enterAnim;
                    mManager.addFragment(f, false);
                }
            } break;
            case OP_REMOVE: {
                Fragment f = op.fragment;
                f.mNextAnim = op.exitAnim;
                mManager.removeFragment(f, mTransition, mTransitionStyle);
            } break;
            case OP_HIDE: {
                Fragment f = op.fragment;
                f.mNextAnim = op.exitAnim;
                mManager.hideFragment(f, mTransition, mTransitionStyle);
            } break;
            case OP_SHOW: {
                Fragment f = op.fragment;
                f.mNextAnim = op.enterAnim;
                mManager.showFragment(f, mTransition, mTransitionStyle);
            } break;
            case OP_DETACH: {
                Fragment f = op.fragment;
                f.mNextAnim = op.exitAnim;
                mManager.detachFragment(f, mTransition, mTransitionStyle);
            } break;
            case OP_ATTACH: {
                Fragment f = op.fragment;
                f.mNextAnim = op.enterAnim;
                mManager.attachFragment(f, mTransition, mTransitionStyle);
            } break;

        }

        op = op.next;
    }

    mManager.moveToState(mManager.mCurState, mTransition,
            mTransitionStyle, true);

    if (mAddToBackStack) {
        mManager.addBackStackState(this);
    }
}

調(diào)用 FragmentManager 中的 add、remove戳护、show金抡、hide 等方法瀑焦,replace 方法是移除 mManager.mAdded 中的 fragment
7. FragmentManager.add()

public void addFragment(Fragment fragment, boolean moveToStateNow) {
    if (mAdded == null) {
        mAdded = new ArrayList<Fragment>();
    }
    if (DEBUG) Log.v(TAG, "add: " + fragment);
    makeActive(fragment);
    if (!fragment.mDetached) {
        if (mAdded.contains(fragment)) {
            throw new IllegalStateException("Fragment already added: " + fragment);
        }
        //添加到 mAddded 集合中
        mAdded.add(fragment);
        fragment.mAdded = true;
        fragment.mRemoving = false;
        if (fragment.mHasMenu && fragment.mMenuVisible) {
            mNeedMenuInvalidate = true;
        }
        if (moveToStateNow) {
            moveToState(fragment);
        }
    }
}
moveToState(fragment) 方法會(huì)觸發(fā) fragment 的生命周期方法
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市梗肝,隨后出現(xiàn)的幾起案子蝠猬,更是在濱河造成了極大的恐慌,老刑警劉巖统捶,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榆芦,死亡現(xiàn)場離奇詭異,居然都是意外死亡喘鸟,警方通過查閱死者的電腦和手機(jī)匆绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來什黑,“玉大人崎淳,你說我怎么就攤上這事°蛋眩” “怎么了拣凹?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恨豁。 經(jīng)常有香客問我嚣镜,道長,這世上最難降的妖魔是什么橘蜜? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任菊匿,我火速辦了婚禮,結(jié)果婚禮上计福,老公的妹妹穿的比我還像新娘跌捆。我一直安慰自己,他們只是感情好象颖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布佩厚。 她就那樣靜靜地躺著,像睡著了一般说订。 火紅的嫁衣襯著肌膚如雪抄瓦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天克蚂,我揣著相機(jī)與錄音闺鲸,去河邊找鬼筋讨。 笑死埃叭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悉罕。 我是一名探鬼主播赤屋,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼立镶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了类早?” 一聲冷哼從身側(cè)響起媚媒,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涩僻,沒想到半個(gè)月后缭召,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逆日,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年嵌巷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片室抽。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搪哪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坪圾,到底是詐尸還是另有隱情晓折,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布兽泄,位于F島的核電站漓概,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏病梢。R本人自食惡果不足惜垛耳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望飘千。 院中可真熱鬧堂鲜,春花似錦、人聲如沸护奈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霉旗。三九已至痴奏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厌秒,已是汗流浹背读拆。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸵闪,地道東北人檐晕。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辟灰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子个榕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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