十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
不知道你们都没有自己特别的学习的方法,我是有吧所有的整理成笔记的习惯
比如今天讲解的关于Fragment
的我会做成笔记
成都创新互联于2013年创立,先为扶风等服务建站,扶风等地企业,进行企业商务咨询服务。为扶风企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。
由于文章有些地方代码过于太长了繁琐,所以部分省略掉了,敲了一下午眼睛和手脖子都酸了,至于省略的部分,对这些笔记,面试内容感兴趣的可以看笔记研究,欢迎留言
相关内容后续GitHub更新,想冲击金三银四的小伙伴可以找找看看,欢迎star
(顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)
https://github.com/xiangjiana/Android-MS
实现很简单,创建一个的布局,然后在 Activity
里点击时替换 Fragment
mFragmentManager = getSupportFragmentManager();
mFragmentManager.beginTransaction()
.replace(R.id.fl_content, fragment)
.commitAllowingStateLoss();
代码很简单,核心就三步:
- 创建
Fragment
- 获取
FragmentManager
- 调用事务,添加、替换
我们一步步来了解这背后的故事。
Fragment 大家应该比较熟悉,放到最后。
先来看看 FragmentManager
。
####二丶 FragmentManager
public abstract class FragmentManager {...}
FragmentManager
是一个抽象类,定义了一些和 Fragment 相关的操作和内部类/接口。
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
回退栈的操作、管理。
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
的要求一致
接着还有当前的状态,当前 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
。
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();
事务最终的提交方法有四种:
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);
}
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 的状态。
前面主要是对 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, 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
即可
由于很多代码太长了,敲了一下午,眼睛和手都酸了,对这样感兴趣的可以拿这份笔记自己研究,有不懂的欢迎留言
知识汇总的PDF相关内容后续GitHub更新,想冲击金三银四的小伙伴可以找找看看,欢迎star
(顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)
https://github.com/xiangjiana/Android-MS