文章来源:https://blog.csdn.net/wenxuzl99/article/details/16112725

在使用Fragment的时候我们一般会这样写:

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.content_view, fragment, fragment.getClass().getName());
        // transaction.addToBackStack(null);
        transaction.commitAllowingStateLoss();

对于是否要加transaction.addToBackStack(null);也就是将Fragment加入到回退栈。官方的说法是取决于你是否要在回退的时候显示上一个Fragment。

虽然知道这回事,但是在做项目的时候还是没有清楚的认识,只是习惯性的加上addToBackStack;查看源码后才了解到该神马时候加入回退栈。

首先看

void addBackStackState(BackStackRecord state) {
        if (mBackStack == null) {
            mBackStack = new ArrayList<BackStackRecord>();
        }
        mBackStack.add(state);
        reportBackStackChanged();
    }

可以看出,我们并不是将Fragment加入到回退栈,而是加了一个叫BackStackRecord的实例;那这个BackStackRecord到底是什么,简单的说一个BackStackRecord记录了一次操作。

final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, Runnable

backstackRecord继承了FragmentTransaction抽象类,获得了诸如add,remove,replace这些控制方法,所以我们控制Fragment时一直使用的getSupportFragmentManager().beginTransaction()其实就是返回一个BackStackRecord实例;

backstackRecord也维护了一个Op对象,Op对象的作用就是记录一次操作的动作和Fragment引用以及操作使用的动画;

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

最后backstackRecord也实现了Runnable接口,通过commit来启动自身,在run方法中又根据维护的Op对象进行不同的操作。其实不同的Fragment操作就是在启动不同的BackStatcRecord线程。

下面我们已一次transaction.add操作为例:此操作也就是调用BackStackRecord里的add方法,方法中维护一个Op来保存这次add操作和相应的Fragment;然后我们会调用commit方法来提交操作,实质上是启动实现了Runnable接口的BackStackRecord自身,在run方法中根据Op执行add分支的操作,这里面我们会调用FragmentManager的addFragment方法

public void run() {
            ......
            switch (op.cmd) {
                case OP_ADD: {
                    Fragment f = op.fragment;
                    f.mNextAnim = op.enterAnim;
                    mManager.addFragment(f, false);
                } break;

......

mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true);

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

}

注意方法的最后会根据mAddToBackStack标识来判断是否加入到回退栈。

接下来在FragmentManager的addFragment方法中

public void addFragment(Fragment fragment, boolean moveToStateNow) {
        if (mAdded == null) {
            mAdded = new ArrayList<Fragment>();
        }
        if (DEBUG) Log.v(TAG, "add: " + fragment);
        makeActive(fragment);                                   //通过此方法将fragment加入到一个mActive列表里。
        if (!fragment.mDetached) {
            if (mAdded.contains(fragment)) {
                throw new IllegalStateException("Fragment already added: " + fragment);
            }
            mAdded.add(fragment);
            fragment.mAdded = true;
            fragment.mRemoving = false;
            if (fragment.mHasMenu && fragment.mMenuVisible) {
                mNeedMenuInvalidate = true;
            }
            if (moveToStateNow) {
                moveToState(fragment);
            }
        }
    }

像上面注释里说的,通过makeActive方法将fragment加入到一个mActive列表。这个列表在后面会用到。但现在先来看看代码里用蓝色标记的两个方法,这是两个方法名相同的重载方法,他们最后都会调用一个非常重要的方法:moveToState

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {
       ......
        if (f.mState < newState) {
           ......
            switch (f.mState) {
                case Fragment.INITIALIZING:
                    ......
                case Fragment.CREATED:
                    if (newState > Fragment.CREATED) {
                    ......
                    }
                case Fragment.ACTIVITY_CREATED:
                case Fragment.STOPPED:
                    if (newState > Fragment.STOPPED) {
                        f.performStart();
                    }
                case Fragment.STARTED:
                    if (newState > Fragment.STARTED) {
                       ......
                        f.performResume();
                       ......
                    }
            }
        } else if (f.mState > newState) {
            switch (f.mState) {
                case Fragment.RESUMED:
                    if (newState < Fragment.RESUMED) {
                       ......
                        f.performPause();
                        ......
                    }
                case Fragment.STARTED:
                    if (newState < Fragment.STARTED) {
                        f.performStop();
                    }
                case Fragment.STOPPED:
                    if (newState < Fragment.STOPPED) {
                        f.performReallyStop();
                    }
                case Fragment.ACTIVITY_CREATED:
                    if (newState < Fragment.ACTIVITY_CREATED) {
                       ......
                    }
                case Fragment.CREATED:
                    if (newState < Fragment.CREATED) {
                        ......
                                f.performDestroy();
                        ......
                        }
                    }
            }
        }
        
        f.mState = newState;
    }

对于这个方法要说明三点:

第一:方法里所有的分支只有

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.

这六种状态,好像不太够。其实这很好理解,如果传来的新状态比fragment的当前状态大那就是处于创建过程,如果新状态比当前状态小那就是处于关闭过程。闭上眼睛想一想就能转过弯儿了!!!

第二:这里面所有的case分支都是没有break方法的,这样就能保证传来一个状态就能把这个状态之后的所有操作都执行一遍,例如创建时传INITIALIZING状态,就能执行INITIALIZING、CREATED、ACTIVITY_CREATED、STOPPED、STARTED这一流程的代码,而不需要我们挨个的每个状态都传;又例如我们重写回到fragment要调用start()方法,那只需要传STOPPED(创建时执行的是onStart)就可以,而不需要再传STARTED(创建时执行的是onResume)。

第三:代码中的红色部分会调用FragmentActivity里的dispatchActivityXXX 方法,这里面最终会调用另外一个重要方法,它也叫做moveToState(其实这个方法最终也是会去调用上面的moveToState方法):

void moveToState(int newState, int transit, int transitStyle, boolean always) {
        ......
            for (int i=0; i<mActive.size(); i++) {
                Fragment f = mActive.get(i);
                if (f != null) {
                    moveToState(f, newState, transit, transitStyle, false);
                    if (f.mLoaderManager != null) {
                        loadersRunning |= f.mLoaderManager.hasRunningLoaders();
                    }
                }
            }

if (!loadersRunning) {
                startPendingDeferredFragments();
            }
        }
    }

这里面有个for循环,它会根据前面提到的mActive列表来调用存储fragment的moveToState方法(是上面的那个moveToState)。所以如果我们使用show、hide而不是用add、remove来操作fragment显示与隐藏的话,就会发现一个问题,假设一个FragmentActivity已经创建了三个fragment并且隐藏,然后它在创建第四个fragment的时候,会发现已经隐藏的三个fragment也都运行了onresume方法。这就是因为这三个fragment已经加入到mActive中,并且在创建第四个的时候循环调用了他们的resume方法。

现在回到最开始的问题,为什么说加入回退栈就可以实现按返回键退回到上一个fragment界面:

这就要看FragmentActivity里面的回退方法了

public void onBackPressed() {
        if (!mFragments.popBackStackImmediate()) {
            finish();
        }
    }

关键在判断条件,也就是popBackStackImmediate()方法的实现和他的返回值:

他的返回值是由popBackStackState(mActivity.mHandler, null, -1, 0)提供的(注意参数是固定的)

boolean popBackStackState(Handler handler, String name, int id, int flags) {
        if (mBackStack == null) {
            return false;
        }
        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);
            bss.popFromBackStack(true);
            reportBackStackChanged();
        } else {
          ......
        }
        return true;
    }

注意方法的第一个判断条件:如果mBackStack  == null 就直接return false,这样就会直接执行FragmentActivity的finishi()方法,这也就是当我们不添加addToBackStack方法时按返回键不会返回上一个fragment界面而是直接退出程序的原因了。

若添加了addToBackStack方法,也就是mBackStack != null 的情况下,根据固定的参数会进入蓝色代码段,在这里取出回退栈列表中的最后一条BackStackReco记录并执行它的popFromBackStack方法:

在这个方法里会根据BackStackRecord维护的Op对象来执行相应的操作,以replace操作为例:

case OP_REPLACE: {
                    Fragment f = op.fragment;
                    if (f != null) {
                        f.mNextAnim = op.popExitAnim;
                        mManager.removeFragment(f,
                                FragmentManagerImpl.reverseTransit(mTransition),
                                mTransitionStyle);
                    }
                    if (op.removed != null) {
                        for (int i=0; i<op.removed.size(); i++) {
                            Fragment old = op.removed.get(i);
                            old.mNextAnim = op.popEnterAnim;
                            mManager.addFragment(old, false);
                        }
                    }
                } break;

从中可以清除的看出是把Op的当前fragment给remove掉,再把Op保存的old fragment给add上,这样一来就会显示上一个界面了。

所以,根据不同的情景,当我们不需要fragment会退到上一个界面或者管理的fragment过多而不想保留BackStackRecord记录过度使用资源时,就可以加入回退栈。

Android-addToBackStack使用和Fragment执行流程的更多相关文章

  1. 简析 addToBackStack使用和Fragment执行流程

    在使用Fragment的时候我们一般会这样写: FragmentTransaction transaction = getSupportFragmentManager().beginTransacti ...

  2. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),G ...

  3. android invalidate 执行流程详解

    invalidate()函数的主要作用是请求View树进行重绘,该函数可以由应用程序调用,或者由系统函数间接 调用,例如setEnable(), setSelected(), setVisiblity ...

  4. Android系统Recovery工作原理之使用update.zip升级过程---updater-script脚本语法简介以及执行流程(转)

    目前update-script脚本格式是edify,其与amend有何区别,暂不讨论,我们只分析其中主要的语法,以及脚本的流程控制. 一.update-script脚本语法简介: 我们顺着所生成的脚本 ...

  5. Android开发第一讲之目录结构和程序的执行流程

    1.如何在eclipse当中,修改字体 下面的这种办法,可以更改xml的字体 窗口--首选项--常规--外观--颜色和字体--基本--文本字体--编辑Window --> Preferences ...

  6. Android系统Recovery工作原理之使用update.zip升级过程分析(九)---updater-script脚本语法简介以及执行流程【转】

    本文转载自:http://blog.csdn.net/mu0206mu/article/details/7465603       Android系统Recovery工作原理之使用update.zip ...

  7. 040 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 02 while循环的执行流程

    040 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 02 while循环的执行流程 本文知识点:while循环的执行流程 三种循环结构中的第一种--wh ...

  8. 003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程

    003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程 Java程序长啥样? 首先编写一个Java程序 记事本编写程序 打开记事本 1.wi ...

  9. 【Android UI】:Fragment官方文档

    概述   Fragment表现Activity中UI的一个行为或者一部分.可以将多个fragment组合在一起,放在一个单独的activity中来创建一个多界面区域的UI,并可以在多个activity ...

随机推荐

  1. No change while using CSS <form target="blank">

    I want to open another Window when I use <form> to request a query. So I used target="bla ...

  2. STL学习笔记(六) 函数对象

    条款38:遵循按值传递的原则来设计仿函数 仿函数都是 pass-by-value Function for_each(InputIterator first, InputIterator last, ...

  3. excel 表格lookup 的操作

    这个实现语句如下,(写在2行然后向下拖动)(2007或以上版本): = IF(,ISBLANK($H2),$G2="), "", IF("), IF($H2&g ...

  4. HDU 4405: Aeroplane chess

    类型:概率DP 题意:一条直线下飞行棋,色子六个面等概率.同时存在一些飞机航线,到了某个点可以直接飞到后面的另一个点,可以连飞,保证一个点至多一条航线.求到达或者超过终点 所需要 掷色子的期望次数. ...

  5. 安装sass的艰难过程

    1.ruby安装 因为sass依赖于ruby环境,所以装sass之前先确认装了ruby.先导官网下载个ruby 在安装的时候,请勾选Add Ruby executables to your PATH这 ...

  6. 问题:Linux 输入任何命令都显示 -bash: fork: Cannot allocate memory

    应该是某个程序吃掉了所有的内存,只能重启

  7. jq 全选、反选、判断选中的条数

    1.全选或全不选.当勾选全选按钮#selectAll旁边的复选框#all时,列表中的选项全部选中,反之取消勾选则列表中的选项全部为未选中状态. $("#all").click(fu ...

  8. Codeforces 934 A.Compatible Pair

    http://codeforces.com/contest/934 A. A Compatible Pair   time limit per test 1 second memory limit p ...

  9. Codeforces Gym 100338C Important Roads 最短路+Tarjan找桥

    原题链接:http://codeforces.com/gym/100338/attachments/download/2136/20062007-winter-petrozavodsk-camp-an ...

  10. SilverLight:基础控件使用(3)-DataGrid控件

    ylbtech-SilverLight-Basic-Control:基础控件使用(3)-DataGrid控件 DataGrid控件-后台绑定 自动生成表列 不自动生成表列 1.A,返回顶部Person ...