FragmentPagerAdapter、FragmentStatePagerAdapter和FragmentStateAdapter的区别你知道嘛

3,012 阅读3分钟

前段时间在给公司项目做优化,换用了ViewPager,可以左右切换页面,交互更顺滑,改完之后发现Fragment总是不复用,每次切换回来会重复走onCreateView()onDestroyView()生命周期???这咋能行呢,于是就在onCreateView()做了简单的判断,因为项目用的是ViewBinding,所以就ViewBinding不为空就直接返回ViewBinding.getRoot()了。这几天在看Android JetpackViewPager2发现多了个FragmentStateAdapter用法比之前简单不少:

class CollectionDemoFragment : Fragment() {
    // When requested, this adapter returns a DemoObjectFragment,
    // representing an object in the collection.
    private lateinit var demoCollectionAdapter: DemoCollectionAdapter
    private lateinit var viewPager: ViewPager2

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.collection_demo, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        demoCollectionAdapter = DemoCollectionAdapter(this)
        viewPager = view.findViewById(R.id.pager)
        viewPager.adapter = demoCollectionAdapter
    }
}
//重点在这里 就重写了两个方法
class DemoCollectionAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
    override fun getItemCount(): Int = 100

    override fun createFragment(position: Int): Fragment {
        // Return a NEW fragment instance in createFragment(int)
        val fragment = DemoObjectFragment()
        fragment.arguments = Bundle().apply {
            // Our object is just an integer :-P
            putInt(ARG_OBJECT, position + 1)
        }
        return fragment
    }
}

然后兴冲冲的给项目安排上,感觉自己又跟上大佬的脚步了,可是无意间看到居然没有重写走onCreateView()onDestroyView()生命周期(顺便提一下FragmentManager.FragmentLifecycleCallbacks可以监控Fragment生命周期类似Activity生命周期监听Application.ActivityLifecycleCallbacks),本着打破砂锅问到底的原则详细了解了下FragmentPagerAdapter、FragmentStatePagerAdapter和FragmentStateAdapter三者的区别。

FragmentPagerAdapter

源于androidx.fragment,继承于PagerAdapter,已于androidx.fragment 1.3.0版本废弃,重点在instantiateItem()

    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        //这个fragment已经存在过?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        //已经存在执行attach(fragment);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {//没有存在通过getItem()获取
            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);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            } else {
                fragment.setUserVisibleHint(false);
            }
        }
        return fragment;
    }

可以看到FragmentPagerAdapter的处理逻辑是存在这个Fragment就会执行attach()必然会重走一遍Fragment的生命周期,并不会复用之前创建的Fragment,显然跟我们要的不符,这个时候我们就用到了FragmentStatePagerAdapter

FragmentStatePagerAdapter

同样源于androidx.fragment,继承于PagerAdapter,已于androidx.fragment 1.3.0版本废弃,同样我们也看一下instantiateItem()里的实现

    @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.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;
    }

可以看到FragmentStatePagerAdapter有缓存Fragment队列,当Fragment实例化之后会取已经实例化的Fragment,并且多了个SavedState的逻辑,用于保存Fragment的状态(滑过后会保存当前界面,以及下一个界面和上一个界面(如果有),最多保存3个,其他会被销毁掉,可以通过ViewPager的setOffscreenPageLimit(int limit)设置) 但在回调onDestroy()方法之前会回调onSaveInstanceState(Bundle outState)方法来保存Fragment的状态,下次Fragment显示时通过onCreate(Bundle savedInstanceState)把存储的状态值取出来,FragmentStatePagerAdapter比较适合页面比较多的情况。

FragmentStateAdapter

源于ViewPager2FragmentPagerAdapterFragmentStatePagerAdapter的替换类,继承于RecyclerView.Adapter, 与FragmentStatePagerAdapter相似,该适配器实现了StatefulAdapter用于保存Fragment的状态。

private void ensureFragment(int position) {
        long itemId = getItemId(position);
        if (!mFragments.containsKey(itemId)) {
            // TODO(133419201): check if a Fragment provided here is a new Fragment
            Fragment newFragment = createFragment(position);
            newFragment.setInitialSavedState(mSavedStates.get(itemId));
            mFragments.put(itemId, newFragment);
        }
    }

可以看到FragmentStateAdapter同样有缓存Fragment队列