zoukankan      html  css  js  c++  java
  • 从源码看commit和commitAllowingStateLoss方法区别

    Fragment介绍

    在很久以前,也就是我刚开始写Android时(大约在2012年的冬天……),那时候如果要实现像下面微信一样的Tab切换页面,需要继承TabActivity,然后使用TabHost,在TabHost中添加子Activity来实现

    微信

    现在大家都知道,我们一般情况下会使用FragmentActivity加Fragment来实现,Fragment是Android 3.0新增的,另外我们的support v4包也提供能Fragment的支持,所以现在在所有版本的SDK中我们都可以使用Fragment。Fragment是Activity的一部分,其中一个很重要的需要大家掌握的就是关于Fragment的生命周期,当然这次我们不会讨论这个问题,不过提供一个图片供大家参考,图片来自xxv/android-lifecycle

    Complete Android Fragment & Activity Lifecycle

    从使用开始

    FragmentManager fm = getSupportFragmentManager();
    FragmentTransaction ft = fm.beginTransaction();
    ft.hide(firstStepFragment);
    if (secondStepFragment==null){
        ft.add(R.id.fl_content, secondStepFragment);
    }else {
        ft.show(secondStepFragment);
    }
    ft.commit();
    

    一般我们会这样动态使用Fragment,从代码可以明显体现出这个功能是通过事务的方式执行的,但在一些情况下,我们执行commit()时,会出现异常,例如stackoverflow上的一个报错,解决办法很简单,用commitAllowingStateLoss方法代替commit即可。那这个异常是怎么产生的呢?今天我们从源码来看看它的发生

    逐步参看源码

    从FragmentActivity的getSupportFragmentManager方法开始:

    public class FragmentActivity  {
        final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
        // ……
        public FragmentManager getSupportFragmentManager() {
            return mFragments.getSupportFragmentManager();
        }
        // ……                            
    }
    
    public class FragmentController {
        private final FragmentHostCallback<?> mHost;
        public FragmentManager getSupportFragmentManager() {
            return mHost.getFragmentManagerImpl();
        }
    }
    
    public abstract class FragmentHostCallback<E> extends FragmentContainer {
        FragmentManagerImpl getFragmentManagerImpl() {
            return mFragmentManager;
        }
    }
    

    所以我们的FragmentTransaction是从FragmentManager的实现类FragmentManagerImpl的中的方法返回的,我们看一看FragmentManagerImpl源码中的beginTransaction方法:

    final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
        @Override
        public FragmentTransaction beginTransaction() {
            return new BackStackRecord(this);
        }
    }
    

    可以看到,返回的是一个BackStackRecord,并且每一次调用都是最新实例化的,等下我们会看到,BackStackRecord的commit方法只能执行一次,或者会抛出一个异常。现在我们看一下我们关注的BackStackRecord的一些源码:

    final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, Runnable {
    
        final FragmentManagerImpl mManager;
    
        static final class Op {
            Op next;
            Op prev;
            int cmd;
            Fragment fragment;
            int enterAnim;
            int exitAnim;
            int popEnterAnim;
            int popExitAnim;
            ArrayList<Fragment> removed;
        }
    
        public BackStackRecord(FragmentManagerImpl manager) {
            mManager = manager;
        }
    
        @Override
        public FragmentTransaction add(Fragment fragment, String tag) {
            doAddOp(0, fragment, tag, OP_ADD);
            return this;
        }
    
        @Override
        public FragmentTransaction add(int containerViewId, Fragment fragment) {
            doAddOp(containerViewId, fragment, null, OP_ADD);
            return this;
        }
    
        @Override
        public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
            doAddOp(containerViewId, fragment, tag, OP_ADD);
            return this;
        }
    
            @Override
        public FragmentTransaction remove(Fragment fragment) {
            Op op = new Op();
            op.cmd = OP_REMOVE;
            op.fragment = fragment;
            addOp(op);
    
            return this;
        }
    
        @Override
        public FragmentTransaction hide(Fragment fragment) {
            Op op = new Op();
            op.cmd = OP_HIDE;
            op.fragment = fragment;
            addOp(op);
    
            return this;
        }
    
        @Override
        public FragmentTransaction show(Fragment fragment) {
            Op op = new Op();
            op.cmd = OP_SHOW;
            op.fragment = fragment;
            addOp(op);
    
            return this;
        }
    
        private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
            final Class fragmentClass = fragment.getClass();
            final int modifiers = fragmentClass.getModifiers();
            if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
                    || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
                throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                        + " must be a public static class to be  properly recreated from"
                        + " instance state.");
            }
    
            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 (containerViewId == View.NO_ID) {
                    throw new IllegalArgumentException("Can't add fragment "
                            + fragment + " with tag " + tag + " to container view with no id");
                }
                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);
        }
    
        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++;
        }
    
        @Override
        public int commit() {
            return commitInternal(false);
        }
    
        @Override
        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);
                P 大专栏  从源码看commit和commitAllowingStateLoss方法区别rintWriter 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、hide、show中的哪一个方法,最终都会执行addOp方法,这个方法会生成一个双向链表的数据结果,具体的对象就是Op,对于不同的方法,Op中的cmd这个值是不一样的。大致的流程是这样的,我们调用add、remove、hide、show等方法后,会生成不同的操作命令,然后这些操作命令形成一个双向链表,其中任何一个操作命令,我们都可以知道它的前一个和后一个是什么命令。

    最关键的部分来了,我们的commit和commitAllowingStateLoss也出现了,可以看到,最终两个方法都会调用commitInternal方法,只是传入的参数不同,我们也可以看到commitInternal方法的第一句有一个判断,也就是上面我们提到的,如果再执行事务的commit或者commitAllowingStateLoss方法,会抛出一个IllegalStateException("commit already called")异常,这也是我们会经常遇见的,所以们在使用Fragment时,每一次都需要调用beginTransaction方法生成新的事务,然后再commit,不能同一个事务commit两次

    接着往下看,刚刚看到commit和commitAllowingStateLoss唯一的不同就是在调用commitInternal时,传入的参数不同,而在commitInternal方法中,用到了这个参数的是这个方法的倒数第二句代码:mManager.enqueueAction(this, allowStateLoss);,mManager就是我们的FragmentManagerImpl,我们看看这个类中的enqueueAction方法干了什么:

    final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
    
        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().post(mExecCommit);
                }
            }
        }
    
        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);
            }
        }
    }
    

    报错的地方出来了,就是在我们checkStateLoss方法中,因为执行commit方法时传入的参数为false,所以会执行checkStateLoss,在checkStateLoss方法中会抛出两个异常,一个是因为mStateSaved为true,一个是因为mNoTransactionsBecause不为空,那么接下来我们就分别看一下为什么会出现这两种情况

    mStateSaved什么时候为true

    checkStateLoss方法中mStateSaved只要为true,我们调用commit就会抛出异常,所以寻找问题就很简单了,看看什么情况下mStateSaved的值会被赋为true。通过查看FragmentManagerImpl的源码,这两个方法被执行时,mStateSaved被赋为了true:

    static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11;
    
    Parcelable saveAllState() {
        execPendingActions();
        if (HONEYCOMB) {
            mStateSaved = true;
        }
    
        // 下面的代码省略……
    }
    
    public void dispatchStop() {
        mStateSaved = true;
        moveToState(Fragment.STOPPED, false);
    }
    

    那么什么时候会执行FragmentManagerImpl的这两个方法呢,通过查看,它们都是在我们的FragmentActivity的生命周期函数中被调用的:

    public class FragmentActivity{
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            Parcelable p = mFragments.saveAllState();
            if (p != null) {
                outState.putParcelable(FRAGMENTS_TAG, p);
            }
            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);
            }
        }
    
        @Override
        protected void onStop() {
            super.onStop();
    
            mStopped = true;
            mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
    
            mFragments.dispatchStop();
        }
    }
    

    一个是FragmentActivity的onSaveInstanceState方法,它被执行后,只要是Android3.0以后都会将mStateSaved赋为true,当onStop方法执行时,mStateSaved在任何情况下都会被赋为true,我们先暂停一下看看另一个异常

    mNoTransactionsBecause什么时候不为空

    一般情况下,我们的mNoTransactionsBecause的值一直都为null,只有当我们使用了Loader时,mNoTransactionsBecause才可能会被赋值,具体的代码就不再像上面那样这么细的看了,大家有兴趣可以参阅相关源码,不过我们需要道Loader是个什么东西,才能更好的理解,大家可以看看这篇文章,讲的比较详细和清楚。

    为什么commit会抛出异常

    刚才我们看了异常的抛出的具体位置和引发条件,那么为什么commit会抛出异常呢,而commitAllowingStateLoss不会呢?我们都知道Activity在资源不足的情况下会被销毁,在销毁之前,会调用onSaveInstanceState,将fragments、views等保存下来,当Activity再被创建时,可以将保存的状态取出来重新装载Activity的状态,当onSaveInstanceState执行后,Activity的状态保存下来了,这个时候我们再调用commit,这个FragmentTransaction事务不会被保存下来,Android为了避免丢失,就给我抛出了一个异常,当然我们可以不在乎这个丢失,所以可以调用commitAllowingStateLoss方法。那么另外一个异常的原因呢?看完上面我提到的那篇文章,你应该知道Loader是为了供我们去异步访问一些数据,而上面的mNoTransactionsBecause代表了Loader的不同状态,如果在执行异步操作,我们commit,新的状态和Loader执行完的状态可能不是预期的,所以这时Android也会抛出一个异常IllegalStateException("Can not perform this action inside of " + mNoTransactionsBecause)

  • 相关阅读:
    五层原理体系结构的简单分析
    Simple Factory 简单工厂模式(静态工厂)
    css一个图片包含多个图片|网站侧栏导航
    百度地图、高德地图的数据从哪里得到的?
    浏览器开发
    开发一款浏览器内核需要学习哪些方面的知识?
    使用PowerDesigner进行数据库建模入门
    How to create a search engine
    合并两个有序数组
    STL中的algorithm
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12014380.html
Copyright © 2011-2022 走看看