上周预热的文章,结果自己打脸了,实在抱歉 (。ŏ_ŏ)。


日常开发中我们经常使用 Fragment 管理布局,使用起来非常方便,但是在简单的 API 背后隐藏了什么操作,很多人恐怕不了解。

如果你回答不出这些问题,那这篇文章可能就对你有些帮助:

  • Fragment FragmentManager FragmentTransaction 的关系和作用

  • Fragment 如何实现布局的添加替换

  • 嵌套 Fragment 的原理

Fragment 的使用

Fragment 的使用大家应该都熟悉,这里举个例子,要实现这样的类似饿了么点餐效果:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

界面丑了点,但意思是差不多的哈,左边一个列表,点击后切换右边的布局。我们就可以使用 Fragment 来实现。

实现也很简单,创建一个的布局,然后在 Activity 里点击时替换 Fragment。

0?wx_fmt=jpeg

代码很简单,核心就三步:

  1. 创建 Fragment

  2. 获取 FragmentManager

  3. 调用事务,添加、替换

我们一步步来了解这背后的故事。

Fragment 大家应该比较熟悉,放到最后。

先来看看FragmentManager

FragmentManager

public abstract class FragmentManager {...}

FragmentManager是一个抽象类,定义了一些和 Fragment 相关的操作和内部类/接口。

定义的操作

FragmentManager 中定义的方法如下:

//开启一系列对 Fragments 的操作
public abstract FragmentTransaction beginTransaction();

//根据 ID 找到从 XML 解析出来的或者事务中添加的 Fragment
//首先会找添加到 FragmentManager 中的,找不到就去回退栈里找
public abstract Fragment findFragmentById(@IdRes int id);

//跟上面的类似,不同的是使用 tag 进行查找
public abstract Fragment findFragmentByTag(String tag);

//弹出回退栈中栈顶的 Fragment,异步执行的
public abstract void popBackStack();

//立即弹出回退栈中栈顶的,直接执行哦
public abstract boolean popBackStackImmediate();

//返回栈顶符合名称的,如果传入的 name 不为空,在栈中间找到了 Fragment,那将弹出这个 Fragment 上面的所有 Fragment
//有点类似启动模式的 singleTask 的感觉
//异步执行
public abstract void popBackStack(String name, int flags);
//...
//获取 manager 中所有添加进来的 Fragment
public abstract List<Fragment> getFragments();

可以看到,定义的方法有很多是异步执行的,后面看看它究竟是如何实现的异步。

内部类/接口:

  • BackStackEntry:Fragment 后退栈中的一个元素

  • onBackStackChangedListener:后退栈变动监听器

  • FragmentLifecycleCallbacks: FragmentManager 中的 Fragment 生命周期监听

//后退栈中的一个元素
public interface BackStackEntry {
//栈中该元素的唯一标识
public int getId();

//获取 FragmentTransaction#addToBackStack(String) 设置的名称
public String getName();
//...
}

可以看到BackStackEntry的接口比较简单,关键信息就是 ID 和 Name。

//在 Fragment 回退栈中有变化时回调
public interface OnBackStackChangedListener {
public void onBackStackChanged();
}
//FragmentManager 中的 Fragment 生命周期监听
public abstract static class FragmentLifecycleCallbacks {
//...
}
}

熟悉 Fragment 生命周期的同学一定觉得很面熟,这个接口就是为我们提供一个 FragmentManager 所有 Fragment 生命周期变化的回调。

小结:

可以看到,FragmentManager是一个抽象类,它定义了对一个 Activity/Fragment 中添加进来的 Fragment 列表Fragment 回退栈的操作、管理。

实现类 FragmentManagerImpl

FragmentManager定义的任务是由FragmentManagerImpl实现的。

主要成员:

final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {

ArrayList<OpGenerator> mPendingActions;
Runnable[] mTmpActions;
boolean mExecutingActions;

ArrayList<Fragment> mActive;
ArrayList<Fragment> mAdded;
ArrayList<Integer> mAvailIndices;
ArrayList<BackStackRecord> mBackStack;
ArrayList<Fragment> mCreatedMenus;
// Must be accessed while locked.
ArrayList<BackStackRecord> mBackStackIndices;
ArrayList<Integer> mAvailBackStackIndices;

ArrayList<OnBackStackChangedListener> mBackStackChangeListeners;
private CopyOnWriteArrayList<Pair<FragmentLifecycleCallbacks, Boolean>> mLifecycleCallbacks;
//...
}

可以看到,FragmentManagerImpl中定义了 添加的、活跃的。以及回退栈的列表,这和 FragmentManager 的要求一致。

int mCurState = Fragment.INITIALIZING;
FragmentHostCallback mHost;
FragmentContainer mContainer;
Fragment mParent;

static Field sAnimationListenerField = null;

boolean mNeedMenuInvalidate;
boolean mStateSaved;
boolean mDestroyed;
String mNoTransactionsBecause;
boolean mHavePendingDeferredStart;

接着还有当前的状态,当前 Fragment 的起始 mParent,以及 FragmentManager 的 mHost 和 mContainer。

FragmentContainer就是一个接口,定义了关于布局的两个方法:

public abstract class FragmentContainer {
@Nullable
public abstract View onFindViewById(@IdRes int id);
public abstract boolean onHasView();
}

FragmentHostCallback就复杂一点了,它提供了 Fragment 需要的信息,也定义了 Fragment 宿主应该做的操作:

public abstract class FragmentHostCallback<E> extends FragmentContainer {
private final Activity mActivity;
final Context mContext;
private final Handler mHandler;
final int mWindowAnimations;
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
//...
}

我们知道,一般来说 Fragment 的宿主就两种:

  1. Activity

  2. Fragment

我们再看看他对FragmentManager定义的关键方法是如何实现的。

@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}

beginTransaction()返回一个新的BackStackRecord,我们后面介绍。

前面提到了,popBackStack()是一个异步操作,它是如何实现异步的呢?

@Override
public void popBackStack() {
enqueueAction(new PopBackStackState(null, -1, 0), false);
}
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 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);
}
}
}

可以看到,调用到最后,是调用宿主中的 Handler 来发送任务的,so easy 嘛。其他的异步执行也是类似,就不赘述了。

后退栈相关方法:

ArrayList<BackStackRecord> mBackStack;
@Override
public int getBackStackEntryCount() {
return mBackStack != null ? mBackStack.size() : 0;
}

@Override
public BackStackEntry getBackStackEntryAt(int index) {
return mBackStack.get(index);
}

可以看到,开始事务和后退栈,返回/操作的都是BackStackRecord,我们来了解了解它是何方神圣。

事务

BackStackRecord继承了FragmentTransaction

final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}

先来看看FragmentTransaction

FragmentTransaction

FragmentTransaction定义了一系列对 Fragment 的操作方法:

//它会调用 add(int, Fragment, String),其中第一个参数传的是 0
public abstract FragmentTransaction add(Fragment fragment, String tag);

//它会调用 add(int, Fragment, String),其中第三个参数是 null
public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);

//添加一个 Fragment 给 Activity 的最终实现
//第一个参数表示 Fragment 要放置的布局 id
//第二个参数表示要添加的 Fragment,【注意】一个 Fragment 只能添加一次
//第三个参数选填,可以给 Fragment 设置一个 tag,后续可以使用这个 tag 查询它
public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment,
@Nullable String tag);

//调用 replace(int, Fragment, String),第三个参数传的是 null
public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment);

//...

对 fragment 的操作基本就这几步,我们知道,要完成对 fragment 的操作,最后还需要提交一下:

0?wx_fmt=jpeg

事务的四种提交方式

事务最终的提交方法有四种:

  1. commit()

  2. commitAllowingStateLoss()

  3. commitNow()

  4. commitNowAllowingStateLoss()

它们之间的特点及区别如下:

public abstract int commit();

commit()在主线程中异步执行,其实也是 Handler 抛出任务,等待主线程调度执行。

注意:
commit()需要在宿主 Activity 保存状态之前调用,否则会报错。
这是因为如果 Activity 出现异常需要恢复状态,在保存状态之后的commit()将会丢失,这和调用的初衷不符,所以会报错。

public abstract int commitAllowingStateLoss();

commitAllowingStateLoss()也是异步执行,但它的不同之处在于,允许在 Activity 保存状态之后调用,也就是说它遇到状态丢失不会报错。

因此我们一般在界面状态出错是可以接受的情况下使用它。

public abstract void commitNow();

commitNow()是同步执行的,立即提交任务。

前面提到FragmentManager.executePendingTransactions()也可以实现立即提交事务。但我们一般建议使用commitNow(), 因为另外那位是一下子执行所有待执行的任务,可能会把当前所有的事务都一下子执行了,这有可能有副作用。

此外,这个方法提交的事务可能不会被添加到 FragmentManger 的后退栈,因为你这样直接提交,有可能影响其他异步执行任务在栈中的顺序。

commit()一样,commitNow()也必须在 Activity 保存状态前调用,否则会抛异常。

public abstract void commitNowAllowingStateLoss();

同步执行的commitAllowingStateLoss()

OK,了解了FragmentTransaction定义的操作,去看看我们真正关心的、beginTransaction()中返回的BackStackRecord:

@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}

事务真正实现/回退栈 BackStackRecord

BackStackRecord既是对 Fragment 进行操作的事务的真正实现,也是 FragmentManager 中的回退栈的实现:

final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}

它的关键成员:

0?wx_fmt=jpeg

可以看到 Op 就是添加了状态和动画信息的 Fragment,mOps就是栈中所有的 Fragment。

事务定义的方法它是如何实现的呢。

先看添加一个 Fragment 到布局add()的实现

@Override
public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
//...

//1.修改添加的 fragmentManager 为当前栈的 manager
fragment.mFragmentManager = mManager;
//...
//2.设置宿主 ID 为布局 ID
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}

//3.构造 Op
Op op = new Op();
op.cmd = opcmd; //状态
op.fragment = fragment;
//4.添加到数组列表中
addOp(op);
}
void addOp(Op op) {
mOps.add(op);
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
}

可以看到添加一个 Fragment 到布局很简单,概况一下就是:
修改 fragmentManager 和 ID,构造成 Op,设置状态信息,然后添加到列表里。

添加完了看看替换replace的实现

@Override
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
return replace(containerViewId, fragment, null);
}

@Override
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
if (containerViewId == 0) {
throw new IllegalArgumentException("Must use non-zero containerViewId");
}

doAddOp(containerViewId, fragment, tag, OP_REPLACE);
return this;
}

太可怕了,也是调用上面刚提到的doAddOp(),不同之处在于第四个参数为OP_REPLACE,看来之前小看了这个状态值!

再看其他方法的实现就很简单了,无非就是构造一个 Op,设置对应的状态值。

@Override
public FragmentTransaction remove(Fragment fragment) {
Op op = new Op();
op.cmd = OP_REMOVE;
op.fragment = fragment;
addOp(op);

return this;
}

@Override
public FragmentTransaction hide(Fragment fragment) {
Op op = new Op();
op.cmd = OP_HIDE;
op.fragment = fragment;
addOp(op);
return this;
}
//...
}

那这些状态值的不同是什么时候起作用的呢?

别忘了我们操作 Fragment 还有最后一步,提交。

看看这两个是怎么实现的:

@Override
public int commit() {
return commitInternal(false);
}

@Override
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
//...
mManager.enqueueAction(this, allowStateLoss); //异步任务入队
return mIndex;
}
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
//...
mPendingActions.add(action);
scheduleCommit(); //发送任务
}
}
private void scheduleCommit() {
synchronized (this) {
//...
if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}

前面已经介绍过了,FragmentManager.enqueueAction()最终是使用 Handler 实现的异步执行。

现在的问题是执行的任务是啥?

答案就是 Handler 发送的任务mExecCommit:

Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions();
}
};

代码多了一点,但我们终于找到了最终的实现:Handler 异步发到主线,调度执行后,聚合、修改 Ops 的状态,然后遍历、修改 Fragment 栈中的 View 的状态。

##真正处理的部分

前面主要是对 Fragment 的包装类 Ops 进行一些状态修改,真正根据 Ops 状态进行操作在这个部分:

void executeOps() {
final int numOps = mOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
final Fragment f = op.fragment;
f.setNextTransition(mTransition, mTransitionStyle);
switch (op.cmd) {
case OP_ADD:
f.setNextAnim(op.enterAnim);
mManager.addFragment(f, false);
break;
case OP_REMOVE:
f.setNextAnim(op.exitAnim);
mManager.removeFragment(f);
break;
case OP_HIDE:
f.setNextAnim(op.exitAnim);
mManager.hideFragment(f);
break;
//...
}
if (!mAllowOptimization && op.cmd != OP_ADD) {
mManager.moveFragmentToExpectedState(f);
}
}
if (!mAllowOptimization) {
mManager.moveToState(mManager.mCurState, true);
}
}

FragmentManager 对这些方法的实现也很简单,修改 Fragment 的状态值,比如remove(Fragment):

public void removeFragment(Fragment fragment) {
//...
if (!fragment.mDetached || inactive) {
if (mAdded != null) {
mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
fragment.mAdded = false; //设置属性值
fragment.mRemoving = true;
}
}

最终会调用moveToState(),我们直接来看它的实现:

0?wx_fmt=jpeg

代码很长,但做的事情很简单:

  1. 根据状态调用对应的生命周期方法

  2. 如果是新创建的,就把布局添加到 ViewGroup 中

Fragment 是什么

Fragment 是什么,从官网、别人博客上看到的都是他人之言,我们还是得去看源码才能得到答案。

public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener {...}

可以看到,Fragment 没有继承任何类,只是实现了这两个接口,第二个不太重要,第一个是在内存不足时可以收到回调。

没有什么特别信息,我们还是去看看它的主要成员。

Fragment 的主要成员

0?wx_fmt=jpeg

一堆标志位和状态值。然后就是关键的成员了:

0?wx_fmt=jpeg

看到这里,结合前面的,我们就清晰了一个 Fragment 的创建、添加过程:

onCreateView()中返回一个 布局,然后在 FragmentManager 中拿到这个布局,添加到要绑定容器(Activity/Fragment)的 ViewGroup 中,然后设置相应的状态值。

生命周期方法

Fragment 的生命周期大家都清楚,一张很清晰的图:

0?wx_fmt=jpeg

总共 11 个方法,这里我们看一下各个方法的具体源码。
(公众号中略去)

总结

OK,看完这篇文章,相信对开头提出的问题你已经有了答案,这里再总结一下。

Fragment、FragmentManager、FragmentTransaction 关系


Fragment

  • 其实是对 View 的封装,它持有 view, containerView, fragmentManager, childFragmentManager 等信息


FragmentManager

  • 是一个抽象类,它定义了对一个 Activity/Fragment 中添加进来的 Fragment 列表Fragment 回退栈的操作、管理方法

  • 还定义了获取事务对象的方法

  • 具体实现在 FragmentImpl 中


FragmentTransaction

  • 定义了对 Fragment 添加、替换、隐藏等操作,还有四种提交方法

  • 具体实现是在 BackStackRecord 中


Fragment 如何实现布局的添加替换

通过获得当前 Activity/Fragment 的 FragmentManager/ChildFragmentManager,进而拿到事务的实现类 BackStackRecord,它将目标 Fragment 构造成 Ops(包装Fragment 和状态信息),然后提交给 FragmentManager 处理。

如果是异步提交,就通过 Handler 发送 Runnable 任务,FragmentManager 拿到任务后,先处理 Ops 状态,然后调用moveToState()方法根据状态调用 Fragment 对应的生命周期方法,从而达到 Fragment 的添加、布局的替换隐藏等。

下面这张图从下往上看就是一个 Fragment 创建经历的方法:

0?wx_fmt=png

嵌套 Fragment 的原理


也比较简单,Fragment 内部有一个 childFragmentManager,通过它管理子 Fragment。

在添加子 Fragment 时,把子 Fragment 的布局 add 到父 Fragment 即可。


考虑公众号字数限制及阅读体验,这里只贴出了关键部分代码,完整代码欢迎去原文地址在 PC 端阅读。

0?wx_fmt=gif

更多相关文章

  1. Android studio Run 出现 红叉 AndroidRunConfigurationType 问
  2. . io .IOException:setDataSource失败了。:状态= 0 x80000000
  3. 如何在Log中模拟方法e
  4. fragment 状态保存时怎么执行一些需要在onResume、onPause方法里
  5. 同时兼容高低版本的setBackground跟setTextColor方法
  6. 四极管:I2CTools编译方法
  7. Android全屏显示,去除标题栏和状态栏
  8. 检查ArrayList是否只包含null值的方法。
  9. 不同Android版本设备正确获取屏幕分辨率的通用方法

随机推荐

  1. 面试官:高并发下重启服务,接口调用老是超时
  2. 串匹配(朴素模式匹配算法)
  3. 假期结束了,是时候收收心准备过年了
  4. Canvas 动画的性能优化实践
  5. 《趣说前端 - 002 》— 两种作用域模型介
  6. 贞炸了!上线之后,消息收不到了!
  7. 【干货】BAT大佬告诉你如何埋点
  8. Vuex 注入 Vue 生命周期的过程
  9. 熔断器 Hystrix 源码解析 —— 请求执行(
  10. 数据库读写分离这个坑,让刚入职的我一脸懵