【背上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 的返回栈需要调用 addToBackStack("")
方法
在 从源码角度看 Fragment 生命周期 一文中,我们提到了 FragmentTransaction ,它是一个「事务」的模型,事务可以回滚到之前的状态。所以当触发返回操作时,就是将之前提交的事务进行回滚。
FragmentTransaction
的实现类为 BackStackRecord
,所以 fragment 的返回栈其实存放的就是 BackStackRecord
作为返回栈的元素,BackStackRecord 实现了FragmentManager.BackStackEntry 接口
从 BackStackRecord
的定义我们可以发现 BackStackRecord
有三种身份
FragmentTransaction
,即是事务,保存了整个事务的全部操作FragmentManager.BackStackEntry
,作为回退栈的元素OpGenerator
,可以生成 BackStackRecord
列表,后文详细介绍我们已经知道 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 操作
FragmentManager
中提供了popBackStack
系列方法
是否觉得很眼熟?提交事务也有类似的api,commit 系列方法
这里分别提供了同步和异步的方法,可能有读者会疑惑,同样是对事务的操作,一个为提交,一个为回滚,为什么一个封装到了 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)
//flag 为 POP_BACK_STACK_INCLUSIVE 弹出包括该元素及及以上的元素
childFragmentManager.popBackStack("♥", androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE)
在分析返回栈源码之前我们回顾一下 FragmentManager 提交事务到 fragment 各个生命周期的流程
下面我们看看 popBackStack 的源码
等等,这个 enqueueAction 有些眼熟…
看来提交事务和回滚事务的流程基本是相同的,只是传递的 action 不同
由源码可知,OpGenerator
是一个接口,其内只有一个 generateOps
方法,用于生成事务列表以及对应的该事务是否是弹出的。有两个实现类
由此可见 commit 调用的为 BackStackRecord
的 generateOps
方法,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
上文我们有提到,「返回」逻辑实际上就是执行提交事务内部操作逻辑的「逆操作」
那么接下的逻辑就很清晰了,根据不同的 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之OnBackPressedDispatcher】Fragment 返回栈预备篇 一文中我们介绍了 OnBackPressedDispatcher
activity 的 onBackPressed
的逻辑主要分为两部分,判断所有注册的 OnBackPressedCallback
是否有 enabled 的,如果有则拦截,不执行后续逻辑;
否则着执行 mFallbackOnBackPressed.run() ,其内部逻辑为调用 ComponentActivity 父类的 onBackPressed
方法
所以我们只需看 mOnBackPressedCallbacks(ArrayDeque
经过查找我们发现它是在 FragmentManager 的 attachController 调用 addCallback
mOnBackPressedDispatcher.addCallback(owner,mOnBackPressedCallback)
进而执行了
而 mOnBackPressedCallback
在初始化时 enabled 赋值为 false
isEnadbled
会在返回栈数量大于 0 且其 mParent 为 PrimaryNavigation
时赋值为true
而返回栈(mBackStack
)的赋值在 BackStackRecord
的 generateOps
方法中,且是否添加到返回栈由 mAddToBackStack
这个布尔类型的属性控制
mAddToBackStack 的赋值在 addToBackStack 方法中,这也解释了为何调用 addToBackStack 方法就能将事务加入返回栈
我们来总结一下,fragment 拦截 activity 返回栈是通过
OnBackPressedDispatcher
实现的,如果开启事务调用了addToBackStack
方法,则mOnBackPressedCallback
的isEnabled
属性会赋值为 true,进而起到拦截 activity 返回逻辑的作用。拦截后执行popBackStackImmediate
方法而 popBackStack系列方法会调用 popBackStackState 构造
records
和isRecordPop
列表,isRecordPop
的内部元素的值均为true 后续流程和提交事务是一样的,根据isRecordPop
值的不同选择执行executePopOps
或executeOps
方法
Ian Lake 在 Fragments: Past, Present, and Future (Android Dev Summit '19)
有提到未来会提供多返回栈的 api
那么以现有的 api 如何实现多返回栈呢?
首先我要弄清楚怎样才会有多返回栈,根据上文我们知道 FragmentManager
内部持有mBackStack
list,这对应着一个返回栈,如果想要实现多返回栈,则需要多个 FragmentManager,而多 FragmentManager
则对应多个 fragment
因此我们可以创建多个宿主 frament 作为导航 fragment 这样就可以用不同的宿主 fragment 的 独立的FragmentManager
分别管理各自的返回栈,如果这样说比较抽象,可以参考下图
图中有四个返回栈,其中最外部有一个宿主 fragment ,内部有四个负责导航的 fragment 管理其内部的返回栈,外部的宿主负责协调各个返回栈为空后如何切换至其他返回栈
单返回栈就很容易了,我们只需在同一个 FragmentManager
上添加返回栈即可
详情参照 demo
我是 Fly_with24
掘金
简书
Github