写点什么

当你面试的时候,被问到关于 Fragment 的种种,5 年经验 Android 程序员面试 27 天

用户头像
Android架构
关注
发布于: 8 小时前

final class FragmentManagerImpl extends FragmentManager implementsLayoutInflaterFactory {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 的要求一致



接着还有当前的状态,当前 Fragment 的起始 mParent,以及 FragmentManagermHostmContainer


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


public abstract class FragmentContainer {@Nullablepublic 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


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


class HostCallbacks extends FragmentHostCallback<FragmentActivity> {public HostCallbacks() {super(FragmentActivity.this /fragmentActivity/);}//...


@Overridepublic LayoutInflater onGetLayoutInflater() {returnFragmentActivity.this.getLayoutInflater().cloneInContext(FragmentActivity.t his);}


@Overridepublic FragmentActivity onGetHost() {return FragmentActivity.this;}......}


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


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


beginTransaction() 返回一个新的 BackStackRecord ,我们后面介绍。前面提到了, popBackStack() 是一个异步操作,它是如何实现异步的呢?


@Overridepublic 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;@Overridepublic int getBackStackEntryCount() {return mBackStack != null ? mBackStack.size() : 0;}@Overridepublic BackStackEntry getBackStackEntryAt(int index) {return mBackStack.get(index);}


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

三丶事务

BackStackRecord 继承了 FragmentTransaction


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


先来看看 FragmentTransaction

3.1.FragmentTransaction

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


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


//它会调用 add(int, Fragment, String),其中第三个参数是 nullpublic 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),第三个参数传的是 nullpublic 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,这只适用于之前添加到宿主上的 fragmentpublic 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.事务的四种提交方式

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


  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 :


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

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

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


final class BackStackRecord extendsFragmentTransaction 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<Op> mOps = new ArrayList<>();static final class Op {int cmd; //状态 Fragment fragment;int enterAnim;int exitAnim;int popEnterAnim;int popExitAnim;}int mIndex = -1;//栈中最后一个元素的索引}


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


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


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


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


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


@Overridepublic FragmentTransaction replace(int containerViewId, Fragment fragment) {return replace(containerViewId, fragment, null);}@Overridepublic 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,设置对应的状态值。


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


return this;}


@Overridepublic FragmentTransaction hide(Fragment fragment) {Op op = new Op();op.cmd = OP_HIDE;op.fragment = fragment;addOp(op);


return this;}


@Overridepublic FragmentTransaction show(Fragment fragment) {Op op = new Op();op.cmd = OP_SHOW;op.fragment = fragment;addOp(op);


return this;}


那这些状态值的不同是什么时候起作用的呢?别忘了我们操作 Fragment 还有最后一步,提交。看看这两个是怎么实现的:


@Overridepublic int commit() {return commitInternal(false);}


@Overridepublic int commitAllowingStateLoss() {return commitInternal(true);}int commitInternal(boolean allowStateLoss) {if (mCommitted) throw new IllegalStateException("commit already called");//...}}


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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
当你面试的时候,被问到关于Fragment的种种,5年经验Android程序员面试27天