欢迎大家下载我个人开发的app[安琪花园](https://www.jianshu.com/p/ce12fcfb15de)
- fragment 是如何添加到界面上的
- fragment 的源码是怎么实现的
带着上面的两个问题对fragment的源码进行了阅读
为什么我会去研究fragment的源码
device-2020-01-20-164643.png
当时我在项目中有这样一个需求, 要在首页的第一个tab的右下角添加悬浮按钮(不是整个Activity).
当前是VIP 在第一tab. 对于我公司的项目,第一个Tab是会经常变化的。有可能下次商城的Tab就放在了最前面。对于这个需求怎么去实现呢?
面临如下两个问题:
- 如果悬浮按钮添加在Activity的布局上面,那么底部几个tab切换也会出现悬浮按钮,显示不符合需求
- 如果悬浮按钮添加在fragment上面,那么第一个tab每次切换都需要去改动fragment的布局代码。这太麻烦了
如何才能实现一次 对后面fragment如何改动都不用去改代码呢? 这就需要我们对fragment是如何添加到屏幕上的要有所了解。
思路
- 自定义一个控件NewsMainRelativeLayout,对于右下角的悬浮按钮的功能全部封装在这一个控件里面
- 先看如下的一段代码
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.content, fragment);
ft.commitAllowingStateLoss();
fragment 添加到界面上只需要调用 上面的几句代码。
通过分析知, fragment里面的oncreateView方法返回的View, 是添加到R.id.content这个控件上面的。
- 自定义一个FragmentContentView控件, 这个控件的id就是第二步里面的R.id.content。
重写该控件的addView方法。
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if(child instanceof NewsMainRelativeLayout){
super.addView(child, index, params);
return;
}
if(getContext() instanceof MainActivity){
FragmentManager manager = ((MainActivity) getContext()).getSupportFragmentManager();
List<Fragment> fragments = manager.getFragments();
if(fragments != null && fragments.size() > 0){
if(child == fragments.get(0).getView()){
NewsMainRelativeLayout newsLayout = new NewsMainRelativeLayout(getContext());
newsLayout.addView(child);
super.addView(newsLayout, index, params);
}else{
super.addView(child, index, params);
}
}else{
super.addView(child, index, params);
}
}else{
super.addView(child, index, params);
}
}
其思路就是一种偷天换日的做法, 判断addView方法里面传过来的child是否和 第一个fragment里面的onCreateView方法返回的View相同。 如果相同,则在外面包裹一层NewsMainRelativeLayout,在添加到R.id.content控件上面。这样就达到了产品需求的目的
fragment是如何添加到界面上的
首先明白以下三个类
- Fragment
- FragmentManager
- FragmentTransaction
FragmentManager
它是act相关联的,而是每个act都有一个自己的FragmentManager,
内部有自己的状态mCurState,对应外部act的生命周期状态。
主要提供 Activty 和Fragment交互的api
实现类 FragmentManagerImpl
FragmentTransaction
封装了一系列对Fragment的操作, 包括add, remove, replace, show, hide,这些方法都是创建一个Op对象然后保存起来。
实现类 BackStackRecord
FragmentTransaction提供了如下的api:
@NonNull
public FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag) {
doAddOp(0, fragment, tag, OP_ADD);
return this;
}
@NonNull
public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}
等等。。。。
以FragmentTransaction的add方法来分析是如何实现的。
在FragmentTransaction里面有一个内部类Op, 里面定义一次操作的相关属性, 而add方法就是新创建
一个Op对象,然后保存起来。
static final class Op {
int mCmd;
Fragment mFragment;
int mEnterAnim;
int mExitAnim;
int mPopEnterAnim;
int mPopExitAnim;
Lifecycle.State mOldMaxState;
Lifecycle.State mCurrentMaxState;
Op() {
}
Op(int cmd, Fragment fragment) {
this.mCmd = cmd;
this.mFragment = fragment;
this.mOldMaxState = Lifecycle.State.RESUMED;
this.mCurrentMaxState = Lifecycle.State.RESUMED;
}
Op(int cmd, @NonNull Fragment fragment, Lifecycle.State state) {
this.mCmd = cmd;
this.mFragment = fragment;
this.mOldMaxState = fragment.mMaxState;
this.mCurrentMaxState = state;
}
}
接下来看最为核心的代码FragmentTransaction.commit方法。
@Override
public int commit() {
return commitInternal(false);
}
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(" ", pw);
pw.close();
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
最终会执行到上面这句代码,mManager.enqueueAction(this, allowStateLoss);
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
if (allowStateLoss) {
// This FragmentManager isn't attached, so drop the entire transaction.
return;
}
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<>();
}
mPendingActions.add(action);
scheduleCommit();
}
}
上面这个方法只是入了队列,但是并没有真正的执行,而是调用了scheduleCommit方法,通过Handler来发送一个消息保存到队列里面的, 所以后续的逻辑 就是Handler来执行了
对Handler不是很了解的可以看我写的 Handler总结
接下看一下scheduleCommit方法
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);
updateOnBackPressedCallbackEnabled();
}
}
}
从上面的这个方法里面可以看到这样一句代码, mHost.getHandler().post(mExecCommit);
mExecCommit就是一个Runnable,说白了就是往Handler里面发送了一条消息。
通过源码的跟踪 最终会执行BackStackRecord里面的这个方法
void executeOps() {
final int numOps = mOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
final Fragment f = op.mFragment;
if (f != null) {
f.setNextTransition(mTransition, mTransitionStyle);
}
switch (op.mCmd) {
case OP_ADD:
f.setNextAnim(op.mEnterAnim);
mManager.addFragment(f, false);
break;
case OP_REMOVE:
f.setNextAnim(op.mExitAnim);
mManager.removeFragment(f);
break;
case OP_HIDE:
f.setNextAnim(op.mExitAnim);
mManager.hideFragment(f);
break;
case OP_SHOW:
f.setNextAnim(op.mEnterAnim);
mManager.showFragment(f);
break;
case OP_DETACH:
f.setNextAnim(op.mExitAnim);
mManager.detachFragment(f);
break;
case OP_ATTACH:
f.setNextAnim(op.mEnterAnim);
mManager.attachFragment(f);
break;
case OP_SET_PRIMARY_NAV:
mManager.setPrimaryNavigationFragment(f);
break;
case OP_UNSET_PRIMARY_NAV:
mManager.setPrimaryNavigationFragment(null);
break;
case OP_SET_MAX_LIFECYCLE:
mManager.setMaxLifecycle(f, op.mCurrentMaxState);
break;
default:
throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
}
if (!mReorderingAllowed && op.mCmd != OP_ADD && f != null) {
mManager.moveFragmentToExpectedState(f);
}
}
if (!mReorderingAllowed) {
// Added fragments are added at the end to comply with prior behavior.
mManager.moveToState(mManager.mCurState, true);
}
}
这个方法对每一种操作进行会进行处理, OP_ADD 的操作会执行如下的方法 对Fragment的状态进行更改
public void addFragment(Fragment fragment, boolean moveToStateNow) {
if (DEBUG) Log.v(TAG, "add: " + fragment);
makeActive(fragment);
if (!fragment.mDetached) {
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
synchronized (mAdded) {
mAdded.add(fragment);
}
fragment.mAdded = true;
fragment.mRemoving = false;
if (fragment.mView == null) {
fragment.mHiddenChanged = false;
}
if (isMenuAvailable(fragment)) {
mNeedMenuInvalidate = true;
}
if (moveToStateNow) {
moveToState(fragment);
}
}
}
从上面的这一连串的源码分析可以看到 addFragmet的执行流程,
但是我们并没有看到fragment的onCreateView方法添加到父控件上面。
这个问题需要从Fragment保存的状态去查找答案。Fragment的状态有如下几种
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // Fully created, not started.
static final int STARTED = 3; // Created and started, not resumed.
static final int RESUMED = 4;
而Fragment的状态改变是通过FragmentManagerImpl的moveToState方法去改变的
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
**** 省略 部分代码 ***
switch (f.mState) {
case Fragment.INITIALIZING:
*** 省略部分代码***
case Fragment.CREATED
if (newState > Fragment.INITIALIZING) {
ensureInflatedFragmentView(f);
}
if (newState > Fragment.CREATED) {
if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
if (f.mContainerId == View.NO_ID) {
throwException(new IllegalArgumentException(
"Cannot create fragment "
+ f
+ " for a container view with no id"));
}
container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
String resName;
try {
resName = f.getResources().getResourceName(f.mContainerId);
} catch (Resources.NotFoundException e) {
resName = "unknown";
}
throwException(new IllegalArgumentException(
"No view found for id 0x"
+ Integer.toHexString(f.mContainerId) + " ("
+ resName
+ ") for fragment " + f));
}
}
f.mContainer = container;
f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
container.addView(f.mView);
}
if (f.mHidden) {
f.mView.setVisibility(View.GONE);
}
f.onViewCreated(f.mView, f.mSavedFragmentState);
dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
false);
// Only animate the view if it is visible. This is done after
// dispatchOnFragmentViewCreated in case visibility is changed
f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
&& f.mContainer != null;
} else {
f.mInnerView = null;
}
}
f.performActivityCreated(f.mSavedFragmentState);
dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
// fall through
*** 省略部分代码***
}
当状态为Created的时候 这个时候 就把oncreateView 返回的View添加到了控件上面。
这就能够解决最开头我项目上面所遇到的问题
公众号:
网友评论