【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo

系列文章

【背上Jetpack】Jetpack 主要组件的依赖及传递关系

【背上Jetpack】AdroidX下使用Activity和Fragment的变化

【背上Jetpack之Fragment】你真的会用Fragment吗?Fragment常见问题以及androidx下Fragment的使用新姿势

【背上Jetpack之Fragment】从源码角度看 Fragment 生命周期 AndroidX Fragment1.2.2源码分析

【背上Jetpack之OnBackPressedDispatcher】Fragment 返回栈预备篇

前言

上一篇 我们介绍了 OnBackPressedDispather ,那么今天我们来正式地从源码的角度看看 fragment 的返回栈吧。由于其主流程和生命周期差不多,因此本文将详细地分析返回栈相关的源码,并插入大量源码。建议将生命周期流程熟悉后阅读本文。文末提供单返回栈和多返回栈的 demo

如果您对 activity 对任务栈和返回栈不是很了解,可以移步 Tasks and the Back Stack

小问号你是否有很多朋友?

在分析源码之前,我们先来思考几个问题。

  • 返回栈中的元素是什么?
  • 谁来管理 fragment 的返回栈?
  • 如何返回?

返回栈中的元素是什么?

返回栈,顾名思义,是一个栈结构。所以我们要搞清楚,这个栈结构到底存的是什么。

我们都知道,使用 fragment 的返回栈需要调用 addToBackStack("") 方法

在 从源码角度看 Fragment 生命周期 一文中,我们提到了 FragmentTransaction ,它是一个「事务」的模型,事务可以回滚到之前的状态。所以当触发返回操作时,就是将之前提交的事务进行回滚。

FragmentTransaction 的实现类为 BackStackRecord ,所以 fragment 的返回栈其实存放的就是 BackStackRecord

作为返回栈的元素,BackStackRecord 实现了FragmentManager.BackStackEntry 接口

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第1张图片

BackStackRecord 的定义我们可以发现 BackStackRecord 有三种身份

  • 继承了 FragmentTransaction,即是事务,保存了整个事务的全部操作
  • 实现了 FragmentManager.BackStackEntry ,作为回退栈的元素
  • 实现了OpGenerator ,可以生成 BackStackRecord 列表,后文详细介绍

谁来管理 fragment 的返回栈?

我们已经知道 fragment 的返回栈其实存放的是 BackSrackRecord , 那么谁来管理 fragment 的返回栈?

FragmentManager 用于管理 fragment ,所以 fragment 返回栈也应该由 FragmentManager 管理

//FragmentManager.java
ArrayList<BackStackRecord> mBackStack;

其实触发 fragment 的返回逻辑有两种途径

  • 开发主动调用 fragment 的返回方法

  • 用户按返回键触发

后文我们会从这两个角度分析一下 fragment 中的返回栈逻辑究竟是怎样的

如何返回?

我们已经知道返回栈中的元素是 BackStackRecord ,也清楚了是 FragmentManager 来管理返回栈。那么如果让我们来实现「返回」逻辑,应该如何做?

首先我们要清楚所谓的「返回」是对事务的回滚,即 对 commit 事务的内部逻辑执行相应的「逆操作」

例如

addFragment←→removeFragment

showFragment←→hideFragment

attachFragment←→detachFragment

有的小伙伴可能会疑惑 replace 呢?

expandReplaceOps 方法会把 replace 替换(目标 fragment 已经被 add )成相应的 remove 和 add 两个操作,或者(目标 fragment 没有被 add )只替换成 add 操作

popBackStack 系列方法

FragmentManager 中提供了popBackStack 系列方法

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第2张图片

是否觉得很眼熟?提交事务也有类似的api,commit 系列方法

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第3张图片

这里分别提供了同步和异步的方法,可能有读者会疑惑,同样是对事务的操作,一个为提交,一个为回滚,为什么一个封装到了 FragmentManager 中,一个却在 FragmentTransaction 中。既然都是对事务的操作,应该都放在FragmentManager 中。我认为可能为了api使用的方便,使得 FragmentManager 开启事务的链式调用一气呵成。各位有什么想法欢迎在评论区留言。

这里主要介绍一下 popBackStack(String name, int flag)

name 为 addToBackStack(String name) 的参数,通过 name 能找到回退栈的特定元素,flag可以为 0 或者FragmentManager.POP_BACK_STACK_INCLUSIVE,0 表示只弹出该元素以上的所有元素,POP_BACK_STACK_INCLUSIVE 表示弹出包含该元素及以上的所有元素。这里说的弹出所有元素包含回退这些事务。如果这么说比较抽象的话,看图

//flag 传入0,弹出 ♥2 上的所有元素
childFragmentManager.popBackStack("♥", 0)

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第4张图片

//flag 为 POP_BACK_STACK_INCLUSIVE 弹出包括该元素及及以上的元素
childFragmentManager.popBackStack("♥",  androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE)

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第5张图片

走进源码

1. popBackStack() 逻辑

在分析返回栈源码之前我们回顾一下 FragmentManager 提交事务到 fragment 各个生命周期的流程

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第6张图片

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第7张图片

下面我们看看 popBackStack 的源码

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第8张图片

等等,这个 enqueueAction 有些眼熟…

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第9张图片

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第10张图片

看来提交事务和回滚事务的流程基本是相同的,只是传递的 action 不同

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第11张图片

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第12张图片

由源码可知,OpGenerator 是一个接口,其内只有一个 generateOps 方法,用于生成事务列表以及对应的该事务是否是弹出的。有两个实现类

OpGenerator实现类

由此可见 commit 调用的为 BackStackRecordgenerateOps 方法,popBackStack 调用的是 PopBackStackState 中的 generateOps

前者的逻辑很简单,向 records list 中添加数据, isRecordPop list 全部传入 false

records.add(this);
isRecordPop.add(false);

后者的逻辑稍微复杂些,其内部调用了 popBackStackState 方法

如果是 popBackStack 方法 ,则将 FragmentManager 的返回栈列表(mBackStack)的栈顶移除, isRecordPop list 全部传入 true

int last = mBackStack.size() - 1;
records.add(mBackStack.remove(last));
isRecordPop.add(true);

如果传入的 name 或 id 有值,且 flag 为 0,则找到返回栈(倒序遍历)中第一个符合 name 或 id 的位置,并将该位置上方的所有 BackStackRecord 并添加到 record list 中,同时 isRecordPop list 全部传入 true

index = mBackStack.size() - 1;
while (index >= 0) {
    BackStackRecord bss = mBackStack.get(index);
    if (name != null && name.equals(bss.getName())) {
        break;
    }
    if (id >= 0 && id == bss.mIndex) {
        break;
    }
    index--;
}

for (int i = mBackStack.size() - 1; i > index; i--) {
  records.add(mBackStack.remove(i));
  isRecordPop.add(true);
}

如果传入的 name 或 id 有值,且 flag 为 POP_BACK_STACK_INCLUSIVE,则在上一条获取位置的基础上继续遍历,直至栈底或者遇到不匹配的跳出循环,接着出栈所有 BackStackRecord

//index 操作与上方相同,先找到返回栈(倒序遍历)中第一个符合 name 或 id 的位置
if ((flags & POP_BACK_STACK_INCLUSIVE) != 0) {
    index--;
    // 继续遍历 mBackStack 直至栈底或者遇到不匹配的跳出循环
    while (index >= 0) {
        BackStackRecord bss = mBackStack.get(index);
        if ((name != null && name.equals(bss.getName()))
                || (id >= 0 && id == bss.mIndex)) {
            index--;
            continue;
        }
        break;
    }
}
//后续出栈逻辑与上方相同

可以配合上面的动图理解

入栈和出栈后续的逻辑大体是相同的,只是根据 isPop 的正负出现了分支,出栈调用的是 executePopOps

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第13张图片

上文我们有提到,「返回」逻辑实际上就是执行提交事务内部操作逻辑的「逆操作」

那么接下的逻辑就很清晰了,根据不同的 mCmd 执行相应的逆操作

void executePopOps(boolean moveToState) {
    for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
        final Op op = mOps.get(opNum);
        Fragment f = op.mFragment;
        switch (op.mCmd) {
            case OP_ADD:
                mManager.removeFragment(f);
                break;
            case OP_REMOVE:
                mManager.addFragment(f);
                break;
            case OP_HIDE:
                mManager.showFragment(f);
                break;
            case OP_SHOW:
                mManager.hideFragment(f);
                break;
            case OP_DETACH:
                mManager.attachFragment(f);
                break;
            case OP_ATTACH:
                mManager.detachFragment(f);
                break;
            case OP_SET_PRIMARY_NAV:
                mManager.setPrimaryNavigationFragment(null);
                break;
            case OP_UNSET_PRIMARY_NAV:
                mManager.setPrimaryNavigationFragment(f);
                break;
            case OP_SET_MAX_LIFECYCLE:
                mManager.setMaxLifecycle(f, op.mOldMaxState);
                break;
            default:
                throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
        }
        if (!mReorderingAllowed && op.mCmd != OP_REMOVE && f != null) {
            mManager.moveFragmentToExpectedState(f);
        }
    }
    if (!mReorderingAllowed && moveToState) {
        mManager.moveToState(mManager.mCurState, true);
    }
}

后面的逻辑就完全一样了

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第14张图片

2. fragment 是怎样拦截 activity 的返回逻辑的?

在 【背上Jetpack之OnBackPressedDispatcher】Fragment 返回栈预备篇 一文中我们介绍了 OnBackPressedDispatcher

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第15张图片

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第16张图片

activity 的 onBackPressed 的逻辑主要分为两部分,判断所有注册的 OnBackPressedCallback 是否有 enabled 的,如果有则拦截,不执行后续逻辑;

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第17张图片

否则着执行 mFallbackOnBackPressed.run() ,其内部逻辑为调用 ComponentActivity 父类的 onBackPressed 方法

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第18张图片

所以我们只需看 mOnBackPressedCallbacks(ArrayDeque

经过查找我们发现它是在 FragmentManager 的 attachController 调用 addCallback

 mOnBackPressedDispatcher.addCallback(owner,mOnBackPressedCallback)

进而执行了

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第19张图片
【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第20张图片

mOnBackPressedCallback 在初始化时 enabled 赋值为 false

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第21张图片

isEnadbled 会在返回栈数量大于 0 且其 mParent 为 PrimaryNavigation 时赋值为true

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第22张图片

而返回栈(mBackStack)的赋值在 BackStackRecordgenerateOps 方法中,且是否添加到返回栈由 mAddToBackStack 这个布尔类型的属性控制

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第23张图片

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第24张图片

mAddToBackStack 的赋值在 addToBackStack 方法中,这也解释了为何调用 addToBackStack 方法就能将事务加入返回栈

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第25张图片

我们来总结一下,fragment 拦截 activity 返回栈是通过 OnBackPressedDispatcher 实现的,如果开启事务调用了 addToBackStack 方法,则 mOnBackPressedCallbackisEnabled 属性会赋值为 true,进而起到拦截 activity 返回逻辑的作用。拦截后执行 popBackStackImmediate 方法

而 popBackStack系列方法会调用 popBackStackState 构造 recordsisRecordPop 列表,isRecordPop 的内部元素的值均为true 后续流程和提交事务是一样的,根据 isRecordPop 值的不同选择执行 executePopOpsexecuteOps 方法

单返回栈和多返回栈的实现

Ian Lake 在 Fragments: Past, Present, and Future (Android Dev Summit '19)

有提到未来会提供多返回栈的 api

【背上Jetpack之Fragment】从源码的角度看Fragment 返回栈 附多返回栈demo_第26张图片

那么以现有的 api 如何实现多返回栈呢?

首先我要弄清楚怎样才会有多返回栈,根据上文我们知道 FragmentManager 内部持有mBackStack list,这对应着一个返回栈,如果想要实现多返回栈,则需要多个 FragmentManager,而多 FragmentManager 则对应多个 fragment

因此我们可以创建多个宿主 frament 作为导航 fragment 这样就可以用不同的宿主 fragment 的 独立的FragmentManager 分别管理各自的返回栈,如果这样说比较抽象,可以参考下图

图中有四个返回栈,其中最外部有一个宿主 fragment ,内部有四个负责导航的 fragment 管理其内部的返回栈,外部的宿主负责协调各个返回栈为空后如何切换至其他返回栈

单返回栈就很容易了,我们只需在同一个 FragmentManager 上添加返回栈即可

详情参照 demo


关于我


我是 Fly_with24

  • 掘金

  • 简书

  • Github

你可能感兴趣的:(Jetpack)