谈到fragment的使用,肯定绕不过FragmentTransaction事务,对fragment的操作必定用到它,其提供show,hide,add,remove,replace等常用的fragment操作,最后commit操作,这么强大的管理类,它内部是如何实现的呢?为什么可以连续调用多个api,最后一次要commit操作?

1、创建FragmentTransaction对象:

 FragmentTransaction ft = mFragmentManager.beginTransaction();

源码实现:

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

可以看到new 出一个新的 BackStackRecord对象,这个BackStackRecord就是api的真正实现者。

我以remove fragment这个操作看下内部实现:

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

这个Op是什么东东呢?,又把op加入到addOp中,我们不妨先看下这个addOp的实现:

 void addOp(Op op) {// 创建空链表,头指针和尾指针指向第一个结点if (mHead == null) {mHead = mTail = op;} else {// 新结点加入到尾部op.prev = mTail;mTail.next = op;mTail = op;}op.enterAnim = mEnterAnim;op.exitAnim = mExitAnim;op.popEnterAnim = mPopEnterAnim;op.popExitAnim = mPopExitAnim;mNumOp++;}

这段代码还是比较容易理解的,其实remove操作,就是新建一个 链表结点,结点中保存了当前的操作,保存了fragment的对象引用,所以 FragmentTransaction的内部主要实现就是通过链表操作的,每个链表结点保存了每一个api操作的信息,好了,我们看下commit的实现:

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

只能commit一次,否则抛出异常

    int commitInternal(boolean allowStateLoss) {if (mCommitted) throw new IllegalStateException("commit already called");if (FragmentManagerImpl.DEBUG) {Log.v(TAG, "Commit: " + this);LogWriter logw = new LogWriter(TAG);PrintWriter pw = new PrintWriter(logw);dump("  ", null, pw, null);}mCommitted = true;if (mAddToBackStack) {mIndex = mManager.allocBackStackIndex(this);} else {mIndex = -1;}mManager.enqueueAction(this, allowStateLoss);return mIndex;}

看下 mManager.enqueueAction(this, allowStateLoss)的实现:

    /*** Adds an action to the queue of pending actions.** @param action the action to add* @param allowStateLoss whether to allow loss of state information* @throws IllegalStateException if the activity has been destroyed*/public void enqueueAction(Runnable 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<Runnable>();}mPendingActions.add(action);if (mPendingActions.size() == 1) {mHost.getHandler().removeCallbacks(mExecCommit);mHost.getHandler().post(mExecCommit);}}}

代码解读:
1、先去检查 checkStateLoss 状态,正常commit操作,是肯定去检查的,如果是在onSaveInstanceState状态保存之后,再去commit操作,肯定会报错。

2、BackStackRecord 类实现了 Runnable,看其run方法。

     public void run() {// 省略部分代码// 指向链表头指针Op op = mHead;// 遍历链表结点while (op != null) {//每一个结点的cmd操作switch (op.cmd) {case OP_ADD: {Fragment f = op.fragment;f.mNextAnim = op.enterAnim;mManager.addFragment(f, false);}break;case OP_REPLACE: {Fragment f = op.fragment;int containerId = f.mContainerId;if (mManager.mAdded != null) {for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {Fragment old = mManager.mAdded.get(i);if (FragmentManagerImpl.DEBUG) {Log.v(TAG,"OP_REPLACE: adding=" + f + " old=" + old);}if (old.mContainerId == containerId) {if (old == f) {op.fragment = f = null;} else {if (op.removed == null) {op.removed = new ArrayList<Fragment>();}op.removed.add(old);old.mNextAnim = op.exitAnim;if (mAddToBackStack) {old.mBackStackNesting += 1;if (FragmentManagerImpl.DEBUG) {Log.v(TAG, "Bump nesting of "+ old + " to " + old.mBackStackNesting);}}mManager.removeFragment(old, mTransition, mTransitionStyle);}}}}if (f != null) {f.mNextAnim = op.enterAnim;mManager.addFragment(f, false);}}break;case OP_REMOVE: {Fragment f = op.fragment;f.mNextAnim = op.exitAnim;mManager.removeFragment(f, mTransition, mTransitionStyle);}break;case OP_HIDE: {Fragment f = op.fragment;f.mNextAnim = op.exitAnim;mManager.hideFragment(f, mTransition, mTransitionStyle);}break;case OP_SHOW: {Fragment f = op.fragment;f.mNextAnim = op.enterAnim;mManager.showFragment(f, mTransition, mTransitionStyle);}break;case OP_DETACH: {Fragment f = op.fragment;f.mNextAnim = op.exitAnim;mManager.detachFragment(f, mTransition, mTransitionStyle);}break;case OP_ATTACH: {Fragment f = op.fragment;f.mNextAnim = op.enterAnim;mManager.attachFragment(f, mTransition, mTransitionStyle);}break;default: {throw new IllegalArgumentException("Unknown cmd: " + op.cmd);}}//指向下一个结点op = op.next;}mManager.moveToState(mManager.mCurState, mTransition,mTransitionStyle, true);// 是否调用了addBackStack(),如果加入,将当前的BackStackRecord加入到栈中if (mAddToBackStack) {mManager.addBackStackState(this);}}

这段代码解读:

1、每次commit之后,都会commit一个runnable任务,run()方法里面 对链表进行遍历操作,从头结点开始,依次访问每个结点,然后读取里面每一个结点的cmd,执行相应的方法。注意replace,是先remove,后add。
2、如果调用了addBackStack()方法,会将当前的任务对象加入到栈中。

 void addBackStackState(BackStackRecord state) {if (mBackStack == null) {mBackStack = new ArrayList<BackStackRecord>();}mBackStack.add(state);reportBackStackChanged();}

可见我的这篇博客 从源码角度解释 fragment 坑(一)

我在想,FragmentTransaction 事务内部为什么要实现链表操作呢?

如果单纯的只是commit之前调用几个api,hide,show我认为可能没有必要,它的必要性,就是在退栈的时候,能够记住你之前的每个结点的行为,并且进行相应的反操作,比如我 先用事务,hide 1,remove 2,add 3,甚至更多,退栈的时候,我们是不是先执行,remove 3,往往3走到onDestroyview方法,2 调用 onCreateView,show 1出来,如果不用链表的操作的话,确实不太方便。

从源码角度理解 FragmentTransaction实现相关推荐

  1. 从源码角度理解LinearLayout#onMeasure对child的measure调用次数

    熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们就从LinearLayout开始学起,看一下它对子View的onMeasure调用次数具体是多少. 简单起见 ...

  2. 从源码角度理解FrameLayout#onMeasure对child的measure调用次数

    熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们就从最简单的FrameLayout开始学起,看一下它对子View的onMeasure调用次数具体是多少. 简 ...

  3. 从源码角度理解ConstraintLayout#onMeasure对child的measure调用次数

    熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们简单看下较为复杂的ConstraintLayout,看一下它对子View的onMeasure调用次数具体是多 ...

  4. 对应到对象 数据库驼峰_从源码角度理解Mybatis字段映射(一) - 驼峰式命名

    凯伦说,公众号ID: KailunTalk,努力写出最优质的技术文章,欢迎关注探讨. 在上篇博客-[JDBC] 处理ResultSet,构建Java对象中提到,我们需要分析Mybatis在转换Resu ...

  5. linux线程handler,Handler从源码角度理解

    上一个文章讲解了Handler的基本使用,同时也有一些问题没有解决,本篇带你从源码的角度理解. 首先让我们来看看Handler的构造方法: image.png 我靠这么多的构造方法啊,我们上一篇只用了 ...

  6. java comparator 降序排序_【转】java comparator 升序、降序、倒序从源码角度理解

    原文链接:https://blog.csdn.net/u013066244/article/details/78997869 环境 jdk:1.7+ 前言 之前我写过关于comparator的理解,但 ...

  7. Android-带你从源码角度理解SharedPreferences存储原理

    SP的特点以及基本使用方式 SharedPreferences因非常适合存储较小键值集合数据且使用非常简单的特点,而受到广大程序员们热爱. SP使用非常简单: //读操作 Context contex ...

  8. jdbc mysql 源码_【JDBC系列】从源码角度理解JDBC和Mysql的预编译特性

    背景 最近因为工作调整的关系,都在和数据库打交道,增加了许多和JDBC亲密接触的机会,其实我们用的是Mybatis啦.知其然,知其所以然,是我们工程师童鞋们应该追求的事情,能够帮助你更好的理解这个技术 ...

  9. 从源码角度深入理解Toast

    Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定: 1: Toast.makeText(this, "333", Toast.LENGTH_LONG).sho ...

最新文章

  1. request的生命周期
  2. Java黑皮书课后题第4章:*4.2(几何:最大圆距离)最大圆面积是指球面上两个点间的距离。编写一个程序,提示用户以度为单位输入地球上两个点的经纬度,显示其最大圆距离值
  3. python中print语句
  4. 浅谈ASP.NET 4中构造“.NET研究”HTML5视频控件
  5. OJ1057: 素数判定(C语言经典列题,判断变量的应用)
  6. Lintcode 553. 炸弹袭击 题解
  7. 数据可视化大屏案例系列 1
  8. MQL4课程-交易函数平仓及修改止损止盈
  9. Linux下实现炫酷的终端分屏
  10. php+mysql统计7天、30天每天数据没有补0
  11. BadBoy录制JMeter脚本
  12. pycharm批量注释
  13. I2C协议研读(三):仲裁和时钟同步
  14. 让你的论文图片更好看
  15. 用 Vue 改造 Bootstrap,渐进提升项目框架
  16. 计算机等级考试怎么领取证书 领取方式
  17. match against mysql_Mysql全文搜索match against的用法
  18. Translate Tab for Mac(快速实时翻译工具)
  19. 11月8日赢在淘宝 北京站ISV聚会笔记
  20. webpack(一)压缩js,加载css,压缩html,压缩图片

热门文章

  1. 十七步学习ROS Toptics -ubuntu 18.04 melodic- ROS/教程/理解主题的概念:ROS/Tutorials/Understanding ROS Toptics
  2. ping .............
  3. vue tree组件_Ant-Design-Vue和Icon按需加载方案 - JeecgBoot实战
  4. 程序员生涯之我见 找到自己的兴趣所在 (zz)
  5. 超大杯来了!一加10 Ultra将在第三季度登场:或搭载OPPO 自研影像芯片
  6. 华策影视:控股股东、实控人等拟合计减持不超4.01%股份
  7. Redmi Note 11系列来势汹汹,一“机”打尽更多看不见的旗舰猛料
  8. 谷歌Pixel 6系列正式发布:搭载自研Tensor SoC 规格超骁龙888
  9. 马斯克称下一代超级工厂占地可能没必要更大 但可能更先进
  10. 三星Galaxy S22系列屏幕规格曝光:顶配版将配备LTPO屏幕