简析 addToBackStack使用和Fragment执行流程

在使用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记录过度使用资源时,就可以加入回退栈。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-05 09:37:00

简析 addToBackStack使用和Fragment执行流程的相关文章

SylixOS中TPSFS格式化流程简析

1.TPSFS简介 TPSFS文件系统是一款掉电安全的文件系统,该系统是SylixOS内建文件系统(专利技术),该文件系统有如下特点: 采用B+树存储文件数据,读取与定位速度快,空间管理效率高: 对数据使用原子操作,掉电安全: 64位文件系统,支持EP级别文件长度: 大文件处理性能好: 支持文件记录锁,可支持大型数据库: 支持多块分配机制,提高了系统性能,使得分配器有了充足的优化空间: 支持子目录可扩展性,使得在一个目录下可以创建无数多个子目录. TPSFS文件系统结构如图 1.1所示. 图 1

Pig源码分析: 简析执行计划的生成

摘要 本文通过跟代码的方式,分析从输入一批Pig-latin到输出物理执行计划(与launcher引擎有关,一般是MR执行计划,也可以是Spark RDD的执行算子)的整体流程. 不会具体涉及AST如何解析.如何使用了Anltr.逻辑执行计划如何映射.逻辑执行计划如何优化.MR执行计划如何切分为MR Job,而是从输入一批Pig DSL到待执行的真正执行计划的关键变化步骤(方法和类). 执行计划完整解析 入口处书Main类的main函数 /** * The Main-Class for the

0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日志.判断用户是否登录等. 2.简单示例 2.1.继承 HandlerInterceptorAdapter 抽象类实现一个拦截器.代码如下: public class DemoInterceptor extends HandlerInterceptorAdapter { @Override    pu

Android WebView远程代码执行漏洞简析

0x00 本文参考Android WebView 远程代码执行漏洞简析.代码地址为,https://github.com/jltxgcy/AppVulnerability/tree/master/WebViewFileDemo.下面我们分析代码. 0x01 首先列出项目工程目录: MainActivity.java的代码如下: public class MainActivity extends Activity { private WebView webView; private Uri mUr

NETGEAR 系列路由器命令执行漏洞简析

NETGEAR 系列路由器命令执行漏洞简析 2016年12月7日,国外网站exploit-db上爆出一个关于NETGEAR R7000路由器的命令注入漏洞.一时间,各路人马开始忙碌起来.厂商忙于声明和修复,有些人忙于利用,而我们则在复现的过程中寻找漏洞的本质. 一.漏洞简介 1.漏洞简介 2016年12月7日,NETGEAR R7000路由器在exploit-db上被爆出存在远程命令执行漏洞,随着安全研究人员的不断深入,R8000和R6400这两款路由器也被证实有同样的问题. 2016年12月1

Android -- Camera源码简析,启动流程

com.android.camera.Camera.java,主要的实现Activity,继承于ActivityBase. ActivityBase 在ActivityBase中执行流程: onCreate中进行判断是否是平板: onResume中判断是否锁屏,锁屏&camera不存在时候,mOnResumePending置为true,否则置为false并执行doOnResume: onWindowFocusChanged中判断是否获取到焦点&mOnResumePending,满足的话执行

web应用构架LAMT及tomcat负载简析

Httpd    (mod_jk.so) workers.properties文件 uriworkermap.properties文件 <--AJP1.3--> Tomcat  --> jdk 大致流程:apache服务器通过mod_jk.so 模块处理jsp文件的动态请求.通过tomcat worker等待执行servlet/JSP的tomcat实例.使用 AJP1.3协议与tomcat通信.tomcat有借助jdk解析. 负载就是 多台tomcat.共同解析apache发送的jsp请

Android -- MediaPlayer内部实现简析

Android -- MediaPlayer内部实现简析 在之前的博客中,已经介绍了使用MediaPlayer时要注意的内容.现在,这里就通过一个MediaPlayer代码实例,来进一步分析MediaPlayer内部是如何运作.实现的:当然这里的分析只截止到底层调用播放器之前,因为播放器这块实在是没搞懂. 我们使用的例子来源于之前MediaPlayer Playback译文中的官方实例: String url = "http://........"; // your URL here

简析 .NET Core 构成体系

简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代码开发.部署.运行过程 总结 前文介绍了.NET Core 在整个.NET 平台所处的地位,以及与.NET Framework的关系(原文链接),本文将详细介绍.NET Core 框架的构成和各模块主要功能,以及如何实现跨平台. 上图描述了 .NET Core的系统构成,最上层是应用层,是开发基于UI应用的