美文网首页
Android-Fragment的生命周期、FragmentSt

Android-Fragment的生命周期、FragmentSt

作者: zzq_nene | 来源:发表于2020-07-26 20:21 被阅读0次
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<Fragment.SavedState> mSavedState = new ArrayList<>();
    private ArrayList<Fragment> 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}.
     *
     * <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
     * current Fragment changes.</p>
     *
     * @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<mFragments.size(); i++) {
            Fragment f = mFragments.get(i);
            if (f != null && f.isAdded()) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                mFragmentManager.putFragment(state, key, f);
            }
        }
        return state;
    }

    @Override
    public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle)state;
            bundle.setClassLoader(loader);
            Parcelable[] fss = bundle.getParcelableArray("states");
            mSavedState.clear();
            mFragments.clear();
            if (fss != null) {
                for (int i=0; i<fss.length; i++) {
                    mSavedState.add((Fragment.SavedState)fss[i]);
                }
            }
            Iterable<String> 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的生命周期、FragmentSt

      本文链接:https://www.haomeiwen.com/subject/jzsglktx.html