Android-Fragment的生命周期、FragmentStatePagerAdapter与FragmentTransaction

image.png

一、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延迟加载的前世今生》

你可能感兴趣的:(Android-Fragment的生命周期、FragmentStatePagerAdapter与FragmentTransaction)