android面试之fragment,当你面试的时候,被问到关于Fragment的种种

前言

不知道你们都没有自己特别的学习的方法,我是有吧所有的整理成笔记的习惯

比如今天讲解的关于Fragment的我会做成笔记

由于文章有些地方代码过于太长了繁琐,所以部分省略掉了,敲了一下午眼睛和手脖子都酸了,至于省略的部分,对这些笔记,面试内容感兴趣的可以看笔记研究,欢迎留言

35979b3f904ce01efaf5925afc84428a.png

相关内容后续GitHub更新,想冲击金三银四的小伙伴可以找找看看,欢迎star

(顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)

https://github.com/xiangjiana/Android-MS

一丶Fragment 的使用

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

mFragmentManager = getSupportFragmentManager();

mFragmentManager.beginTransaction()

.replace(R.id.fl_content, fragment)

.commitAllowingStateLoss();

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

创建 Fragment

获取 FragmentManager

调用事务,添加、替换

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

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

先来看看 FragmentManager 。

####二丶 FragmentManager

public abstract class FragmentManager {...}

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

2.1.定义的操作

FragmentManager 中定义的方法如下:

//开启一系列对 Fragments 的操作

public abstract FragmentTransaction beginTransaction();

//FragmentTransaction.commit() 是异步执行的,如果你想立即执行,可以调用这个方法

public abstract boolean executePendingTransactions();

//根据 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();

......

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

2.2.内部类/接口:

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

onBackStackChangedListener:后退栈变动监听器

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

//后退栈中的一个元素

public interface BackStackEntry {

//栈中该元素的唯一标识

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

@StringRes

public int getBreadCrumbTitleRes();

@StringRes

public int getBreadCrumbShortTitleRes();

public CharSequence getBreadCrumbTitle();

public CharSequence getBreadCrumbShortTitle();

}

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

//在 Fragment 回退栈中有变化时回调

public interface OnBackStackChangedListener {

public void onBackStackChanged();

}

//FragmentManager 中的 Fragment 生命周期监听

public abstract static class FragmentLifecycleCallbacks {

public void onFragmentPreAttached(FragmentManager fm, Fragment f, Context context) {}

public void onFragmentAttached(FragmentManager fm, Fragment f, Context context) {}

public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {}

public void onFragmentActivityCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {}

public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v, Bundle savedInstanceState) {}

public void onFragmentStarted(FragmentManager fm, Fragment f) {}

public void onFragmentResumed(FragmentManager fm, Fragment f) {}

public void onFragmentPaused(FragmentManager fm, Fragment f) {}

public void onFragmentStopped(FragmentManager fm, Fragment f) {}

public void onFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle outState) {}

public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {}

public void onFragmentDestroyed(FragmentManager fm, Fragment f) {}

public void onFragmentDetached(FragmentManager fm, Fragment f) {}

}

}

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

小结:

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

2.3.实现类 FragmentManagerImpl

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

主要成员:

final class FragmentManagerImpl extends FragmentManager implements

LayoutInflaterFactory {

ArrayList mPendingActions;

Runnable[] mTmpActions;

boolean mExecutingActions;

ArrayList mActive;

ArrayList mAdded;

ArrayList mAvailIndices;

ArrayList mBackStack;

ArrayList mCreatedMenus;

// Must be accessed while locked.

ArrayList mBackStackIndices;

ArrayList mAvailBackStackIndices;

ArrayList mBackStackChangeListeners;

private CopyOnWriteArrayList> mLifecycleCallbacks;

//...

}

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

61b5302b65ba9e46cb47da0c7bff005e.png

接着还有当前的状态,当前 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 extends FragmentContainer {

private final Activity mActivity;

final Context mContext;

private final Handler mHandler;

final int mWindowAnimations;

final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();

//...

}

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

Activity

Fragment

比如 FragmentActivity 的内部类 HostCallbacks 就实现了这个抽象类:

class HostCallbacks extends FragmentHostCallback {

public HostCallbacks() {

super(FragmentActivity.this /*fragmentActivity*/);

}

//...

@Override

public LayoutInflater onGetLayoutInflater() {

return

FragmentActivity.this.getLayoutInflater().cloneInContext(FragmentActivity.t his);

}

@Override

public FragmentActivity onGetHost() {

return FragmentActivity.this;

}

......

}

我们再看看他对 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 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 。

3.1.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

//这一个方法等价于先调用 remove(), 再调用 add() public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment, @Nullable String tag);

//移除一个已经存在的 fragment

//如果之前添加到宿主上,那它的布局也会被移除

public abstract FragmentTransaction remove(Fragment fragment);

//隐藏一个已存的 fragment

//其实就是将添加到宿主上的布局隐藏

public abstract FragmentTransaction hide(Fragment fragment);

//显示前面隐藏的 fragment,这只适用于之前添加到宿主上的 fragment

public abstract FragmentTransaction show(Fragment fragment);

//将指定的 fragment 将布局上解除

//当调用这个方法时,fragment 的布局已经销毁了

public abstract FragmentTransaction detach(Fragment fragment);

//当前面解除一个 fragment 的布局绑定后,调用这个方法可以重新绑定

//这将导致该 fragment 的布局重建,然后添加、展示到界面上

public abstract FragmentTransaction attach(Fragment fragment);

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

mFragmentManager.beginTransaction()

.replace(R.id.fl_child, getChildFragment())

// .commit()

.commitAllowingStateLoss();

2.2.事务的四种提交方式

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

commit()

commitAllowingStateLoss()

commitNow()

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);

}

3.3事务真正实现/回退栈 BackStackRecord

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

final class BackStackRecord extends

FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}

它的关键成员:

final FragmentManagerImpl mManager;

//Op 可选的状态值

static final int OP_NULL = 0;

static final int OP_ADD = 1;

static final int OP_REPLACE = 2;

static final int OP_REMOVE = 3;

static final int OP_HIDE = 4;

static final int OP_SHOW = 5;

static final int OP_DETACH = 6;

static final int OP_ATTACH = 7;

ArrayList mOps = new ArrayList<>();

static final class Op {

int cmd; //状态

Fragment fragment;

int enterAnim;

int exitAnim;

int popEnterAnim;

int popExitAnim;

}

int mIndex = -1;

//栈中最后一个元素的索引

}

可以看到 Op 就是添加了状态和动画信息的 Fragment, mOps就是栈中所有的 Fragment。事务定义的方法它是如何实现的呢

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

@Override

public FragmentTransaction add(int containerViewId, Fragment fragment) {

doAddOp(containerViewId, fragment, null, OP_ADD);

return this;

......

}

可以看到添加一个 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;

}

@Override

public FragmentTransaction show(Fragment fragment) {

Op op = new Op();

op.cmd = OP_SHOW;

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) {

if (mCommitted) throw new IllegalStateException("commit already called");

//...

}

}

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

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

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

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

3.4.真正处理的部分

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

/**

* Executes the operations contained within this transaction. The Fragment states will only

* be modified if optimizations are not allowed.

*/

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;

case OP_SHOW:

f.setNextAnim(op.enterAnim);

mManager.showFragment(f);

break;

case OP_DETACH:

f.setNextAnim(op.exitAnim);

mManager.detachFragment(f);

break;

case OP_ATTACH:

f.setNextAnim(op.enterAnim);

mManager.attachFragment(f);

break;

default:

throw new IllegalArgumentException("Unknown cmd: " + op.cmd);

}

if (!mAllowOptimization && op.cmd != OP_ADD) {

mManager.moveFragmentToExpectedState(f);

}

}

if (!mAllowOptimization) {

// Added fragments are added at the end to comply with prior behavior.mManager.moveToState(mManager.mCurState, true);

}

}

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

public void removeFragment(Fragment fragment) {

if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);

final boolean inactive = !fragment.isInBackStack();

if (!fragment.mDetached || inactive) {

if (mAdded != null) {

mAdded.remove(fragment);

}

if (fragment.mHasMenu && fragment.mMenuVisible) {

mNeedMenuInvalidate = true;

}

fragment.mAdded = false; //设置属性值

fragment.mRemoving = true;

}

}

代码很长,先省略掉......但做的事情很简单:

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

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

四丶总结

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 创建经历的方法:

63eb9d15d1b8d5b9fc0592e997c2d52b.png

嵌套 Fragment的原理

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

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

由于很多代码太长了,敲了一下午,眼睛和手都酸了,对这样感兴趣的可以拿这份笔记自己研究,有不懂的欢迎留言

726262f8ccb71a24e8172e8c5cf8d089.png

知识汇总的PDF相关内容后续GitHub更新,想冲击金三银四的小伙伴可以找找看看,欢迎star

(顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)

https://github.com/xiangjiana/Android-MS

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值