一、FragmentTransaction的add、remove、show、hide、attach、detach、replace、addToStack
(1)add和remove
如果使用add和remove的时候,不使用到addToStack方法将fragment添加到回退栈,那么在remove的时候会完全销毁fragment
(2)show和hide
只是针对fragment做了一个显示和隐藏,并不会销毁fragment
(3)attach和detach
detach的时候,fragment会销毁视图,但是不会完全销毁。
(4)replace
replace是remove+add的操作。执行remove操作,那么fragment就会被销毁,直到detach,add一个fragment,就会创建一个新的fragment
(5)addToStack
就是将fragment添加到回退栈中,如果在调用replace的时候又调用了addToStack的话,那么在replace中执行remove操作的是,并不会完全销毁fragment,只是将fragment的视图结束。
二、Fragment.onHiddenChanged
用FragmentTransaction来控制fragment的hide和show时,那么这个方法就会被调用。每当你对某个Fragment使用hide或者是show的时候,那么这个Fragment就会自动调用这个方法。
三、Fragment.setUserVisibleHint
在调用mCurTransaction.commitNowAllowingStateLoss();的时候会激活调用该方法,因为此时会调用FragmentPagerAdapter.setPrimaryItem方法,在setPrimaryItem方法中会调用Fragment.setUserVisibleHint方法
不过fragment的setUserVisibleHint方法已经是过时的方法,官方API推荐使用FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)替换
getSupportFragmentManager().beginTransaction().setMaxLifecycle()
setUserVisibleHint方法的触发
因为ViewPager是控制Fragment的可见与不可见的,所以先看ViewPager的setCurrentItem
1.ViewPager#setCurrentItem
public void setCurrentItem(int item) {
mPopulatePending = false;
setCurrentItemInternal(item, !mFirstLayout, false);
}
setCurrentItem最终调用了setCurrentItemInternal方法,且是参数最多的一个
2.ViewPager#setCurrentItemInternal
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
if (mAdapter == null || mAdapter.getCount() <= 0) {
setScrollingCacheEnabled(false);
return;
}
if (!always && mCurItem == item && mItems.size() != 0) {
setScrollingCacheEnabled(false);
return;
}
if (item < 0) {
item = 0;
} else if (item >= mAdapter.getCount()) {
item = mAdapter.getCount() - 1;
}
final int pageLimit = mOffscreenPageLimit;
if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
// We are doing a jump by more than one page. To avoid
// glitches, we want to keep all current pages in the view
// until the scroll ends.
for (int i = 0; i < mItems.size(); i++) {
mItems.get(i).scrolling = true;
}
}
final boolean dispatchSelected = mCurItem != item;
if (mFirstLayout) {
// We don't have any idea how big we are yet and shouldn't have any pages either.
// Just set things up and let the pending layout handle things.
mCurItem = item;
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
requestLayout();
} else {
populate(item);
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
}
当不是第一次mFirstLayout参数在ViewPager的onLayout方法结束之后就会置为false
3.ViewPager#populate(int newCurrentItem)
在ViewPager的populate方法中,会调用了
mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
而curItem初始的时候为空,赋值的部分是在populate中调用了下面的代码
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
final ItemInfo ii = mItems.get(curIndex);
if (ii.position >= mCurItem) {
if (ii.position == mCurItem) curItem = ii;
break;
}
}
if (curItem == null && N > 0) {
curItem = addNewItem(mCurItem, curIndex);
}
在分析ViewPager的setAdapter的时候,最初的时候mItems其实都是空的,第一个数据其实就是通过调用对应的adapter的instantiateItem获取到
mAdapter.setPrimaryItem(this, mCurItem, curItem.object);中,curItem的object其实就是Fragment,那么接着分析
4.ViewPager#addNewItem
ItemInfo addNewItem(int position, int index) {
ItemInfo ii = new ItemInfo();
ii.position = position;
// 从第五点可以知道这里的object其实就是Fragment对象,如果是ViewPager+Fragment
ii.object = mAdapter.instantiateItem(this, position);
ii.widthFactor = mAdapter.getPageWidth(position);
if (index < 0 || index >= mItems.size()) {
mItems.add(ii);
} else {
mItems.add(index, ii);
}
return ii;
}
5.FragmentPagerAdapter#instantiateItem
因为是ViewPager+Fragment的做法,所以ViewPager中的adapter其实就是FragmentPagerAdapter,从FragmentPagerAdapter的instantiateItem方法可以看出,这里返回的是一个Fragment对象
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// 是否已经有这个Fragment,如果有,则直接从FragmentManager中取出
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
// 如果还没有这个Fragment,则通过getItem获取到这个Fragment对象
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
6.FragmentPagerAdapter#setPrimaryItem
setPrimaryItem方法其实就是具体去调用对应的Fragment的显示隐藏的回调的方法,在这里会先判断被选择的item是否是与当前的item一致,如果被选择的fragment与当前的一致,则什么都不做,如果不一致,则将当前的Fragment的显示隐藏的回调置为false,将新的被选择的Fragment置为true,并且更新当前Fragment对象缓存
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
四、FragmentTransaction.setMaxLifecycle
1.Lifecycle.State的各种状态
使用FragmentTransaction.setMaxLifecycle设置Fragment的最大生命周期,代表Fragment只能执行到设置的生命周期,而不能继续往下一个生命周期执行。比如:
fragment = TestLifecycleFragment()
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.add(R.id.ll_fragment, fragment)
fragmentTransaction.setMaxLifecycle(fragment, Lifecycle.State.CREATED)
fragmentTransaction.commit()
表示fragment的只能执行到onCreate这个生命周期,而不能执行下一个生命周期,那么就无法执行onCreateView,也就无法显示出Fragment。如果是一个onResume的生命周期的Fragment被设置为fragmentTransaction.setMaxLifecycle(fragment, Lifecycle.State.CREATED),那么这个Fragment就会调用onPause、onStop、onDestroyView,也就是回退到了onCreate状态
如果是设置为RESUMED
fragment = TestLifecycleFragment()
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.add(R.id.ll_fragment, fragment)
fragmentTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED)
fragmentTransaction.commit()
那么这个fragment就能正常显示出来,且Fragment可以执行到onResume这个生命周期
如果是设置为STARTED
fragment = TestLifecycleFragment()
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.add(R.id.ll_fragment, fragment)
fragmentTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED)
fragmentTransaction.commit()
那么Fragment只能执行到onStart,如果是一个onResume的Fragment设置为STARTED,那么Fragment就会调用onPause,即回退到了onStart状态
2.结合ViewPager实现懒加载
在FragmentStatePagerAdapter中有一个构造器,即:
public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
可以看到这个构造器中传递了一个Behavior的int值,这个Behavior是在FragmentStatePagerAdapter中定义的一个泛型。
@Retention(RetentionPolicy.SOURCE)
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
private @interface Behavior { }
而BEHAVIOR_SET_USER_VISIBLE_HINT和BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT是在FragmentStatePagerAdapter中定义的静态常量。
这两个常量的使用是在FragmentStatePagerAdapter.setPrimaryItem方法中。
@Override
@SuppressWarnings({"ReferenceEquality", "deprecation"})
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
而FragmentStatePagerAdapter.setPrimaryItem方法的调用,是在ViewPager.populate方法中,即要显示item的时候调用setPrimaryItem。
当Behavior使用BEHAVIOR_SET_USER_VISIBLE_HINT的时候,即Fragment会回调setUserVisibleHint。当Behavior使用BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT的时候,意味着只有当前显示的Fragment会被执行到onResume,而其他的Fragment的生命周期只会执行到onStart。
这样一来,如果想要做懒加载,则其实可以针对Fragment的生命周期onResume来实现,当Fragment需要显示的时候,调用FragmentTransaction.setMaxLifecycle设置为RESUME,当不需要显示的时候,设置为onStart,如果是不使用ViewPager或者ViewPager2的话。
如果是使用ViewPager的话,则在onResume做数据的加载,不在onStart和onViewCreated做数据的加载,如果只需要主动加载第一次加载,则只需要一个变量判断是否是第一次加载即可。
即在自定义FragmentStatePagerAdapter的时候,使用其两个参数的构造器,然后给Behavior传入FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT即可,然后在Fragment的onResume生命周期内执行数据加载的操作,这样就可以实现懒加载,只有当前Fragment会去加载。并且如果只需要加载一次,则可以使用一个变量判断是否是第一次加载数据,如果不是则不加载数据即可。
五、FragmentStatePagerAdapter源码解析
@SuppressWarnings("deprecation")
public abstract class FragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapt";
private static final boolean DEBUG = false;
@Retention(RetentionPolicy.SOURCE)
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
private @interface Behavior { }
/**
* Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
* fragment changes.
*
* @deprecated This behavior relies on the deprecated
* {@link Fragment#setUserVisibleHint(boolean)} API. Use
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
* {@link FragmentTransaction#setMaxLifecycle}.
* @see #FragmentStatePagerAdapter(FragmentManager, int)
*/
@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
/**
* Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
* state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
*
* @see #FragmentStatePagerAdapter(FragmentManager, int)
*/
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
private final FragmentManager mFragmentManager;
private final int mBehavior;
private FragmentTransaction mCurTransaction = null;
private ArrayList mSavedState = new ArrayList<>();
private ArrayList mFragments = new ArrayList<>();
private Fragment mCurrentPrimaryItem = null;
private boolean mExecutingFinishUpdate;
/**
* Constructor for {@link FragmentStatePagerAdapter} that sets the fragment manager for the
* adapter. This is the equivalent of calling
* {@link #FragmentStatePagerAdapter(FragmentManager, int)} and passing in
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
*
* Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
* current Fragment changes.
*
* @param fm fragment manager that will interact with this adapter
* @deprecated use {@link #FragmentStatePagerAdapter(FragmentManager, int)} with
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
*/
@Deprecated
public FragmentStatePagerAdapter(@NonNull FragmentManager fm) {
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
/**
* Constructor for {@link FragmentStatePagerAdapter}.
*
* If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current
* Fragment is in the {@link Lifecycle.State#RESUMED} state, while all other fragments are
* capped at {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is
* passed, all fragments are in the {@link Lifecycle.State#RESUMED} state and there will be
* callbacks to {@link Fragment#setUserVisibleHint(boolean)}.
*
* @param fm fragment manager that will interact with this adapter
* @param behavior determines if only current fragments are in a resumed state
*/
public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
/**
* Return the Fragment associated with a specified position.
*/
@NonNull
public abstract Fragment getItem(int position);
@Override
public void startUpdate(@NonNull ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
}
}
@SuppressWarnings("deprecation")
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
// 调用fragment的对应的方法
// 比如setUserVisibleHint
// 此时将fragment添加到事务中,但是还没提交事务
// 优先调用了setUserVisibleHint,而提交事务是在finishUpdate中
// 只有提交事务了才会执行生命周期
fragment.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
fragment.setUserVisibleHint(false);
}
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
}
return fragment;
}
// TODO(b/141958824): Suppressed during upgrade to AGP 3.6.
@SuppressWarnings("ReferenceEquality")
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
if (fragment.equals(mCurrentPrimaryItem)) {
mCurrentPrimaryItem = null;
}
}
@Override
@SuppressWarnings({"ReferenceEquality", "deprecation"})
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
// 在切换ViewPager的时候,会调用adapter的setPrimaryItem方法
// 切换的时候,会将当前Fragment的setUserVisibleHint设置为false
// 会将目标fragment的setUserVisibleHint设置为true
// 即当前fragment需要隐藏,目标fragment需要显示。
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(@NonNull ViewGroup container) {
if (mCurTransaction != null) {
// We drop any transactions that attempt to be committed
// from a re-entrant call to finishUpdate(). We need to
// do this as a workaround for Robolectric running measure/layout
// calls inline rather than allowing them to be posted
// as they would on a real device.
if (!mExecutingFinishUpdate) {
// 提交事务
// 在ViewPager.populate方法中调用
// 当执行完Adapter的startUpdate、addNewItem
// instantiateItem、destroyItem、setPrimaryItem
// 之后,才会调用finishUpdate
try {
mExecutingFinishUpdate = true;
mCurTransaction.commitNowAllowingStateLoss();
} finally {
mExecutingFinishUpdate = false;
}
}
mCurTransaction = null;
}
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return ((Fragment)object).getView() == view;
}
@Override
@Nullable
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i=0; i keys = bundle.keySet();
for (String key: keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
f.setMenuVisibility(false);
mFragments.set(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
}
从分析FragmentStatePagerAdapter来看,setUserVisibleHint方法会优先于Fragment的生命周期函数 执行。因为在FragmentStatePagerAdapter中提交事务,是在调用finishUpdate方法中进行的,只有提交事务的时候,才会去执行Fragment的生命周期。
参考
《学不动也要学!探究Fragment延迟加载的前世今生》