Android 进阶:Fragment 源码深入理解
来源:互联网 发布:linux suse iso下载 编辑:程序博客网 时间:2024/05/22 12:00
上周预热的文章,结果自己打脸了,实在抱歉 (。ŏ_ŏ)。
日常开发中我们经常使用 Fragment 管理布局,使用起来非常方便,但是在简单的 API 背后隐藏了什么操作,很多人恐怕不了解。
如果你回答不出这些问题,那这篇文章可能就对你有些帮助:
Fragment FragmentManager FragmentTransaction 的关系和作用
Fragment 如何实现布局的添加替换
嵌套 Fragment 的原理
Fragment 的使用
Fragment 的使用大家应该都熟悉,这里举个例子,要实现这样的类似饿了么点餐效果:
界面丑了点,但意思是差不多的哈,左边一个列表,点击后切换右边的布局。我们就可以使用 Fragment 来实现。
实现也很简单,创建一个的布局,然后在 Activity 里点击时替换 Fragment。
代码很简单,核心就三步:
创建 Fragment
获取 FragmentManager
调用事务,添加、替换
我们一步步来了解这背后的故事。
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 中所有添加进来的 Fragmentpublic 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 的宿主就两种:
Activity
Fragment
我们再看看他对 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 implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}
先来看看 FragmentTransaction
。
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 的操作基本就这几步,我们知道,要完成对 fragment 的操作,最后还需要提交一下:
事务的四种提交方式
事务最终的提交方法有四种:
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
:
@Overridepublic FragmentTransaction beginTransaction() { return new BackStackRecord(this);}
事务真正实现/回退栈 BackStackRecord
BackStackRecord
既是对 Fragment 进行操作的事务的真正实现,也是 FragmentManager 中的回退栈的实现:
final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}
它的关键成员:
可以看到 Op 就是添加了状态和动画信息的 Fragment, mOps
就是栈中所有的 Fragment。
事务定义的方法它是如何实现的呢。
先看添加一个 Fragment 到布局 add()
的实现:
@Overridepublic 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
的实现:
@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;} //...}
那这些状态值的不同是什么时候起作用的呢?
别忘了我们操作 Fragment 还有最后一步,提交。
看看这两个是怎么实现的:
@Overridepublic int commit() { return commitInternal(false);}@Overridepublic 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()
,我们直接来看它的实现:
代码很长,但做的事情很简单:
根据状态调用对应的生命周期方法
如果是新创建的,就把布局添加到 ViewGroup 中
Fragment 是什么
Fragment 是什么,从官网、别人博客上看到的都是他人之言,我们还是得去看源码才能得到答案。
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener {...}
可以看到,Fragment 没有继承任何类,只是实现了这两个接口,第二个不太重要,第一个是在内存不足时可以收到回调。
没有什么特别信息,我们还是去看看它的主要成员。
Fragment 的主要成员
一堆标志位和状态值。然后就是关键的成员了:
看到这里,结合前面的,我们就清晰了一个 Fragment 的创建、添加过程:
在 onCreateView()
中返回一个 布局,然后在 FragmentManager 中拿到这个布局,添加到要绑定容器(Activity/Fragment)的 ViewGroup 中,然后设置相应的状态值。
生命周期方法
Fragment 的生命周期大家都清楚,一张很清晰的图:
总共 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 创建经历的方法:
嵌套 Fragment 的原理
也比较简单,Fragment 内部有一个 childFragmentManager,通过它管理子 Fragment。
在添加子 Fragment 时,把子 Fragment 的布局 add 到父 Fragment 即可。
考虑公众号字数限制及阅读体验,这里只贴出了关键部分代码,完整代码欢迎去原文地址在 PC 端阅读。
- Android 进阶:Fragment 源码深入理解
- Android 进阶17:Fragment FragmentManager FragmentTransaction 深入理解
- Android Fragment 深入理解
- 深入理解Fragment进阶系列(一):生命周期详解
- 深入理解Fragment(一)
- 深入理解Fragment(二)
- 深入理解Fragment(三)
- 深入理解fragment(一)
- Android源码分析-深入理解setContentView方法
- 从源码出发深入理解 Android Service
- 从源码出发深入理解 Android Service
- Android高手进阶——Adapter深入理解与优化
- Android高手进阶——Adapter深入理解与优化
- Android高手进阶——Adapter深入理解与优化
- Android高手进阶——Adapter深入理解与优化
- Android高手进阶——Adapter深入理解与优化
- Android高手进阶——Adapter深入理解与优化
- Android高手进阶:Adapter深入理解与优化
- Nginx SSL+tomcat集群,request.getScheme() 取到https正确的协议
- 一线互联网公司 Android 面试回顾
- 入职第一天
- 并发编程:线程池的使用与执行流程
- Actor Critic算法源码分析
- Android 进阶:Fragment 源码深入理解
- 走心的安卓跳槽经验分享
- 云上建站快速入门:博客、论坛、CMS、电子商务网站统统搞定
- Python 安装第三方库,模块和包的安装方法
- Excel在统计分析中的应用—第五章—概率分布及概率分布图-Part6-连续型概率分布(正态分布函数的应用)
- python基础--字典
- 添加,修改,删除约束
- Consider defining a bean of type 'com.xxx.service.impl.xxxImpl' in your configuration
- Linux学习