ViewPager+Fragment的几个问题点

1,115 阅读3分钟

1、关于fragment的生命周期问题

fragment有多个版本,从最早的android.app,到后面的support.v4,再到现在的androidx,各个版本都有不同,也修复了一些bug,fragment需要依赖于fragmentactivity才可以使用。 他的生命周期如下图所示:

有没有觉得很复杂,跟你想象的有点不一样吧,他确实就是这么复杂。

· 在执行add的时候,经过以下几个方法:

onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume

· remove的时候

onPause -> onStop-> onDestoryView -> onDestory -> onDetach

· detach

onPause->onStop->onDestoryView

· attach

onCreateView->onActivityCreated ->onStart -> onResume

· hide

会调用onHiddenChanged()

· setRetainInstance

如果调用了setRetainInstance(true)的fragment不会销毁实例,只会销毁视图并detach,不会执行onDestory,具体看下面的代码:

从注释中,不难看出来,设置了这个方法之后,不会调用destory。

这个方法有什么用,在MVVM框架中,通常可以设置一个viewmodel给fragment就可以通过下面的方式来获取保存的数据:

public static <T> T findViewModel(@NonNull FragmentManager fragmentManager, String viewModelTag) {
  
        ViewModelHolder<T> retainedViewModel =
                (ViewModelHolder<T>) fragmentManager
                        .findFragmentByTag(viewModelTag);

        if (retainedViewModel != null && retainedViewModel.getViewModel() != null) {
            return retainedViewModel.getViewModel();
        }
        return null;
    }

2、当在viewpager中使用fragment时候,需要注意的事项:

·如果是嵌套的fragment中使用viewpager,在初始化adapter的时候,不能使用getSupportManager,而应该使用getChildSupportManager,否则会报错。

/**
    返回一个fragmentmanager,如果是嵌套使用的fragment
     * Return a private FragmentManager for placing and managing Fragments
     * inside of this Fragment.
     */
    @NonNull
    final public FragmentManager getChildFragmentManager() {
        if (mHost == null) {
            throw new IllegalStateException("Fragment " + this + " has not been attached yet.");
        }
        return mChildFragmentManager;
    }

·viewpager中的adapter 系统有提供了两个PagerAdapter的实现类

这两个有什么区别? 先来看FragmentPagerAdapter,他在初始化的时候做了些什么事

代码中已经写的很清楚了,先去查找本地中是否已经有存在tag的fragment实例,有的话直接attach进来即可,如果没有的话,在add进来,再来看看destroyItem

@Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        //只对fragment进行detach操作,并没有remove,这时候只是销毁了视图,他的实例还在的
        mCurTransaction.detach((Fragment)object);
    }

接下来看看FragmentStatePagerAdapter如何初始化instantiateItem

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 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) {
        //先初始化fragment数组
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
            fragment.setUserVisibleHint(false);
        }
        //添加fragment
        mFragments.set(position, fragment);
        
        mCurTransaction.add(container.getId(), fragment);

        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        }

        return fragment;
    }

上面的注释都写的很清楚了,应该可以看懂,再来看看destroyItem

@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);
        //设置当前的frament为空
        mFragments.set(position, null);
        //在栈中移除当前这个fragment,这时候会走destory,并且数据也会丢失,需要另外做处理
        mCurTransaction.remove(fragment);
    }

看完上面的两个分析应该知道这两个adapter该用在什么场景下了吧,当item比较少的时候,可以用FragmentPagerAdapter,因为item较少,不会加载很多的界面,所以内存方面可以控制的,不会加载大量的内存,如果是item有很多的话,比如类似头条那种的,那就应该使用FragmentStatePagerAdapter来做,这样可以保证内存的及时回收,而不会导致oom的问题。

3、如何防止viewpager的缓存item回收

·如果item不多的话,可以设置这个方法:setOffscreenPageLimit,这个在item可控制的情况下,可以这么设置,在比较多的item的情况下,不建议使用这个

·重写destroyItem,类似下面这样

override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
//        super.destroyItem(container, position, `object`)
    }

未完待续。。。

以上的内容参考:www.jianshu.com/p/3d27ddc95…