1. 程式人生 > >Android 元件之FragmentManager與FragmentTransaction小述

Android 元件之FragmentManager與FragmentTransaction小述

一、概述

本節主要分析下FragmentManager與FragmentTransaction的內部程式碼,瞭解一下當我們提交事務時,兩者是怎麼協調處理的

二、FragmentManager的介紹

關於FragmentManager的內容我們可以檢視官網FragmentManager,當然更詳細的我們也可以檢視FragmentManager原始碼,在這裡我就簡單的介紹下,當我們在Activity中要獲取FragmentManager時,我們直接呼叫的getFragmentManager()方法

        FragmentManager fm = getFragmentManager
();

如果你只想使用FragmentManager,那麼這樣一行程式碼就完全足夠了,但是如果你想知其所以然,那麼有一些地方就需要注意了,因為FragmentManager是一個抽象類,所以我們獲取到的只是FragmentManager的一個實現類(子類FragmentManagerImpl)物件而已,具體的獲取過程我們可以接著往下看,這是我們超類Activity類的部分程式碼

        final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

        /**
        * Return the FragmentManager for interacting with fragments associated
        * with this activity.
         */
public FragmentManager getFragmentManager() { return mFragments.getFragmentManager(); }

這是我們FragmentController類,我們發現其又呼叫了mHost.getFragmentManagerImpl()方法

        private final FragmentHostCallback<?> mHost;

        public static final FragmentController createController
(FragmentHostCallback<?> callbacks) { return new FragmentController(callbacks); } public FragmentManager getFragmentManager() { return mHost.getFragmentManagerImpl(); }

這是我們的抽象類FragmentHostCallback< E >,通過物件組合,最後呼叫方法的返回值即一個FragmentManagerImpl物件

        final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();

        FragmentManagerImpl getFragmentManagerImpl() {
            return mFragmentManager;
        }

而FragmentManagerImpl又何許東西也?這就是我們最後獲取到的FragmentManagerImpl類,是一個內部類

    public abstract class FragmentManager {
        ...//省略程式碼
        final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
            ...
        }
        ...
    }

知道了這些我們就明白了,其實我們平時所使用的findFragmentById都是FragmentManagerImpl的方法,多型嘛

    final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
            /**注意只是根據需要展示了FragmentManagerImpl的部分程式碼*/
            ArrayList<Fragment> mAdded;//管理著一個fragment佇列
            ArrayList<BackStackRecord> mBackStack;//管理著一個回退棧

            @Override public FragmentTransaction beginTransaction() {
                return new BackStackRecord(this);
            }
            //addFragment、removeFragment、hideFragment、showFragment
            //detachFragment、attachFragment常用方法
            public void addFragment(Fragment fragment, boolean moveToStateNow) {
                 //將fragment新增到容器裡,並改變相應的標誌位
                ...
            }
            //findFragmentById、findFragmentByTag常用方法
            public Fragment findFragmentById(int id) {
                //從容器里根據id取出相應的fragment
                ...
            }

在這裡我只是簡單展示了一下FragmentManagerImpl的極少量程式碼,讓我們心裡先有個大致印象,當我們介紹事務FragmentTransaction的時候,我們會接觸到FragmentManagerImpl更多的方法,明白當我們提交一個事務時,兩者是怎麼協調處理的。

三、FragmentTransaction的分析

上面我們說了FragmentManager,通過beginTransaction()方法,我們便能開啟一個事務,之後就是我們的一些普通操作了

        private void addFragment4() {
            FragmentManager fm = getFragmentManager();
            //在這裡為了直觀展示,就加一個區域性變數
            FragmentTransaction fragmentTransaction = fm.beginTransaction();
            MyFragment4 fragment4 = (MyFragment4) fm.findFragmentById(R.id.activity5_fragment);
            if(fragment4 == null){
                fragment4 = new MyFragment4();
                fragmentTransaction.add(R.id.activity5_fragment, fragment4)
                        //根據需要是否要加入到回退棧
                        .addToBackStack(null)
                        .commit();
            }
        }

我們知道,fm.beginTransaction()實際呼叫的是其子類FragmentManagerImpl的方法,其實就是個多型思想

        @Override public FragmentTransaction beginTransaction() {
            return new BackStackRecord(this);
        }

通過官網FragmentTransaction我們可以知道,FragmentTransaction也是一個抽象類,我們所使用的事務即其子類BackStackRecord類,類中程式碼非常多,我們可以檢視BackStackRecord原始碼,在這裡,我就根據我們常用的add並commit的流程來簡單介紹下BackStackRecord,並瞭解一下其與FragmentManagerImpl是怎麼協調工作的,這是我們的BackStackRecord類

    final class BackStackRecord extends FragmentTransaction implements 
                FragmentManager.BackStackEntry, Runnable {
            //在這裡我們持有FragmentManagerImpl物件
            //當然也就能根據需要呼叫FragmentManagerImpl方法了
            final FragmentManagerImpl mManager;

            public BackStackRecord(FragmentManagerImpl manager) {
                mManager = manager;
            }

        }

這是我們的FragmentManagerImpl類,當我們在Activity中beginTransaction()時,我們知道我們建立了一個事務BackStackRecord,並將自身引用傳遞了過去

    final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
            /**注意只是根據需要展示了FragmentManagerImpl的部分程式碼*/
            ArrayList<Fragment> mAdded;//管理著一個fragment佇列

            @Override public FragmentTransaction beginTransaction() {
                return new BackStackRecord(this);
            }
        }

之後我們會呼叫BackStackRecord類的add(int var1, Fragment var2)方法

        public FragmentTransaction add(int containerViewId, Fragment fragment) {
            //doAddOp(...)這是一個私有方法
            //在方法裡會建立一個Op物件
            //Op物件的作用就是記錄一次操作的動作和Fragment引用以及操作使用的動畫
            doAddOp(containerViewId, fragment, null, OP_ADD);
            return this;
        }

之後我們接著呼叫BackStackRecord類的addToBackStack(null)加入回退棧方法

        public FragmentTransaction addToBackStack(String name) {
            if (!mAllowAddToBackStack) {
                throw new IllegalStateException"This FragmentTransaction is not allowed to be added to the back stack."}
            //我們發現其實這個方法只是改變了標誌位,並沒有實際的邏輯程式碼
            //這也是我們為什麼必須在commit之前加入回退棧的原因
            //因為commit之後加入,沒有改變標誌位的事務就不會被提交
            mAddToBackStack = true;
            mName = name;
            return this;
        }

之後我們接著在BackStackRecord類中呼叫commit()方法,提交我們的本次的事務

        public int commit() {
            return commitInternal(false);
        }
        //如果提交時,生命週期處於Saving Activity之後
        //那麼使用commit就會由於丟失資訊從而丟擲錯誤
        //如果不需要儲存資訊,可以使用commitAllowingStateLoss
        public int commitAllowingStateLoss() {
            return commitInternal(true);
        }
        //在這裡我們就簡單看下allowStateLoss為false的情況
        int commitInternal(boolean allowStateLoss) {
            ...//省略部分程式碼
            if (mAddToBackStack) {
                //使用mAvailBackStackIndices和mBackStackIndices兩個陣列
                //來為BackStackRecord分配Index
                mIndex = mManager.allocBackStackIndex(this);
            } else {
                Index = -1;
            }
            //這是我們最後提交的方法,會呼叫FragmentManagerImpl類的enqueueAction方法
            mManager.enqueueAction(this, allowStateLoss);
            return mIndex;
        }

那我們接下來就看下FragmentManagerImpl類的allocBackStackIndex(…)方法,當加入返回棧並在commit事務之前

        // Must be accessed while locked.
        ArrayList<BackStackRecord> mBackStackIndices;
        ArrayList<Integer> mAvailBackStackIndices;

        public int allocBackStackIndex(BackStackRecord bse) {
            synchronized (this) {
                if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
                    if (mBackStackIndices == null) {
                        mBackStackIndices = new ArrayList<BackStackRecord>();
                    }
                    int index = mBackStackIndices.size();
                    mBackStackIndices.add(bse);
                    return index;
                } else {
                    int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
                    mBackStackIndices.set(index, bse);
                    return index;
                }
            }
        }

這是我們最後commit提交後呼叫的FragmentManagerImpl類的enqueueAction(…)方法

        ArrayList<Runnable> mPendingActions;//每提交一個事務都在不同執行緒裡
        Runnable mExecCommit = new Runnable() {
            @Override
            public void run() {
                //這個方法我就不細寫了,其主要作用
                //遍歷mPendingActions管理的事務執行緒
                //並呼叫每個執行緒事務(BackStackRecord)類的run方法
                //隨後一個個移除相應的事務執行緒
                execPendingActions();
            }
        };

        public void enqueueAction(Runnable action, boolean allowStateLoss) {
            if (!allowStateLoss) {
                checkStateLoss();
            }
            synchronized (this) {
                if (mDestroyed || mHost == null) {
                    throw new IllegalStateException("Activity has been destroyed");
                }
                if (mPendingActions == null) {
                    //這是管理事務的執行緒集合,每個執行緒即代表著一個待處理的事務操作
                    mPendingActions = new ArrayList<Runnable>();
                }
                mPendingActions.add(action);
                if (mPendingActions.size() == 1) {
                    mHost.getHandler().removeCallbacks(mExecCommit);
                    //mHost.getHandler()返回一個handler物件
                    //即handler.post(Runnable action)
                    //所以會呼叫mExecCommit的run方法
                    mHost.getHandler().post(mExecCommit);
                }
            }
        }

這就是我們的BackStackRecord類的run方法,在這裡我們根據Op物件所攜帶的資訊,判斷進行哪種操作,隨後再呼叫FragmentManagerImpl的方法,moveToState(…)和是否需要將事務新增到回退棧

        public void run() {
                ...
                //根據OP的資訊呼叫相應的操作,例如當我們add操作
                switch (op.cmd) {
                    case OP_ADD:
                        Fragment f = op.fragment;
                        f.mNextAnim = op.enterAnim;
                        //將當前操作的fragment新增到FragmentManager管理的列表中
                        mManager.addFragment(f, false);
                        break;
                    case OP_REMOVE:
                        Fragment f = op.fragment;
                        f.mNextAnim = op.exitAnim;
                        mManager.removeFragment(f, mTransition, mTransitionStyle);
                        break;
                     ...
                }
                ...
                mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true);
                if (mAddToBackStack) {
                        //將本次事務新增到回退棧
                       mManager.addBackStackState(this);
                }
            }

那麼最後我們再接著回來看下FragmentManagerImpl類的部分方法

            public void addFragment(Fragment fragment, boolean moveToStateNow) {
                //將fragment新增到管理佇列中,並改變相應的標誌位
                ...
            }
            //在回退棧中新增一個事務BackStackRecord
            void addBackStackState(BackStackRecord state) {
                if (mBackStack == null) {
                    mBackStack = new ArrayList<BackStackRecord>();
                }
                mBackStack.add(state);
                //回撥onBackStackChanged
                reportBackStackChanged();
            }
            //這個方法非常重要,每當Fragment的生命週期中狀態改變,都會被呼叫
            //這也保證了Fragment與Activity能夠保持同步
            //具體的程式碼,感興趣的可以檢視原始碼瞭解下
            void moveToState(int newState, int transit, int transitStyle, boolean always) {
               //Fragment的狀態切換的操作邏輯
               ...
            }

到這裡分析就已經結束了,開啟並提交一個事務,是否將事務新增到返回棧兩種情況,我們合二為一的做了介紹,下面我們再來看一下,當我們按返回鍵時,當有事務在返回棧的情況下,事務是怎麼被彈出棧的,這是我們Activity的程式碼

        public void onBackPressed() {
            if (mActionBar != null && mActionBar.collapseActionView()) {
                return;
            }
            //如果我們Fragment的事務回退棧還有事務能被彈出則返回true,否則返回false
            if (!mFragments.getFragmentManager().popBackStackImmediate()) {
                //經過一些其他條件的判斷,最終會呼叫finish()方法
                finishAfterTransition();
            }
        }

下面我們來接著看下FragmentManagerImpl類的popBackStackImmediate(…)方法

         @Override public boolean popBackStackImmediate() {
            checkStateLoss();
            //如果存在待處理的事務,則直接返回true
            executePendingTransactions();
            //引數是固定的,註釋看下面
            return popBackStackState(mHost.getHandler(), null, -1, 0);
        }

這是FragmentManagerImpl類的popBackStackState(…)方法

        //1往左移0位,實際還是1,並且0&1=0 (按位與)
        public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;

        boolean popBackStackState(Handler handler, String name, int id, int flags) {
            //如果我們的回退棧為null,那麼直接返回false退出Activity
            if (mBackStack == null) {
                return false;
            }
            //當返回鍵的時候,傳參是固定的,所以直接走這個分支
            if(if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0)) {
                int last = mBackStack.size()-1;
                if (last < 0) {
                    return false;
                }
                //將我們管理的回退棧最頂層的remove掉
                final BackStackRecord bss = mBackStack.remove(last);
                SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
                SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
                //作用是迴圈所有Op,找到第一個被刪除的fragment
                //和最後一個被新增的fragment
                bss.calculateBackFragments(firstOutFragments, lastInFragments);
                //這是主要方法,註釋看下面
                bss.popFromBackStack(true, null, firstOutFragments, lastInFragments);
                //FragmentManagerImpl管理著一個ArrayList<OnBackStackChangedListener> 集合
                //這個方法作用便是迴圈遍歷onBackStackChanged()方法
                reportBackStackChanged();
            }else {
                ...
            }
        }

這是我們需要的BackStackRecord類的popFromBackStack(…)方法

        public TransitionState popFromBackStack(boolean doStateMove, TransitionState state
                , SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
            ...
            //根據事務的Op攜帶的資訊,知道事務回退棧裡的事務是什麼操作,例如add
            //知道棧裡的事務是的型別了,當然back時候就知道怎麼相應處理了
            //例如事務A是add而來,那麼back後執行remove;
            //如果事務A是replace而來,那麼back後執行先remove掉新的後再add進替換掉那個old的
            Op op = mTail;
            while (op != null) {
                switch (op.cmd) {
                    case OP_ADD: {
                        Fragment f = op.fragment;
                        f.mNextAnim = op.popExitAnim;
                        //因為回退棧裡的事務裡Op標識說明這是新增操作
                        //所以本次back鍵直接將這個事務removeFragment即可
                        mManager.removeFragment(f
                            , FragmentManagerImpl.reverseTransit(mTransition)
                            , mTransitionStyle);
                    }
                    break;
                    ...//其他操作情況
                }
            }
            ...
        }

四、總結

本節主要簡單分析了一下我們在新增事務時的具體實現流程,如果想要檢視更多Fragment的基礎知識,去我的部落格目錄裡檢視吧,因為關於每塊知識點的介紹,部落格單節寫的比較零散,不容易查詢