千家信息网

Android ViewPager2中缓存和复用机制的示例分析

发表于:2026-11-03 作者:千家信息网编辑
千家信息网最后更新 2026年11月03日,这篇文章主要为大家展示了"Android ViewPager2中缓存和复用机制的示例分析",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Android Vi
千家信息网最后更新 2026年11月03日Android ViewPager2中缓存和复用机制的示例分析

这篇文章主要为大家展示了"Android ViewPager2中缓存和复用机制的示例分析",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Android ViewPager2中缓存和复用机制的示例分析"这篇文章吧。

1. 前言

众所周知ViewPager2是ViewPager的替代版本。它解决了ViewPager的一些痛点,包括支持right-to-left布局,支持垂直方向滑动,支持可修改的Fragment集合等。ViewPager2内部是使用RecyclerView来实现的。

所以它继承了RecyclerView的优势,包含但不限于以下:

  1. 支持横向和垂直方向布局

  2. 支持嵌套滑动

  3. 支持ItemPrefetch(预加载)功能

  4. 支持三级缓存

ViewPager2相对于RecyclerView,它又扩展出了以下功能

  1. 支持屏蔽用户触摸功能setUserInputEnabled

  2. 支持模拟拖拽功能fakeDragBy

  3. 支持离屏显示功能setOffscreenPageLimit

  4. 支持显示Fragment的适配器FragmentStateAdapter

如果熟悉RecyclerView,那么上手ViewPager2将会非常简单。可以简单把ViewPager2想象成每个ItemView都是全屏的RecyclerView。本文将重点讲解ViewPager2的离屏显示功能和基于FragmentStateAdapter的缓存机制。

2. 回顾RecyclerView缓存机制

本章节,简单回顾下RecyclerView缓存机制。RecyclerView有三级缓存,简单起见,这里只介绍mViewCaches和mRecyclerPool两种缓存池。更多关于RecyclerView的缓存原理,请移步公众号相关文章。

  1. mViewCaches:该缓存离UI更近,效率更高,它的特点是只要position能对应上,就可以直接复用ViewHolder,无需重新绑定,该缓存池是用队列实现的,先进先出,默认大小为2,如果RecyclerView开启了预抓取功能,则缓存池大小为2+预抓取个数,默认预抓取个数为1。所以默认开启预抓取缓存池大小为3。

  2. mRecyclerPool:该缓存池离UI最远,效率比mViewCaches低,回收到该缓存池的ViewHolder会将数据解绑,当复用该ViewHolder时,需要重新绑定数据。它的数据结构是类似HashMap。key为itemType,value是数组,value存储ViewHolder,数组默认大小为5,最多每种itemType的ViewHolder可以存储5个。

3. offscreenPageLimit原理

//androidx.viewpager2:ViewPager2:1.0.0@aar//ViewPager2.javapublic void setOffscreenPageLimit(@OffscreenPageLimit int limit) {    if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {        throw new IllegalArgumentException(                "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");    }    mOffscreenPageLimit = limit;    mRecyclerView.requestLayout();  }

调用setOffscreenPageLimit方法就可以为ViewPager2设置离屏显示的个数,默认值为-1。如果设置不当,会抛异常。我们看到该方法,只是给mOffscreenPageLimit赋值。为什么就能实现离屏显示功能呢?如下代码

//androidx.viewpager2:ViewPager2:1.0.0@aar//ViewPager2$LinearLayoutManagerImpl@Overrideprotected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,        @NonNull int[] extraLayoutSpace) {    int pageLimit = getOffscreenPageLimit();    if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {        super.calculateExtraLayoutSpace(state, extraLayoutSpace);        return;    }    final int offscreenSpace = getPageSize() * pageLimit;    extraLayoutSpace[0] = offscreenSpace;    extraLayoutSpace[1] = offscreenSpace;}

以水平滑动ViewPager2为例:getPageSize()表示ViewPager2的宽度,离屏的空间大小为getPageSize() * pageLimit。extraLayoutSpace[0]表示左边的大小,extraLayoutSpace[1]表示右边的大小。

假设设置offscreenPageLimit为1,简单讲,Android系统会默认把画布宽度增加到3倍。左右两边各有一个离屏ViewPager2的宽度。

4. FragmentStateAdapter原理以及缓存机制

4.1 简单使用

FragmentStateAdapter继承自RecyclerView.Adapter。它有一个抽象方法,createFragment()。它能将Fragment与ViewPager2完美结合。

public abstract class FragmentStateAdapter extends        RecyclerView.Adapter implements StatefulAdapter {    public abstract Fragment createFragment(int position);}

使用FragmentStateAdapter非常简单,Demo如下

class ViewPager2WithFragmentsActivity : AppCompatActivity() {    private lateinit var mViewPager2: ViewPager2    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_recycler_view_view_pager2)        mViewPager2 = findViewById(R.id.viewPager2)        (mViewPager2.getChildAt(0) as RecyclerView).layoutManager?.apply {//            isItemPrefetchEnabled = false        }        mViewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL        mViewPager2.adapter = MyAdapter(this)//        mViewPager2.offscreenPageLimit = 1    }    inner class MyAdapter(fragmentActivity: FragmentActivity) :        FragmentStateAdapter(fragmentActivity) {        override fun getItemCount(): Int {            return 100        }        override fun createFragment(position: Int): Fragment {            return MyFragment("Item $position")        }    }    class MyFragment(val text: String) : Fragment() {        init {            println("MyFragment $text")        }        override fun onCreateView(            inflater: LayoutInflater,            container: ViewGroup?,            savedInstanceState: Bundle?        ): View? {            var view = layoutInflater.inflate(R.layout.view_item_view_pager_snap, container)            view.findViewById(R.id.text_view).text = text            return view;        }    }}

4.2 原理

首先FragmentStateAdapter对应的ViewHolder定义如下,它只是返回一个简单的带有id的FrameLayout。由此可以看出,FragmentStateAdapter并不复用Fragment,它仅仅是复用FrameLayout而已。

public final class FragmentViewHolder extends ViewHolder {    private FragmentViewHolder(@NonNull FrameLayout container) {        super(container);    }    @NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {        FrameLayout container = new FrameLayout(parent.getContext());        container.setLayoutParams(                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                        ViewGroup.LayoutParams.MATCH_PARENT));        container.setId(ViewCompat.generateViewId());        container.setSaveEnabled(false);        return new FragmentViewHolder(container);    }    @NonNull FrameLayout getContainer() {        return (FrameLayout) itemView;    }}

然后介绍FragmentStateAdapter中两个非常重要的数据结构:

final LongSparseArray mFragments = new LongSparseArray<>();private final LongSparseArray mItemIdToViewHolder = new LongSparseArray<>();

mFragments:是position与Fragment的映射表。随着position的增长,Fragment是会不断的新建出来的。 Fragment可以被缓存起来,当它被回收后无法重复使用。

Fragment什么时候会被回收掉呢?

mItemIdToViewHolder:是position与ViewHolder的Id的映射表。由于ViewHolder是RecyclerView缓存机制的载体。所以随着position的增长,ViewHolder并不会像Fragment那样不断的新建出来,而是会充分利用RecyclerView的复用机制。所以如下图,position 4处打上了一个大大的问号,具体的值是不确定的,它由缓存的大小以及离屏个数共同决定的。

接下来我们讲解onViewRecycled()。当ViewHolder从mViewCaches缓存中移出到mRecyclerPool缓存中时会调用该方法

@Overridepublic final void onViewRecycled(@NonNull FragmentViewHolder holder) {    final int viewHolderId = holder.getContainer().getId();    final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH    if (boundItemId != null) {        removeFragment(boundItemId);        mItemIdToViewHolder.remove(boundItemId);    }}

该方法的作用是,当ViewHolder回收到RecyclerPool中时,将ViewHolder相关的信息从上面两张表中移除。

举例 当ViewHolder1发生回收时,position 0对应的信息从两张表中删除

最后讲解onBindViewHolder方法

@Overridepublic final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {    final long itemId = holder.getItemId();    final int viewHolderId = holder.getContainer().getId();    final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH    if (boundItemId != null && boundItemId != itemId) {        removeFragment(boundItemId);        mItemIdToViewHolder.remove(boundItemId);    }    mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry    ensureFragment(position);    /** Special case when {@link RecyclerView} decides to keep the {@link container}     * attached to the window, but not to the view hierarchy (i.e. parent is null) */    final FrameLayout container = holder.getContainer();    if (ViewCompat.isAttachedToWindow(container)) {        if (container.getParent() != null) {            throw new IllegalStateException("Design assumption violated.");        }        container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {            @Override            public void onLayoutChange(View v, int left, int top, int right, int bottom,                    int oldLeft, int oldTop, int oldRight, int oldBottom) {                if (container.getParent() != null) {                    container.removeOnLayoutChangeListener(this);                    placeFragmentInViewHolder(holder);                }            }        });    }    gcFragments();}

该方法可以分成3个部分:

  1. 检查该复用的ViewHolder在两张表中是否还有残留的数据,如果有,将它从两张表中移除掉。

  2. 新建Fragment,并将ViewHolder与Fragment和position的信息注册到两张表中

  3. 在合适的时机把Fragment展示在ViewPager2上。

大概的脉络就是这样,为了避免文章冗余,其它的细支且也蛮重要的方法就没有列出来

5. 案例讲解回收机制

5.1 默认情况

默认情况:offscreenPageLimit = -1,开启预抓取功能

因为开启了预抓取,所以mViewCaches大小为3。

  1. 刚开始进入ViewPager2,没有触发Touch事件,不会触发预抓取,所以只有Fragment1

  2. 滑动到Fragment2,会触发Fragment3预抓取,由于offscreenPageLimit = -1,所以只有Fragment2会展示在ViewPager2上,1和3进入mViewCaches缓存中

  3. 滑动到Fragment3。1、2、4进入mViewCaches缓存中

  4. 滑动到Fragment4。2、3、5进入mViewCaches缓存中,由于缓存数量为3,所以1被挤出到mRecyclerPool缓存中,同时把Fragment1从mFragments中移除掉

  5. 滑动到Fragment5。Fragment6会复用Fragment1对应的ViewHolder。3、4、6进入mViewCaches缓存中,2被挤出到mRecyclerPool缓存中

5.2 offscreenPageLimit=1

offscreenPageLimit=1,所以ViewPager2一下子能展示3屏Fragment,左右各显示一屏

  1. Fragment1左边没有数据,所以屏幕只有1和2

  2. 滑动到fragment2,1、2、3显示在屏幕上(1和3肉眼不可见,下同),同时预抓取4放入mViewCaches

  3. 滑动到fragment3,2、3、4显示在屏幕上,1和5放入mViewCaches

  4. 滑动到fragment4,3、4、5显示在屏幕上,1、2、6放入mViewCaches

  5. 滑动到fragment5,4、5、6显示在屏幕上,2、3、7放入mViewCaches,1被回收到mRecyclerPool缓存中。Fragment1同时从mFragments中删除掉

以上是"Android ViewPager2中缓存和复用机制的示例分析"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

缓存 支持 机制 复用 功能 大小 方法 数据 屏幕 个数 原理 示例 分析 信息 内容 只有 同时 宽度 篇文章 重要 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技名片 云信互联网科技有限公司 互联网最新的科技 互联网下的科技发展 苏州互联网科技贷款公司 互联网科技logo设计 碳银互联网科技有限公司 兼蜜互联网科技 互联网科技公司介绍 互联网科技素材视频竖屏 西藏七维空间互联网科技有限公司 互联网科技博主是什么意思 恒银科技与互联网金融 湖北码尼互联网科技有限公司电话 彤创互联网科技有限公司 知域互联网科技有限公司大骗子 工业互联网等科技实现智能化赋能 卓翼科技产业互联网合作 南通创西澳互联网科技有限公司 版纳互联网科技选哪家 科技股互联网 智网互联网科技 科技类的互联网平台 银川新华互联网科技学校地址 佳云科技和互联网广告 浙南科技城互联网大厦建设工程 众安科技工业互联网合作 怎样加入科技互联网 互联网风格科技公司名称 互联网科技职业技能培训学校
0