Fragment篇——FragmentManager分析及用例

說到管理Activity中的Fragment食零,自然就要重點(diǎn)說一下FragmentManager困乒,之前已經(jīng)說過了,getFragmentManager()獲取到的FragmentManager支持原生的Fragment贰谣,而getSupportFragmentManager()支持的是v4包的Fragment娜搂。

獲取Fragment的方法
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/fragment_study1"
        android:name="com.example.study.StudyFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
   <fragment
        android:id="@+id/fragment_study2"
        android:name="com.example.study.StudyFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <fragment
        android:tag="fragment_second"
        android:name="com.example.study.SecondFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

獲取Fragment的三種方式

fragmentManager = getSupportFragmentManager();
// 通過id查找對應(yīng)的Fragment實(shí)例
fragment1 = (StudyFragment) fragmentManager.findFragmentById(R.id.fragment_study1);
// 通過tag找到對應(yīng)的Fragment,在動態(tài)加載和靜態(tài)加載中都可以使用
 secondFragment = (SecondFragment) fragmentManager.findFragmentByTag("fragment_second");
// 獲取添加到FragmentManager的所有Fragment吱抚,通過index訪問百宇,0代表最早添加的Fragment
fragmentManager.getFragments()
FragmentManager是如何管理Fragment的

每次我們對Fragment的操作都需要通過FragmentTransaction,到了這里估計很多博客都已經(jīng)說到了FragmentTransaction里面的幾個操作方法秘豹,雖然在下也是要說的携御,順便說一下Fragment管理的設(shè)計吧,不看不知道既绕,看了才知道可牛逼哄哄了啄刹。

我們知道,沒有FragmenActivity就沒有Fragment凄贩。

那么為什么我們必須要繼承自FragmentActivity呢誓军,這就是Fragment設(shè)計上的便利了,開發(fā)者希望我們在使用Fragment的時候只需要關(guān)注對Fragment的操作怎炊,而Fragment的管理則交由FragmentActivity的FragmentManager來實(shí)現(xiàn)谭企。
在FragmentAactivity這個類下面,當(dāng)我們調(diào)用getSupportManager的時候

public class FragmentActivity extends BaseFragmentActivityJB implements
        ActivityCompat.OnRequestPermissionsResultCallback,
        ActivityCompatApi23.RequestPermissionsRequestCodeValidator {
    ···
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
    
    ···

    public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
    }
    ···
}

我們可以看到FragmentActivity里面有一個FragmentController评肆,這個FragmentController定義了所有對Fragment的管理操作债查,包括我們的Activity在onCreate,onResume瓜挽,onDestroy等各種生命周期或回調(diào)對Fragment的影響盹廷,都是由這個類來控制的。

public class FragmentController {
    private final FragmentHostCallback<?> mHost;

    /**
     * Returns a {@link FragmentController}.
     */
    public static final FragmentController createController(FragmentHostCallback<?> callbacks) {
        return new FragmentController(callbacks);
    }

    /**
     * Returns a {@link FragmentManager} for this controller.
     */
    public FragmentManager getSupportFragmentManager() {
        //獲取到FragmentManager對象
        return mHost.getFragmentManagerImpl();
    }

FragmentHostCallback是一個抽象類久橙,負(fù)責(zé)調(diào)用各種各樣的回調(diào)俄占,這樣的話,當(dāng)Avtivity的狀態(tài)淆衷,生命周期發(fā)生改變的時候缸榄,就可以通過這個回調(diào)接口進(jìn)行統(tǒng)一管理,在上面提到的HostCallbacks是FragmentActivity里面的一個繼承FragmentHostCallback的內(nèi)部類祝拯。下面我們來看看FragmentHostCallback的默認(rèn)實(shí)現(xiàn)

public abstract class FragmentHostCallback<E> extends FragmentContainer {
    private final Activity mActivity;
    ···
    // 實(shí)例化FragmentManager對象甚带,F(xiàn)ragmentManagerImpl是繼承自FragmentManager抽象類的她肯,對FragmentManager的各種方法提供具體實(shí)現(xiàn)
    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
    ···
}

FragmentManagerImpl里面的具體實(shí)現(xiàn)就是有關(guān)Fragment是如何運(yùn)行的,各種各樣的生命周期鹰贵,判斷Fragment的不同狀態(tài)晴氨,切換狀態(tài),Transaction只是用作記錄對Fragment的操作記錄碉输,最終調(diào)用commit的時候籽前,實(shí)際上調(diào)用的還是FragmentManagerImpl的方法

// FragmentManager 的實(shí)現(xiàn)類
final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
    ···
    @Override
    public FragmentTransaction beginTransaction() {
        // 每次的FragmentTransaction都是獨(dú)立的
        return new BackStackRecord(this);
    }
    ···
}

// Transaction的實(shí)現(xiàn)類
final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {

    // 初始化的時候傳入FragmentManagerImpl 的實(shí)例
    public BackStackRecord(FragmentManagerImpl manager) {
        mManager = manager;
    }

    @Override
    public int commit() {
        //返回棧id,要是不添加進(jìn)棧敷钾,返回-1
        return commitInternal(false);
    }

    int commitInternal(boolean allowStateLoss) {
       // 提交以后無法再次提交
        if (mCommitted) throw new IllegalStateException("commit already called");
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(TAG);
            PrintWriter pw = new PrintWriter(logw);
            dump("  ", null, pw, null);
        }
        mCommitted = true;
        //是否要添加到回退棧
        if (mAddToBackStack) {
            // 在回退棧中分配棧ID
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        //執(zhí)行這個Transaction
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

}

FragmentManagerImpl的部分實(shí)現(xiàn)

    /**
     * 添加一個操作到待操作隊(duì)列中
     *
     * @param action 添加的操作
     * @param allowStateLoss 是否允許丟失狀態(tài)信息
     * @throws 如果Activity已經(jīng)銷毀了拋出IllegalStateException異常
     */
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            // 檢查狀態(tài)是否丟失枝哄,默認(rèn)的commit實(shí)現(xiàn)會執(zhí)行這一步
            checkStateLoss();
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<>();
            }
            // 添加到待操作隊(duì)列中
            mPendingActions.add(action);
            scheduleCommit();      //執(zhí)行Commit操作
        }
    }

    private void scheduleCommit() {
        synchronized (this) {
            // 是否有延時的事務(wù)
            boolean postponeReady =
                    mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
            // 是否有待執(zhí)行的事務(wù)
            boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
            if (postponeReady || pendingReady) {
                mHost.getHandler().removeCallbacks(mExecCommit);
                // 執(zhí)行這個Runnable
                mHost.getHandler().post(mExecCommit);
            }
        }
    }

    //通過handler調(diào)用,在主線程運(yùn)行
    public boolean execPendingActions() {
        //收集和執(zhí)行延時的操作闰非,這種延時是因?yàn)檫€沒準(zhǔn)備好膘格??
        ensureExecReady(true);

        boolean didSomething = false;
       //根據(jù)事務(wù)對象生成待執(zhí)行的操作财松,這個事務(wù)對象是FragmentTransaction的實(shí)現(xiàn)
        while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
            mExecutingActions = true;
            try {
               //優(yōu)化執(zhí)行事務(wù),里面的處理邏輯相當(dāng)復(fù)雜
                optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);
            } finally {
               //清空緩存事務(wù)隊(duì)列
                cleanupExec();
            }
            didSomething = true;
        }
        //判斷FragmentList是否需要延時纱控,進(jìn)而調(diào)用moveToState修改Fragment的狀態(tài)辆毡,根據(jù)狀態(tài)來觸發(fā)Fragment的不同生命周期
        doPendingDeferredStart();

        return didSomething;
    }

上面的就是我們通過getSupportFragmentManager()獲取到FragmentManager,然后再開啟事務(wù)甜害,提交事務(wù)所經(jīng)歷的代碼流程舶掖。控制Fragment的生命周期的回調(diào)尔店,通過FragmentManager的moveToState方法眨攘。

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive)

關(guān)于Fragment的生命周期,要改篇再學(xué)習(xí)∠荩現(xiàn)在繼續(xù)來學(xué)習(xí)一下FragmentTransaction的一些常用方法

#將一個fragment實(shí)例添加到Activity里面指定id的容器中
add(Fragment fragment, String tag)
add(int containerViewId, Fragment fragment)
add(int containerViewId, Fragment fragment, String tag);
 #將一個fragment實(shí)例從FragmentManager的FragmentList中移除
remove(Fragment fragment);
#只控制Fragment的隱藏
hide(Fragment fragment)
#只控制Fragment的顯示
show(Fragment fragment)
#清除視圖鲫售,從containerid指定的Added列表移除,F(xiàn)ragmentList依然保留
detach(Fragment fragment)
#創(chuàng)建視圖该肴,添加到containerid指定的Added列表情竹,F(xiàn)ragmentList依然保留
attach(Fragment fragment)
#替換containerViewId中的fragment,它會把containerViewId中所有fragment刪除匀哄,然后添加當(dāng)前的fragment
replace(int containerViewId, Fragment fragment)
replace(int containerViewId, Fragment fragment, String tag)
FragmentTransaction的用例

之前的例子都已經(jīng)對FragmentTransaction的用法有過一些介紹了

//添加Fragment到FragmentList中
private void addFragment(Fragment fragment, String tag){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.add(R.id.fragment_container,fragment,tag);
        transaction.commit();
    }

// 清空fragmentList的所有Fragment秦效,替換成新的Fragment,注意Fragment里面的坑
private void replaceFragment(Fragment fragment, String tag){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fragment_container,fragment,tag);
        transaction.commit();
    }

//移除指定的Fragment
private void removeFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.remove(fragment);
        transaction.commit();
    }

//把Fragment設(shè)置成顯示狀態(tài)涎嚼,但是并沒有添加到FragmentList中
private void showFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.show(fragment);
        transaction.commit();
    }

//把Fragment設(shè)置成顯示狀態(tài)阱州,但是并沒有添加到FragmentList中
private void hideFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.hide(fragment);
        transaction.commit();
    }

// 效果和show相近,創(chuàng)建視圖法梯,添加到containerid指定的Added列表苔货,F(xiàn)ragmentList依然保留,但是會引起生命周期的變化
private void attachFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.attach(fragment);
        transaction.commit();
    }

// 效果和hide相近,清除視圖蒲赂,從containerid指定的Added列表移除阱冶,F(xiàn)ragmentList依然保留,但是會引起生命周期的變化
private void detachFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.detach(fragment);
        transaction.commit();
    }

雖然新的Android版本中滥嘴,F(xiàn)ragment在replace的時候不再重新實(shí)例化了(據(jù)說在舊的版本會出現(xiàn)這個問題木蹬,目前我使用的API 25 ),但是我們還是建議采用show/hide的方式來控制Fragment的顯示和隱藏若皱。還有一點(diǎn)需要注意的是镊叁,attach和detach會改變Fragment在Added列表中的順序,從而也會改變Fragment顯示的順序走触。

FragmentTransaction的事務(wù)回退棧

上面介紹了常用的用法晦譬,接下來要介紹的是FragmentTransaction的事務(wù)回滾,FragmentTransaction把事務(wù)添加到回退棧中,只需要在調(diào)用 transaction.commit()之前互广,調(diào)用以下代碼

//tag標(biāo)記這個用于標(biāo)記這個事務(wù)
transaction.addToBackStack(String tag);

加入回退棧的時候敛腌,調(diào)用commit方法會返回一個index,作為事務(wù)的id惫皱,否則返回-1像樊。使用popBackStack方法進(jìn)行回退,彈出回退棧旅敷。

//默認(rèn)將最上層的操作彈出回退棧
popBackStack()
//使用commit返回的事務(wù)id
popBackStack(int id, int flags);  
//使用加入回退棧時的tag值
popBackStack(String name, int flags);  

flag的取值生棍,當(dāng)取值0時,表示除了指定這一層之上的所有層都退出棧媳谁,指定的這一層為棧頂層涂滴;當(dāng)取值POP_BACK_STACK_INCLUSIVE時,表示連著指定的這一層一起退出棧晴音;

需要注意的是柔纵,使用popBackStack()來彈出棧內(nèi)容的話,調(diào)用該方法后會將事物操作插入到FragmentManager的操作隊(duì)列段多,只有當(dāng)輪詢到該事物時才能執(zhí)行首量。如果想立即執(zhí)行事物的話,需要使用下面幾個對應(yīng)的方法:

popBackStackImmediate()  
popBackStackImmediate(String tag)  
popBackStackImmediate(String tag, int flag)  
popBackStackImmediate(int id, int flag)

在FragmentActivity的onBackPressed()方法內(nèi)可以看到进苍,當(dāng)popBackStackImmediate返回true的情況加缘,則不會執(zhí)行Activity的onBackPressed()方法

    @Override
    public void onBackPressed() {
        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            super.onBackPressed();
        }
    }

2、回退棧(back stack)狀態(tài)改變監(jiān)聽
FragmentManager還為我們提供了監(jiān)控回退棧狀態(tài)改變的方法:

addOnBackStackChangedListener(listener);//添加監(jiān)聽器  
removeOnBackStackChangedListener(listener);//移除監(jiān)聽器  

通過添加監(jiān)聽器觉啊,就可以在回退棧內(nèi)容改變時拣宏,及時收到通知;
(1)杠人、OnCreate()中:
為fragmentManger添加一個監(jiān)聽器:

FragmentManager manager = getSupportFragmentManager();  
listener = new FragmentManager.OnBackStackChangedListener() {  
      
    @Override  
    public void onBackStackChanged() {  
        // TODO Auto-generated method stub  
        Log.d("qijian","backstack changed");  
    }  
};  
manager.addOnBackStackChangedListener(listener);

(2)勋乾、當(dāng)onDestory()中將監(jiān)聽器remove掉:

protected void onDestroy() {  
    // TODO Auto-generated method stub  
    super.onDestroy();  
    FragmentManager manager = getSupportFragmentManager();  
    manager.removeOnBackStackChangedListener(listener);  
}

大家一定要注意宋下,不管是這里的回退棧的監(jiān)聽還是其它的監(jiān)聽器,在頁面對應(yīng)的銷毀時辑莫,都要記得remove掉学歧,不然會造成頁面不釋放,這也是造成OOM的問題之一各吨。

參考文章:
Fragment詳解之四——管理Fragment(2)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末枝笨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子揭蜒,更是在濱河造成了極大的恐慌横浑,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屉更,死亡現(xiàn)場離奇詭異徙融,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瑰谜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門欺冀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萨脑,你說我怎么就攤上這事脚猾。” “怎么了砚哗?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長砰奕。 經(jīng)常有香客問我蛛芥,道長,這世上最難降的妖魔是什么军援? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任仅淑,我火速辦了婚禮,結(jié)果婚禮上胸哥,老公的妹妹穿的比我還像新娘涯竟。我一直安慰自己,他們只是感情好空厌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布庐船。 她就那樣靜靜地躺著,像睡著了一般嘲更。 火紅的嫁衣襯著肌膚如雪筐钟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天赋朦,我揣著相機(jī)與錄音篓冲,去河邊找鬼李破。 笑死,一個胖子當(dāng)著我的面吹牛壹将,可吹牛的內(nèi)容都是我干的嗤攻。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼诽俯,長吁一口氣:“原來是場噩夢啊……” “哼妇菱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起惊畏,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤恶耽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后颜启,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體偷俭,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年缰盏,在試婚紗的時候發(fā)現(xiàn)自己被綠了涌萤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡口猜,死狀恐怖负溪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情济炎,我是刑警寧澤川抡,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站须尚,受9級特大地震影響崖堤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耐床,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一密幔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撩轰,春花似錦胯甩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溉苛,卻和暖如春镜廉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愚战。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工娇唯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留齐遵,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓塔插,卻偏偏與公主長得像梗摇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子想许,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

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