從源碼角度剖析Fragment核心知識點

自動Android在3.0版本中退出Fragment以來,fragment在我們日常的開發中無處不在,他使我們的在開發android時能更好的做到view的解耦。關于Fragment的用法,相信大家已經用的滾瓜爛熟了,各種FragmentTransaction的操作,都信手拈來。今天我們要從源碼的角度去剖析fragment內部實現的原理,我相信只有了解了內部實現原理,我們在碰到fragment的issue的時候才知道如何去解決。

我們今天要分析的是support v4包中的Fragment,相信絕大部分人都是用的兼容包中的fragment。

先上一張圖:

Fragment有關類結構圖

這張圖描述的是與fragment相關的類的UML圖,基本描述了這些類之間的關系。
Fragment理論上可以被任何對象持有,然后管理其生命周期,但是絕大部分時候,我們都是在activity里面使用它,我們可以從Activity出發,理清楚這些類之間的關系。

FragmentActivity

兼容包中,支持fragment的activity叫做FragmentActivity, 我們常用的AppCompatActivity也是繼承自它,

在FragmentActivity中有一個重要的成員變量mFragments,它的類型是FragmentController

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

正是這個mFragments的存在使得在FragmentActivity中進行fragment操作成為可能,我們可以看到在FragmentActivity中進行的任何fragment的操作都得經FragmentController之手

 @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    mFragments.attachHost(null /*parent*/);

    super.onCreate(savedInstanceState);

    //...
    mFragments.restoreAllState(p, nc != null ? nc.fragments : null);

    //...
    mFragments.dispatchCreate();
}

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

因此,我們進到FragmentController類中去一探究竟。打開FragmentController,發現其函數實現非常簡單。基本上你可以認為FragmentController是FragmentManager的代理,Activity需要的FragmentController的函數,最后調用的都是FragmentHostCallback中的FragmentManager的方法。

FragmentHostCallback,我們可以認為他是用來管理Fragment生命周期,同時作為代理提供fragment需要與外界打交道時的函數實現的比如從fragment中啟動新的Activity, 請求權限等等。其Fragment生命周期的的管理由FragmentManager負責,其余部分代理功能由其自己負責。

我們打開FragmentHostCallback的源代碼,可以看到其頭頂注釋中寫著:fragment可以被任何對象持有,要使一個對象具有持有和管理fragment生命周期的能力,我們只需要實現FragmentHostCallback中的函數。顯然,我們最常見的FragmentActivity肯定實現了FragmentHostCallback,我們跳轉到FragmentActivity,看到其內部有一個非靜態內部類HostCallbacks, 正式這個內部類的存在,使得FragmentActivity具有了持有和管理Fragment的能力,Fragment與外部交互的功能都由FragmentActivity實現了。至于FragmentController,前面已經說到,只是作為中間橋梁的作用。

Fragment

前面講了這么多,我們還沒有開始介紹今天的主角Fragment, 接下來我們就來揭開其稍許神秘的面紗。

Fragment = Attr + View + + State, 此處等待UML圖

Attr

Attr指得是Fragment的一些固有屬性,不會隨著Fragment的生命周期發生變化的,比如

Bundle mArguments;  //構造參數
boolean mFromLayout; //是否從layout文件中創建
...

View

View是Fragment創建出來并顯示給用戶的界面的view,如果Fragment被持有,會被添加到Activity某一塊layout中去

// The parent container of the fragment after dynamically added to UI.
ViewGroup mContainer;

// The View generated for this fragment.
View mView;

// The real inner view that will save/restore state.
View mInnerView;

State

State指的是Fragment在生命周期變遷中中會發生改變的狀態

int mState = INITIALIZING; //生命周期狀態
boolean mAdded; //是否被添加
boolean mRemoving; //是否被移除
boolean mHidden;// 是否被隱藏
boolean mDetached; //是否已經分離
...

其中Fragment的成員變量mState,直接映射了Fragment的生命周期狀態變遷,其取值狀態在以下常量中:

static final int INITIALIZING = 0;     // Not yet created.
static final int CREATED = 1;          // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3;          // Fully created, not started.
static final int STARTED = 4;          // Created and started, not resumed.
static final int RESUMED = 5;          // Created started and resumed.

我們在開中經常重寫的onCreate, onResume, onStop方法都發生mState的變遷過程中。 接下來,我們就來看看Fragment的生命周期是如何變化的。

Fragment事務操作:BackStackRecord

我們在開發中,每次要操作那個Fragment的添加,刪除,隱藏,顯示等,都需要使用FragmentTransaction,比如添加一個Fragment:

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.contaniner, testFragment);
transaction.commit();

FragmentTransaction實際上是一個抽象類,里面定義了一些關于Fragment操作的函數接口,

public abstract class FragmentTransaction {

    add

    replace

    remove

    hide 

    show

    detach

    attach

    addToBackStack

    ...

}

從FragmentManger.beginTransaction真正返回的確是一個BackStackRecord類, 其實現了FragmentTransaction所定義的接口。

接下來,我們以添加為例,看看BackStackRecord是如何完成Add這個transaction的。

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

    if (tag != null) {
        if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
            throw new IllegalStateException("Can't change tag of fragment "
                    + fragment + ": was " + fragment.mTag
                    + " now " + tag);
        }
        fragment.mTag = tag;
    }

    if (containerViewId != 0) {
        if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
            throw new IllegalStateException("Can't change container ID of fragment "
                    + fragment + ": was " + fragment.mFragmentId
                    + " now " + containerViewId);
        }
        fragment.mContainerId = fragment.mFragmentId = containerViewId;
    }

    Op op = new Op();
    op.cmd = opcmd;
    op.fragment = fragment;
    addOp(op);
}   

BackStackRecord中實現的四個add函數,最后都調用了doAddOp,我們注意到已經添加過的Fragment其tag和被添加到的containerId是不能更改的,否則會拋異常。 函數最后new了一個Op,Op是啥?Op其實就是Operation的簡稱,有經驗的讀者應該知道在一次FragmentTransaction中實際上可以進行多次add,remove之類的操作。每次操作,都會生成一個新的Op對象,在transaction commit操作時,會將這些Operation全部執行掉

我們來看看Op類對象里都有些啥

static final class Op {
    Op next;
    Op prev;
    int cmd;
    Fragment fragment;
    int enterAnim;
    int exitAnim;
    int popEnterAnim;
    int popExitAnim;
    ArrayList<Fragment> removed;
}

從next, prev 兩個指針可以看出,Op是作為一個鏈表的節點而存在的,因此FragmentTransaction肯定是在一個鏈表中存儲了一次事務中的所有需要執行的操作。

cmd定義了Operation操作的類型

static final int OP_NULL = 0;
static final int OP_ADD = 1;
static final int OP_REPLACE = 2;
static final int OP_REMOVE = 3;
static final int OP_HIDE = 4;
static final int OP_SHOW = 5;
static final int OP_DETACH = 6;
static final int OP_ATTACH = 7;

fragment指定了這次Operation要操縱哪一個Fragment
此外Op類還包含了轉場動畫和一個操作會移除的Fragment集合。

doAddOp函數最后執行了

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++;
}   

addOp執行了一個典型的添加節點到鏈表末尾的數據結構操作,并將transaction的動畫賦給op,最后講mNumOp加一,mNumOp代表了這次transaction中包含的操作個數。如果為0,則isEmpty返回true.

等到操作都添加好了之后,就差commit了。

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) {
        //添加到返回棧
        mIndex = mManager.allocBackStackIndex(this);
    } else {
        mIndex = -1;
    }
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
}

如果設置了addToBackStack,就會執行添加到返回棧的操作,這個我們后面會專門講,mManager.enqueueAction 顧名思義就是講當前的transaction操做入隊列,因此在FragmentManager肯定會維護一個隊列來存儲當前尚未執行的transaction.

public void enqueueAction(Runnable action, boolean allowStateLoss) {
    ...
    synchronized (this) {
        ...
    
        mPendingActions.add(action);
        if (mPendingActions.size() == 1) {
            mHost.getHandler().removeCallbacks(mExecCommit);
            mHost.getHandler().post(mExecCommit);
        }
    }
}

enqueueAction首先會將action,添加到一個叫mPendingAction的列表中去,這個列表中存儲著一堆Runnable對象,代表著還未執行的事務,稍等一會兒,我們剛剛說到的是BackStackRecord類對吧,怎么可以添加到Runnable列表中呢,原來BackStackRecod本身還是實現了Runnabled接口,是一個可以執行的對象。添加完畢之后,會調用FragmentHostCallback中提供的getHandler方法,獲取到Handler方法,然后向主線程的MessageQueue中發送一個mExecCommit可執行對象

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

public boolean execPendingActions() {

    boolean didSomething = false;

    while (true) {
        int numActions;
        
        synchronized (this) {
            if (mPendingActions == null || mPendingActions.size() == 0) {
                break;
            }
            
            numActions = mPendingActions.size();
            if (mTmpActions == null || mTmpActions.length < numActions) {
                mTmpActions = new Runnable[numActions];
            }
            //講mPendingAction中的對象轉移到mTmpActions
            mPendingActions.toArray(mTmpActions);
            mPendingActions.clear();
            mHost.getHandler().removeCallbacks(mExecCommit);
        }
        
        //遍歷mTmpActions,執行run
        mExecutingActions = true;
        for (int i=0; i<numActions; i++) {
            mTmpActions[i].run();
            mTmpActions[i] = null;
        }
        mExecutingActions = false;
        didSomething = true;
    }
    
    ...
    return didSomething;
}

因此繞來繞去,最后調用的BackStackRecord本身的run方法,這也符合了commit方法名字本身的定義,transaction只是被批量提交到了主線程的任務隊列里,并不是馬上執行,等待主線程的looper去安排這些任務的執行。

那好,我們現在回家吧,去看看BackStackRecord。

public void run() {
    ...
    
    Op op = mHead;
    while (op != null) {
        int enterAnim = state != null ? 0 : op.enterAnim;
        int exitAnim = state != null ? 0 : op.exitAnim;
        switch (op.cmd) {
            case OP_ADD: {
                Fragment f = op.fragment;
                f.mNextAnim = enterAnim;
                mManager.addFragment(f, false);
            } break;
            case OP_REPLACE: {
                  ... 
            } break;
            case OP_REMOVE: {
                Fragment f = op.fragment;
                f.mNextAnim = exitAnim;
                mManager.removeFragment(f, transition, transitionStyle);
            } break;
            case OP_HIDE: {
                ...
            } break;
            ...
            //更多case
        }

        op = op.next;
    }
    //將active狀態的fragment全部執行狀態變遷檢查
    mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);

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

run方法內部根據不同的cmd走了很不不同的分支,每個分支內部都會對,fragment狀態做改變,最后調用moveToState將fragment的生命周期狀態mState進行變遷。

當然我們注意到hide和show,它們內部實際上不會調用調用moveToState, 因為hideFragment實際上就做了三件事請,

  • 設置mHidden 為true
  • fragment.mView.setVisibility(View.GONE); 隱藏fragment的view
  • fragment.onHiddenChanged(true); 調用onHiddenChange

showFragment也是類似,只不過行為正好相反。

public void addFragment(Fragment fragment, boolean moveToStateNow) {
    ...
    makeActive(fragment);
    if (!fragment.mDetached) {
        ...
        mAdded.add(fragment);
        fragment.mAdded = true;
        fragment.mRemoving = false;
        if (fragment.mHasMenu && fragment.mMenuVisible) {
            mNeedMenuInvalidate = true;
        }
        if (moveToStateNow) {
            moveToState(fragment);
        }
    }
}

void makeActive(Fragment f) {
    if (f.mIndex >= 0) {
        return;
    }
    
    if (mAvailIndices == null || mAvailIndices.size() <= 0) {
        if (mActive == null) {
            mActive = new ArrayList<Fragment>();
        }
        f.setIndex(mActive.size(), mParent);
        mActive.add(f);
        
    } else {
        f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
        mActive.set(f.mIndex, f);
    }
    if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);
}

addFragment會將fragment添加到 mAdded和 mActive這兩個集合當中,這兩個集合維護了當前activity中維護的已經添加的fragment列表和當前處于活躍狀態的fragment列表,如果fragment位于mActive中,那么當activity的狀態發生變化時,fragment也會跟隨著發生變化。FragmentManger 如何引導fragment的狀態發生變化呢?

這一切都發生在moveToState函數當中

Fragment狀態變遷:moveToState

Fragment狀態變遷發生在用戶主動發起transaction,或者fragment被add到activity之后跟隨activity的生命周期變化一起發生改變。每次狀態變遷最終都會走到函數moveToState,字面意思是將fragment遷移到新的狀態

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
        boolean keepActive) {
    // Fragments that are not currently added will sit in the onCreate() state.
    ...
    if (f.mState < newState) {
        // For fragments that are created from a layout, when restoring from
        // state we don't want to allow them to be created until they are
        // being reloaded from the layout.
        ...
        switch (f.mState) {
            case Fragment.INITIALIZING:
                ...
            case Fragment.CREATED:
                if (newState > Fragment.CREATED) {
                   ...
                }
            case Fragment.ACTIVITY_CREATED:
            case Fragment.STOPPED:
                if (newState > Fragment.STOPPED) {
                    if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                    f.performStart();
                }
            case Fragment.STARTED:
                if (newState > Fragment.STARTED) {
                    if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
                    f.performResume();
                    f.mSavedFragmentState = null;
                    f.mSavedViewState = null;
                }
        }
    } else if (f.mState > newState) {
        switch (f.mState) {
            case Fragment.RESUMED:
                if (newState < Fragment.RESUMED) {
                    if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
                    f.performPause();
                }
            case Fragment.STARTED:
                if (newState < Fragment.STARTED) {
                    if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
                    f.performStop();
                }
            case Fragment.STOPPED:
                if (newState < Fragment.STOPPED) {
                    if (DEBUG) Log.v(TAG, "movefrom STOPPED: " + f);
                    f.performReallyStop();
                }
            case Fragment.ACTIVITY_CREATED:
                if (newState < Fragment.ACTIVITY_CREATED) {
                   ...
                }
            case Fragment.CREATED:
                if (newState < Fragment.CREATED) {
                    ...
                }
        }
    }

    ...
}

fragment的state取值,為前面提到的七中狀態,其中最低值是INITIALIZING狀態,代表fragment剛創建,還未被add, 最高狀態值是RESUMED,代表fragment處于前臺。 所以moveToState內部分兩條線,狀態躍升,和狀態降低,里面各有一個switch判斷,注意到switch里每個case都沒有break,這意味著,狀態可以持續變遷,比如從INITIALIZING,一直躍升到RESUMED,將每個case都走一遍,每次case語句內,都會改變state的值。

Fragment狀態變遷圖

比如我們常見的add操作,最后調用的是

mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);

就是將fragment遷移到FragmentManager當前的狀態,因為我們不知道用戶什么時候add fragment,因此fragment被add之后,就將其狀態遷移到FragmentManager當前的狀態,然后跟隨FragmentManager一起發生狀態變遷,除非用戶手動removeFragment將其從mActive列表中移除。

public void dispatchCreate() {
    mStateSaved = false;
    moveToState(Fragment.CREATED, false);
}

public void dispatchActivityCreated() {
    mStateSaved = false;
    moveToState(Fragment.ACTIVITY_CREATED, false);
}

public void dispatchStart() {
    mStateSaved = false;
    moveToState(Fragment.STARTED, false);
}

public void dispatchResume() {
    mStateSaved = false;
    moveToState(Fragment.RESUMED, false);
}

public void dispatchPause() {
    moveToState(Fragment.STARTED, false);
}

public void dispatchStop() {
    // See saveAllState() for the explanation of this.  We do this for
    // all platform versions, to keep our behavior more consistent between
    // them.
    mStateSaved = true;

    moveToState(Fragment.STOPPED, false);
}

public void dispatchReallyStop() {
    moveToState(Fragment.ACTIVITY_CREATED, false);
}

public void dispatchDestroyView() {
    moveToState(Fragment.CREATED, false);
}

public void dispatchDestroy() {
    mDestroyed = true;
    execPendingActions();
    moveToState(Fragment.INITIALIZING, false);
    mHost = null;
    mContainer = null;
    mParent = null;
}

這些 dispatchxxx函數由FragmentActivity狀態變化引發,然后調用moveToState將處于mActive集合中的fragment的狀態全部變更一次。
比如,當FragmentActivity pause的時候,其會通過FM
通知framgents進行狀態變遷。

/**
 * Dispatch onPause() to fragments.
 */
@Override
protected void onPause() {
    super.onPause();
    mResumed = false;
    if (mHandler.hasMessages(MSG_RESUME_PENDING)) {
        mHandler.removeMessages(MSG_RESUME_PENDING);
        onResumeFragments();
    }
    mFragments.dispatchPause();
}

Fragment狀態的保存

既然Fragment持有view,以及一些狀態屬性,那么在Activity保存自身狀態以便下次恢復的時候,就需要把fragment的狀態也保存起來,這樣activity被系統finish掉,然后重新創建時就能恢復上次的fragment狀態。那我們首先直奔FragmentActivity的onSaveInstanceState函數:

 /**
 * Save all appropriate fragment state.
 */
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    //調用FragmentController 代理FragmentManager 保存狀態
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    //保存跟fragment啟動activity等待result的狀態
    if (mPendingFragmentActivityResults.size() > 0) {
        outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);

        int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
        String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
        for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
            requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
            fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
        }
        outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
        outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
    }
}

saveAllState函數就會將FragmentManager在重新恢復fragmentstate時需要的所有信息保存起來,小伙伴們可以跟進去看,我在這里畫了張圖:

FragmentManager狀態的保存

關于返回棧的保存沒有詳細標注,不過我們后面會再詳細講到。

Fragment 狀態的恢復

Fragment狀態的恢復其實就是保存狀態的逆過程,不過,額外的工作是,我們需要根據之前保存的每個active的fragmentstate來恢復創建Fragment:


public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
    if (mInstance != null) {
        return mInstance;
    }

    final Context context = host.getContext();
    if (mArguments != null) {
        mArguments.setClassLoader(context.getClassLoader());
    }
    //利用保存的狀態恢復fragment
    mInstance = Fragment.instantiate(context, mClassName, mArguments);

    if (mSavedFragmentState != null) {
        mSavedFragmentState.setClassLoader(context.getClassLoader());
        mInstance.mSavedFragmentState = mSavedFragmentState;
    }
    mInstance.setIndex(mIndex, parent);
    mInstance.mFromLayout = mFromLayout;
    mInstance.mRestored = true;
    mInstance.mFragmentId = mFragmentId;
    mInstance.mContainerId = mContainerId;
    mInstance.mTag = mTag;
    mInstance.mRetainInstance = mRetainInstance;
    mInstance.mDetached = mDetached;
    mInstance.mFragmentManager = host.mFragmentManager;

    if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
            "Instantiated fragment " + mInstance);

    return mInstance;
}

其中構造Fragment的代碼如下:

  public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
    try {
        Class<?> clazz = sClassMap.get(fname);
        if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = context.getClassLoader().loadClass(fname);
            sClassMap.put(fname, clazz);
        }
        Fragment f = (Fragment)clazz.newInstance();
        if (args != null) {
            args.setClassLoader(f.getClass().getClassLoader());
            f.mArguments = args;
        }
        return f;
    } catch (ClassNotFoundException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (java.lang.InstantiationException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (IllegalAccessException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    }
}

是不是發現這段代碼灰常熟悉,很多人恐怕都遇到過代碼中描述的異常。因為Fragment在恢復時,是利用反射的方式去創建,首先利用類加載器去加載類,然后調用其pubic empty 構造函數去創建fragment。

所以如果Fragment不是public的,或者fragment沒有public的無參構造函數,那么你的應用肯定會碰到這些異常,只不過你在調試的時候可能無法發現,因為你的手機環境太好,構不成saveInstanceState然后恢復它們的條件。

如果我們用Android studio 模板去創建fragment,那么他會給我們默認實現一個newInstance的靜態構造函數,并把構造參數寫在argument里,因為前面那張圖里可以看到,argument會在保存fragment狀態時保存起來的。另外不要寫任何帶參數的構造函數,因為這樣子,默認構造函數就會被隱藏,除非你手動去實現它。 話說這個instantiate是個public方法,因此你也可以直接調用這個函數去創建fragment,只不過它使用起來不是那么方便,使用者不知道我們需要往里面傳什么參數。

MyFragment.instantiate(activity, MyFragment.getClass().getName(), args)
臭名昭著的 “Can not perform this action after onSaveInstanceState”

這里不得不提一提開發中經常會碰到的一個異常,異常拋出時的message為:“Can not perform this action after onSaveInstanceState”, 意為,FragmentTransaction不能再onSaveInstanceState后提交,為什么會拋出這樣一個異常呢,因為FragmentManager認為在onSaveInstanceState 發生之后提交的transaction不能在下次Fm恢復時得到恢復,繼而認為這樣做是危險的,拒絕提交,除非你指定了allowStateLoss,即允許狀態的丟失。

開發者只需要用commitAllowingStateLoss,即可以成功提交這樣的transaction,即使它有可能會丟失狀態。

Fragment返回棧

使用fragmentTransaction的時候可以將其加入返回棧,這樣用戶就可以有機會去撤銷這一動作,將其從返回棧中pop出來。

public FragmentTransaction addToBackStack(String name) {
    if (!mAllowAddToBackStack) {
        throw new IllegalStateException(
                "This FragmentTransaction is not allowed to be added to the back stack.");
    }
    //標記 即將加入返回棧
    mAddToBackStack = true;
    //標記在返回棧中的名字
    mName = name;
    return this;
}

//transaction提交
public void run() {
    //...

    //加入到FragmentManager的返回棧中
    if (mAddToBackStack) {
        mManager.addBackStackState(this);
    }
}

//FragmentManager添加返回棧
 void addBackStackState(BackStackRecord state) {
    if (mBackStack == null) {
        mBackStack = new ArrayList<BackStackRecord>();
    }
    mBackStack.add(state);
    //通知返回棧監聽者們發生改變
    reportBackStackChanged();
}
    

FragmentManager中有一個叫做mBackStack的列表,保存了添加到返回棧的所有record, 當用戶選擇popBackStack的時候,就可以將其pop出來。

我們常用的pop操作就是popBackStack函數,其實FM還提供了很多pop操作,它可以指定以name和id指定pop哪個record,還可以指定flag:POP_BACK_STACK_INCLUSIVE, 如果指定了這個flag就表示將name或者id相等且連續的record全部pop出來。這些操作最后都調用了函數

boolean popBackStackState(Handler handler, String name, int id, int flags) {
    //如果返回棧不存在,立即返回
    if (mBackStack == null) {
        return false;
    }
    //如果name、id、POP_BACK_STACK_INCLUSIVE全部都沒有設置,直接移除棧頂的一個record
    if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0) {
        int last = mBackStack.size()-1;
        if (last < 0) {
            return false;
        }
        final BackStackRecord bss = mBackStack.remove(last);
        SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
        SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
        bss.calculateBackFragments(firstOutFragments, lastInFragments);
        bss.popFromBackStack(true, null, firstOutFragments, lastInFragments);
        reportBackStackChanged();
    } else {
        int index = -1;
        if (name != null || id >= 0) {
            //從棧頂開始尋找一個name或者id匹配的record,找到即跳出循環
            index = mBackStack.size()-1;
            while (index >= 0) {
                BackStackRecord bss = mBackStack.get(index);
                if (name != null && name.equals(bss.getName())) {
                    break;
                }
                if (id >= 0 && id == bss.mIndex) {
                    break;
                }
                index--;
            }
            //如果沒找到,函數返回
            if (index < 0) {
                return false;
            }
            //如果設置了POP_BACK_STACK_INCLUSIVE,則向棧底方向尋找連續匹配的record
            if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) {
                index--;
                // Consume all following entries that match.
                while (index >= 0) {
                    BackStackRecord bss = mBackStack.get(index);
                    if ((name != null && name.equals(bss.getName()))
                            || (id >= 0 && id == bss.mIndex)) {
                        index--;
                        continue;
                    }
                    break;
                }
            }
        }
        if (index == mBackStack.size()-1) {
            return false;
        }
        //彈出從棧頂到連續相同匹配的record記錄位置的所有record,加入集合states中
        final ArrayList<BackStackRecord> states
                = new ArrayList<BackStackRecord>();
        for (int i=mBackStack.size()-1; i>index; i--) {
            states.add(mBackStack.remove(i));
        }
     
        final int LAST = states.size()-1;
        SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
        SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
        //計算在連續record中,最先remove的fragment集合 和 最后進入的fragment集合
        for (int i=0; i<=LAST; i++) {
            states.get(i).calculateBackFragments(firstOutFragments, lastInFragments);
        }
        BackStackRecord.TransitionState state = null;
        //對這些record列表執行逆操作
        for (int i=0; i<=LAST; i++) {
            if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
            state = states.get(i).popFromBackStack(i == LAST, state,
                    firstOutFragments, lastInFragments);
        }
        //通知返回棧監聽器更新
        reportBackStackChanged();
    }
    return true;
}

其中比較復雜的兩步,一步是calculateBackFragments, 還有一個是popFromBackStack,我們先看calculateBackFragments:

為什么需要計算calculateBackFragments? 其實是因為涉及到動畫的方面,在一次在一次transaction中可能會移除和添加多次fragments,這樣動畫的編排就必須按順序來,這次分析中,我們就不對動畫做過多分析了,因為這是相對來說次要點的知識點。所以我們集中關心BackStackRecord 的popFromBackStack函數

public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
            SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
    
    //...動畫相關


    int transitionStyle = state != null ? 0 : mTransitionStyle;
    int transition = state != null ? 0 : mTransition;
    Op op = mTail;
    while (op != null) {
        int popEnterAnim = state != null ? 0 : op.popEnterAnim;
        int popExitAnim= state != null ? 0 : op.popExitAnim;
        switch (op.cmd) {
            case OP_ADD: {
                Fragment f = op.fragment;
                f.mNextAnim = popExitAnim;
                mManager.removeFragment(f,
                        FragmentManagerImpl.reverseTransit(transition), transitionStyle);
            } break;
            case OP_REPLACE: {
                Fragment f = op.fragment;
                if (f != null) {
                    f.mNextAnim = popExitAnim;
                    mManager.removeFragment(f,
                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
                }
                if (op.removed != null) {
                    for (int i=0; i<op.removed.size(); i++) {
                        Fragment old = op.removed.get(i);
                        old.mNextAnim = popEnterAnim;
                        mManager.addFragment(old, false);
                    }
                }
            } break;
            case OP_REMOVE: {
                Fragment f = op.fragment;
                f.mNextAnim = popEnterAnim;
                mManager.addFragment(f, false);
            } break;
            case OP_HIDE: {
                Fragment f = op.fragment;
                f.mNextAnim = popEnterAnim;
                mManager.showFragment(f,
                        FragmentManagerImpl.reverseTransit(transition), transitionStyle);
            } break;
            case OP_SHOW: {
                Fragment f = op.fragment;
                f.mNextAnim = popExitAnim;
                mManager.hideFragment(f,
                        FragmentManagerImpl.reverseTransit(transition), transitionStyle);
            } break;
            case OP_DETACH: {
                Fragment f = op.fragment;
                f.mNextAnim = popEnterAnim;
                mManager.attachFragment(f,
                        FragmentManagerImpl.reverseTransit(transition), transitionStyle);
            } break;
            case OP_ATTACH: {
                Fragment f = op.fragment;
                f.mNextAnim = popEnterAnim;
                mManager.detachFragment(f,
                        FragmentManagerImpl.reverseTransit(transition), transitionStyle);
            } break;
            default: {
                throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
            }
        }

        op = op.prev;
    }

    if (doStateMove) {
        mManager.moveToState(mManager.mCurState,
                FragmentManagerImpl.reverseTransit(transition), transitionStyle, true);
        state = null;
    }

    if (mIndex >= 0) {
        mManager.freeBackStackIndex(mIndex);
        mIndex = -1;
    }
    return state;
}

基本上從多個switch-case分支語句里就可以看出pop執行的就是commit的反操作,因為因為transaction都是成對存在的

  • add <-> remove
  • attach <-> detach
  • show <-> hide
  • replace( remove x n + add x m) <-> replace ( remove x m + add x n)

返回棧的狀態的保存

返回棧狀態的保存,相當于要把整個BackStackRecord的列表保存下來,以便下次恢復,前面提到BackStackRecord,可以跟 BackStackState這個parcelable映射起來。基本上BackStackRecord都是可以parcel的類型,出了Op鏈表,Op鏈表映射為BackStackState的int[] mOps;

看完了返回棧,基本上有關Fragment的核心知識點都了解的差不多了,當然我們也跳過了一些知識點,比如有關動畫的部分、ChildFragmentManager等。但基本上Fragment在使用到遇到問題的部分,都可以從上面的分析中找到原因。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容