1. 程式人生 > >原始碼分析commitAllowingStateLoss() 和commit()的區別

原始碼分析commitAllowingStateLoss() 和commit()的區別

之前在使用Fragment的時候偶爾會有這麼一個報錯,Can not perform this action after onSaveInstanceState,意思為無法再onSaveInstanceState之後執行該操作,這個操作就是指commit(),之前也沒怎麼在意,後來通過檢視原始碼去了解了一下這個問題,以下是對這個問題的解析及對應解決辦法的對比。

        Fragment是我們經常用到的東西,常用的操作有新增(add)、移除(remove)、替換(replace)等,這些操作組成一個集合transaction,我們在通過呼叫getSupportFragmentManager().beginTransaction()來獲取這個FragmentTransaction類的例項來管理這些操作,將他們存進由activity管理的back stack中,這樣我們就可以進行fragment變化的回退操作,也可以這樣去獲取FragmentTransaction類的例項:

FragmentManager  mFragmentManager = getSupportFragmentManager();   FragmentTransaction  mFragmentTransaction = mFragmentManager.beginTransaction();           為什麼我們會有這種報錯呢,因為我們在使用add(),remove(),replace()等方法將Fragment的變化新增進去,然後在通過commit去提交這些變化(另外,在commit之前可以去呼叫addToBackState()方法,將這些變化加入到activity管理的back stack中去,這樣使用者呼叫返回鍵就可以回退這些變化了),提交完成之後這些變化就會應用到我們的Fragment中去。但是,這個commit()方法,你只能在avtivity儲存他的狀態之前呼叫,也就是onSaveInstanceState(),我們都知道activity有一個儲存狀態的方法和恢復狀態的方法,這個就不詳細解釋了,在onSaveInstanceState()方法之後去呼叫commit(),就會丟擲我們遇到的這個異常,這是因為在onSaveInstanceState()之後呼叫commit()方法,這些變化就不會被activity儲存,即這些狀態會被丟失,但我們可以去用commitAllowingStateLoss()這個方法去代替commit()來解決這個為題,下面我們通過原始碼去看這兩個方法的區別。         首先從我們獲取FragmentTransaction類的例項開始,即getSupportFragmentManager(),原始碼是這樣的:

    /**      * Return the FragmentManager for interacting with fragments associated      * with this activity.      */     public FragmentManager getSupportFragmentManager() {         return mFragments;     }         而這個返回的mFragments是一個FragmentManagerImpl類 的例項,他繼承自FragmentManager這個類:     final FragmentManagerImpl mFragments = new FragmentManagerImpl();         我們在FragmentManager這個類中還看到beginTransaction()這個抽象方法,開啟他的實現類 final class FragmentManagerImpl extends FragmentManager {       ... ...       @Override     public FragmentTransaction beginTransaction() {         return new BackStackRecord(this);     }       .... ...   }         我們看到這個實現類中的該方法是返回一個BackStateRecord類的實體,我們繼續去追蹤這個類,就會發現,這個類其實是繼承自FragmentTransaction的,並且,我們在這裡看到我們熟悉的方法: final class BackStackRecord extends FragmentTransaction implements         FragmentManager.BackStackEntry, Runnable {       public BackStackRecord(FragmentManagerImpl manager) {         mManager = manager;     }       public int commit() {         return commitInternal(false);     }       public int commitAllowingStateLoss() {         return commitInternal(true);     }          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;     }   }         終於找到了我們有用的東西了,這裡省略了其他不必要的程式碼,只留下我們需要用的核心程式碼,有興趣可以自己去檢視原始碼,這裡還有省略掉我們常用的add、remove、replace等方法,迴歸正題,看我們要找的兩個方法commit()和commitAllowingStateLoss(),他們都呼叫了commitInternal(boolean allowStateLoss)這個方法,只是傳入的引數不同,我們去看commitInternal方法,該方法首先去判斷是否已經commit,這個簡單,直接跳過,最後他執行的是FragmentTransactionImpl類的enqueueAction方法,好,不要嫌麻煩,我們繼續去追蹤這個方法:     public void enqueueAction(Runnable action, boolean allowStateLoss) {         if (!allowStateLoss) {             checkStateLoss();         }         synchronized (this) {             if (mActivity == null) {                 throw new IllegalStateException("Activity has been destroyed");             }             if (mPendingActions == null) {                 mPendingActions = new ArrayList<Runnable>();             }             mPendingActions.add(action);             if (mPendingActions.size() == 1) {                 mActivity.mHandler.removeCallbacks(mExecCommit);                 mActivity.mHandler.post(mExecCommit);             }         }     }         通過這個方法我們可以看到,他首先去根據commit和commitAllowingStateLoss這兩個方法傳入的引數不同去判斷,然後將任務扔進activity的執行緒佇列中,這裡我們重點去看的是checkStateLoss()這個方法:     private void checkStateLoss() {         if (mStateSaved) {             throw new IllegalStateException(                     "Can not perform this action after onSaveInstanceState");         }         if (mNoTransactionsBecause != null) {             throw new IllegalStateException(                     "Can not perform this action inside of " + mNoTransactionsBecause);         }     }         這個方法很簡單,就只是一個簡單的判斷而已,並且只有呼叫commit方法才會執行這裡,commitAllowingStateLoss則直接跳過這步,這裡我們呼叫commit方法時,系統系判斷狀態(mStateSaved)是否已經儲存,如果已經儲存,則丟擲"Can not perform this action after onSaveInstanceState"異常,這就是我們遇到的問題了,而用commitAllowingStateLoss方法則不會這樣,這就與我們之前分析的activity去儲存狀態對應上了,在activity儲存狀態完成之後呼叫commit時,這個mStateSaved就是已經儲存狀態,所以會丟擲異常。         長篇大論終於講完了,其實回頭一看並沒有多麼複雜,就跟著原始碼一步一步去找,就會找到我們發生錯誤的地方,看了原始碼之後發現,原來並沒有多麼難,so easy!哈哈