四种commit使用细节及源码分析——Fragment(二)

一、 每个事务(FragmentTranscation)只能被commit一次

介绍

代码段一
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
        transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
        transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
    }
}

如果运行上面的代码,是会报错的,因为每个事务只能提交一次

 Caused by: java.lang.IllegalStateException: commit already called

每个提交都重新创建一个事务,是可以正常运行的,例如下面的代码,

代码段二
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
    }
}

源码分析:

在FragmentManager.java类中找到如下函数,

代码段三
    @Override
    public FragmentTransaction beginTransaction() {
        return new BackStackRecord(this);
    }

原来,我们开启的每一个事务都是一个回退栈记录(BackStackRecord是FragmentTransaction的一个具体实现类)

在BackStackRecord.java 类中,我们看到四种commit的函数,后面的章节,将会对着四种进行比较分析。我们接着分析为什么一个事务只能提交一次。

代码段四
    @Override
    public int commit() {
        return commitInternal(false);
    }

    @Override
    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }

    @Override
    public void commitNow() {
        disallowAddToBackStack();
        mManager.execSingleAction(this, false);
    }

    @Override
    public void commitNowAllowingStateLoss() {
        disallowAddToBackStack();
        mManager.execSingleAction(this, true);
    }

commit()会调用commitInternal(),

代码段五
    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;
    }

在第3行代码,如果mCommitted为true则会抛出异常。在第10行,mCommitted=true。整个源码中对mCommitted进行赋值的地方仅此1处,第一次提交mCommitted置为true,第二次提交就会抛出异常,所以一个事务只能被提交一次

二、 Activity执行完onSaveInstanceState()方法后不能再执行commit()方法

我想手动控制Fragment的添加显示,在Activity被onDestroy的时候将Fragment移除掉(代码如下)

代码段六
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), "ContentFragment").commit();
    }

    @Override
    protected void onDestroy() {
        getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag("ContentFragment")).commit();
        super.onDestroy();
    }
}

运行下程序,发现退出时程序崩溃了…我们得到如下崩溃日志:

Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

意思是:不能在onSaveInstanceState()被执行之后调用commit()

源码分析:

Android 3.0(SDK>=11)以后的Activity,知道如何管理Fragment,也就是Activity继承了FragmentActivity,在FragmentActivity.java类中,看看在activity销毁前执行的函数,onSaveInstanceState()

代码段七·
    /**
     * Save all appropriate fragment state.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState();
        ...
    }

mFragments 是一个FragmentController类型的变量,于是来到了FragmentController.java找到saveAllState()

代码段八
    public Parcelable saveAllState() {
        return mHost.mFragmentManager.saveAllState();
    }

接下来到FragmentManager.java中看看saveAllState()

代码段九
    static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11;
    Parcelable saveAllState() {
        ...
        if (HONEYCOMB) {
            // As of Honeycomb, we save state after pausing.  Prior to that
            // it is before pausing.  With fragments this is an issue, since
            // there are many things you may do after pausing but before
            // stopping that change the fragment state.  For those older
            // devices, we will not at this point say that we have saved
            // the state, so we will allow them to continue doing fragment
            // transactions.  This retains the same semantics as Honeycomb,
            // though you do have the risk of losing the very most recent state
            // if the process is killed...  we'll live with that.
            mStateSaved = true;
        }
        ...

如果是大于等于11版本的安卓,会执行mStateSaved = true;

继续回到commit()函数的分析,可以看到最终执行commitInternal(),然后执行enqueueAction()函数

代码段十
    public void enqueueAction(OpGenerator 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<>();
            }
            mPendingActions.add(action);
            scheduleCommit();
        }
    }
代码段十一
    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);
        }
    }

代码的逻辑明白了,

但是为什么要这么做呢?

原因是在Android 11版本后的activity会管理fragment,也就是说在activity 在执行onSaveInstanceState()的时候会保存fragment的状态,用于在恢复activity的时候使用。那么在保存fragment的状态后,就不能再去改变fragment的状态,也就是不允许提交事务,这样才能保障activity恢复的时候,可以显示原来fragment的状态。

三、commit() 和commitAllowingStateLoss()

commitAllowingStateLoss() 提交事务,允许状态丢失,就是在onSaveInstanceState()保存了状态后,这时候再次发生的提交,这些新的状态是不会被保存的。

源码分析

在代码段三可知道,commit() 和commitAllowingStateLoss() 这两个函数会调用commitInternal(),只是传入的参数不一样,前者是false,后者是true。

这个参数用于判断,在enqueueAction()(代码段十)中是否执行checkStateLoss(),如果不执行就可以正常提交,但是状态可能会丢失。

使用commitAllowingStateLoss(),不会执行checkStateLoss(),所以可能造成状态丢失

状态丢失

如果在onSaveInstanceState()之后,调用的是commitAllowingStateLoss(),是丢失什么状态,在什么情况下丢失呢?
可能会丢掉FragmentManager的状态, 即onSaveInstanceState之后任何被添加或被移除的Fragments.

举例说明:

  1. 在Activity里显示一个FragmentA;
  2. 然后Activity放在后台, onStop()和onSaveInstanceState()被调用;
  3. 在某个事件触发下, 你用FragmentB replace FragmentA , 使用的是 commitAllowingStateLoss().

这时候, 用户再返回应用, 可能会有两种情况发生:

  1. 如果系统杀死了activity,activity将会重建, 使用上述步骤2保存的状态, 所以A会显示, B不会显示;
  2. 如果系统没有杀死activity, 会被提到前台, FragmentB就会显示出来, 到下次Activity stop的时候, 这个包含了B的状态就会被存

(上述测试可以利用开发者选项中的”Don’t Keep Activities”选项).

四、commit(), commitNow() 和 executePendingTransactions()

一个事务的提交到执行,经历了如下的流程:
commit() >>enqueueAction()>>scheduleCommit()>>execPendingActions()

代码段十二
    private void scheduleCommit() {
        synchronized (this) {
            boolean postponeReady =
                    mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
            boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
            if (postponeReady || pendingReady) {
                mHost.getHandler().removeCallbacks(mExecCommit);
                mHost.getHandler().post(mExecCommit);
            }
        }
    }
代码段十三
    Runnable mExecCommit = new Runnable() {
        @Override
        public void run() {
            execPendingActions();
        }
    };

事务最终的执行是在execPendingActions()中被执行

  • 调用commit并不是立即执行的, 它会被发送到主线程的任务队列当中去, 当主线程准备好执行它的时候执行.

  • popBackStack()的工作也是这样, 发送到主线程任务队列中去. 也即说它们都是异步的.

  • 在commit()调用之后加上 executePendingTransactions(),这个事务会立即执行, 即变异步为同步.

代码段十四
    @Override
    public boolean executePendingTransactions() {
        boolean updates = execPendingActions();
        forcePostponedTransactions();
        return updates;
    }

在executePendingTransactions()中直接调用了execPendingActions();可以立即执行事务

五、commitNow()和commitNowAllowingStateLoss()

在代码段三可知道,commitNow()和commitNowAllowingStateLoss()都调用了execSingleAction()。execSingleAction()函数内的执行和execPendingActions()内的执行差不多,就是去执行这个事务

support library从v24.0.0开始提供了 commitNow 方法, 之前用executePendingTransactions()会将所有pending在队列中还有你新提交的transactions都执行了, 而commitNow 将只会执行你当前要提交的transaction. 所以commitNow 避免你会不小心执行了那些你可能并不想执行的transactions.

不能加入回退栈

不能对要加在back stack中的transaction使用commitNow(), 即addToBackStack()和commitNow()不能同时使用。为什么呢?

如果你有一个提交使用了commit(), 紧接着又有另一个提交使用了commitNow(), 两个都想加入back stack, 那back stack会变成什么样呢? 到底是哪个transaction在上, 哪个在下? 答案将是一种不确定的状态, 因为系统并没有提供任何保证来确保顺序, 所以系统决定干脆不支持这个操作。

前面提过popBackStack()是异步的, 所以它同样也有一个同步的兄弟popBackStackImmediate().

实际应用的时候怎么选择呢?

  1. 如果你需要同步操作, 并且你不需要加到back stack里, 使用commitNow().
    support library在FragmentPagerAdapter里就使用了commitNow()来保证在更新结束的时候, 正确的页面被加上或移除.
  2. 如果你操作很多transactions, 并且不需要同步, 或者你需要把transactions加在back stack里, 那就使用commit().
  3. 如果想在某一时间, 执行所有的transactions, 那么使用executePendingTransactions().

源码分析:

在代码四种的,commitNow()和commitNowAllowingStateLoss()都有调用disallowAddToBackStack(),

代码段十五
    @Override
    public FragmentTransaction disallowAddToBackStack() {
        if (mAddToBackStack) {
            throw new IllegalStateException(
                    "This transaction is already being added to the back stack");
        }
        mAllowAddToBackStack = false;
        return this;
    }

在加入回退栈的函数,会进行判断,是否允许加入回退栈

    @Override
    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;
    }

参考:
https://www.jianshu.com/p/f50a1d7ab161
https://www.jianshu.com/p/d9143a92ad94
https://www.cnblogs.com/mengdd/p/5827045.html

发布了244 篇原创文章 · 获赞 799 · 访问量 234万+
展开阅读全文

Fragment already added 接手别人的代码,出现这个问题,不知道如何 更改

07-30

接手别人的代码,出现这个问题,不知道如何改,求助。界面是有四个fragment,通过底部的四个图片切换四个fragment,出现这个问题,主要代码如下,求帮助: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); page_num = intent.getIntExtra("page_num", 0); setContentView(R.layout.main_activity); ButterKnife.inject(this); setSwipeBackEnable(false); queue = VolleyQuery.getQueue(this); intidrawable(); getLocal(); intiView(page_num); //设置导航点击方法 nav_mshop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { slider_animate(0); } }); nav_apply.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { slider_animate(1); } }); nav_join.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { slider_animate(2); } }); nav_mine.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { slider_animate(3); } }); if (page_num == 0) { getNewVersion("auto"); } } //滑动动画&&选择fragment private void slider_animate(int postion) { for (int i = 0; i < 4; i++) { LinearLayout linearLayout = (LinearLayout) nav_container.getChildAt(i); TextView textView = (TextView) linearLayout.getChildAt(1); ImageView imageView = (ImageView) linearLayout.getChildAt(0); if (postion == i) { textView.setTextColor(getResources().getColorStateList(R.color.nav_text_pink)); imageView.setImageDrawable(drawables[i][0]); } else { textView.setTextColor(getResources().getColorStateList(R.color.nav_text_gra)); imageView.setImageDrawable(drawables[i][1]); } } if (mfilst.get(postion) == null) { switch (postion) { case 0: MshopFragment mshopFragment = new MshopFragment(); mfilst.put(postion, mshopFragment); break; case 1: ApplyFragment applyFragment = new ApplyFragment(); mfilst.put(postion, applyFragment); break; case 2: LeagueFragment leagueFragment = new LeagueFragment(); mfilst.put(postion, leagueFragment); break; case 3: MyFragment myFragment = new MyFragment(); mfilst.put(postion, myFragment); break; } } FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); if (!mfilst.get(postion).isAdded()) { try { fragmentTransaction.add(R.id.main_container, mfilst.get(postion), String.valueOf(postion)); } catch (Exception e) { e.printStackTrace(); } } for (int i = 0; i < 4; i++) { if (i != postion) { if (mfilst.get(i) != null && mfilst.get(i).isAdded()) { fragmentTransaction.hide(mfilst.get(i)); } } } fragmentTransaction.show(mfilst.get(postion)); fragmentTransaction.commit(); } boolean addList = false; @Override protected void onResume() { super.onResume(); this.addList = false; } @Override protected void onPause() { super.onPause(); } private void intiView(int page_num) { slider_animate(page_num); } private void intidrawable() { drawables[0][0] = getResources().getDrawable(R.mipmap.main_mshop_bottom_pink); drawables[0][1] = getResources().getDrawable(R.mipmap.main_mshop_bottom_gra); drawables[1][0] = getResources().getDrawable(R.mipmap.main_apply_bottom_pink); drawables[1][1] = getResources().getDrawable(R.mipmap.main_apply_bottom_gra); drawables[2][0] = getResources().getDrawable(R.mipmap.main_join_bottom_pink); drawables[2][1] = getResources().getDrawable(R.mipmap.main_join_bottom_gra); drawables[3][0] = getResources().getDrawable(R.mipmap.main_mine_bottom_pink); drawables[3][1] = getResources().getDrawable(R.mipmap.main_mine_bottom_gra); } 错误日志: Caused by: java.lang.IllegalStateException: Fragment already added: LeagueFragment{42e18a48 #1 id=0x7f0a00bb 2} at android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1207) at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:673) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1499) at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:456) at android.os.Handler.handleCallback(Handler.java:615) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4881) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:808) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:575) at dalvik.system.NativeStart.main(Native Method) 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 代码科技 设计师: Amelia_0503

分享到微信朋友圈

×

扫一扫,手机浏览