RecyclerView与Glide的缓存机制

详解RecyclerView的四级缓存机制,包括可见缓存、缓存列表、自定义缓存和缓存池的工作原理,以及Glide的四级缓存流程,帮助理解Android应用中图片加载和列表展示的性能优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RecyclerView与Glide的缓存机制

RecyclerView的item加载流程:

1、界面需要填充布局,将需求交给回收池

2、如果回收池没有对应的缓存布局,那么界面就会去找适配器,适配器执行onCreateViewHolder方法返回一个ViewHolder对象,然后拿到一个view,把它填充到界面上。(对于第一屏来说,每个item都要执行onCreatteViewHolder)

3、第一屏完成填充后,上滑屏幕,屏幕底部就会出现空缺,这时就会触发加载机制

4、仍然会从回收池中找,然后找到了对应的view,适配器就执行onBindViewHolder方法,对这个view的数据进行刷新,刷新后重新被添加到view数上

RecyclerView核心组件

1、回收池

能回收任意item控件,并返回符合类型的item控件;

比如onBindViewHolder方法中的第一个参数holder就是从回收池中返回的。

它是怎么工作的呢?

RecyclerView需要填充view时,首先会从回收池中找对应的view,如果回收池中有对应的view,那就直接取这个view出来然后填充即可;如果回收池没有对应的view,那就去找适配器,这时候,适配器就会调用onCreateViewHolder,把ViewHolder(包含view)创建出来,并返回给RecyclerView。

在加载第一屏时,是最消耗性能的,因为这时候回收池并不存在可复用的view,对于第一屏中的每个item,都需要对其进行创建以及inflate等操作。

怎么对第一屏的性能进行优化?

首先我们需要分析一下,为什么第一屏消耗性能最大,就是因为加载第一屏的时候,都需要执行一次onCreateViewHolder方法,然后在onCreateViewHolder回调方法中,我们还需要在里面执行inflate方法,从xml中加载出view对象出来。比如这样:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    //在onCreateViewHolder中inflate出view
    val mView = LayoutInflater.from(context).inflate(R.layout.item_test, parent, false)
    return MyViewHolder(mView)
}

相信大家知道,inflate内部会经过dom解析,然后通过反射来得到view对象,这个过程是比较耗时的,尤其是如果item布局相当繁琐的时候,耗时会更加严重。

既然知道了原因,那么就可以对症下药了,很容易,我们就可以想到,我们可以对View进行提前初始化。

比如:我们可以提前对第一屏的所有view进行inflate,将其返回的view对象保存在一个集合中。然后在回调onCreateViewHolder的时候,就直接从这个集合中取对应的view出来就行了。

具体需要提前初始化布局的数量需要自己衡量,一般来说,执行了几次onCreateViewHolder,就提前初始化几个view就行了。

下面就简单的演示一下:

MainActivity中:
val cacheViewList: ArrayList<View> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_text_mod)
        rv = findViewById(R.id.rv)
        linearLayoutManager = LinearLayoutManager(this)
        rv?.layoutManager = linearLayoutManager

        //提前初始化布局,假设需要提前初始化10个view
        for (i in 1..10) {
            val mView = LayoutInflater.from(this).inflate(R.layout.item_test, rv, false)
            cacheList.add(mView)
        }

        adapter = MyAdapter(datas, cacheViewList)
        rv?.adapter = adapter
}

MyAdapter中:
class MyAdapter(
    private val data: ArrayList<String>,
    private val cacheList: ArrayList<View>
) : RecyclerView.Adapter<MyViewHolder>() {

    var pos = 0

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(cacheList[pos++])
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.itemView.findViewById<TextView>(R.id.tv).text = data[position]
    }

    override fun getItemCount(): Int {
        return data.size
    }
}

还有一种情况,上滑屏幕时,屏幕最上面的那个item被移出了屏幕,这时候,屏幕最下面就会出现空缺,这时候就会给回收池传递一个viewType值,然后从回收池中找出符合要求的View出来,然后调用onBindViewHolder方法,将这个view回调出去。

Adapter里面有一个getItemViewType方法,它返回是View的类型。

2、适配器

Adapter,辅助RecyclerView实现列表展示,适配器模式,将用户界面展示与交互分离。

适配器主要用来实例化View对象,以及刷新View对象的内容。

3、RecyclerView

是做触摸事件的交互,主要实现边界值判断,根据用户的触摸反馈,协调回收池对象与适配器对象之间的工作。

RecyclerView只是个容器,它负责展示内容,但是具体展示什么内容,它并不负责处理。

那么由谁来控制具体展示什么内容呢?那就是适配器。

RecyclerView四级缓存机制

1、可见缓存:mAttachedScrap

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();

可见缓存指的是,针对于屏幕上看得见的item的缓存,这个缓存可以方便我们遍历,也方便我们更新。

在LayoutManager对views进行布局时,会将RecyclerView上的Views全部暂存到mAttachedScrap中,以备后续使用,该缓存中的ViewHolder的特性是,如果和RecyclerView上的position或者itemId匹配上了,那么认为是干净的ViewHolder,是可以直接拿出来使用的,无需调用onBindViewHolder方法。

mAttachedScrap它是一个类型为ViewHolder的ArrayList。

它的容量是无限大的。如果控件是1像素,屏幕高度是100像素,那么可见缓存的容量就是100。

触发该层级缓存的场景一般是调用notifyItemXXX方法。

2、缓存列表:mCachedViews

final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

跟可见缓存一样,它也是一个类型为ViewHolder的ArrayList,不同的是,它的容量大小是2

但是mCacheViews可以是通过如下方法改变缓存的大小的:

public void setItemViewCacheSize(int size) {
    mRecycler.setViewCacheSize(size);
}

超过数量的话按照先入先出原则,移出头部的itemView保存到RecyclerPool缓存池(如果有自定义缓存就会保存到自定义缓存里),

当一个item(ViewHolder)被移除出屏幕后,就把这个item放到这个缓存集合中,

当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView。

该层级缓存触发的一个常见的场景是滑动RecyclerView或者执行notifyXXX方法会触发该缓存。

3、自定义缓存:mViewCacheExtension

private ViewCacheExtension mViewCacheExtension;

//通过这个方法设置进来
void setViewCacheExtension(ViewCacheExtension extension) {
            mViewCacheExtension = extension;
}

这个是用户自定义的缓存,可通过setViewCacheExtension方法将自定义缓存设置进来。这个一般比较少用。

4、缓存池(回收池):mRecyclerPool

RecycledViewPool mRecyclerPool;

mCachedViews满了后或者adapter被更换,会将mCachedViews中缓存的View移到的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView

RecycledViewPool有一个静态内部类ScrapData,并且维维护了一个ScrapData类型的集合。如下:

static class ScrapData {
    final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
    int mMaxScrap = DEFAULT_MAX_SCRAP;
    long mCreateRunningAverageNs = 0;
    long mBindRunningAverageNs = 0;
}
//维护了一个ScrapData类型的集合
SparseArray<ScrapData> mScrap = new SparseArray<>();

RecyclerViewPool类提供了修改不同类型View的最大缓存数量的API:

public void setMaxRecycledViews(int viewType, int max) {
    ScrapData scrapData = getScrapDataForType(viewType);
    scrapData.mMaxScrap = max;
    final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
    while (scrapHeap.size() > max) {
        scrapHeap.remove(scrapHeap.size() - 1);
    }
}

四级缓存按照顺序需要依次读取。所以完整缓存流程是:

一、保存缓存流程:

  • 插入或是删除itemView时,先把屏幕内的ViewHolder保存至mAttachedScrap
  • 滑动屏幕的时候,先消失的itemView会保存到mCachedViews中,
  • mCachedViews超过数量就按照先入先出原则,移出头部的itemView保存到mRecyclerPool(如果有自定义缓存就会保存到自定义缓存里),
  • RecyclerPool缓存池会按照itemViewitemtype进行保存,每个itemType缓存个数为5个,超过就会被回收。

二、获取缓存流程:

  • 先从mAttachedScrap中获取,
  • 如果获取失败,则从mCachedViews中获取
  • 如果再获取失败,则从自定义缓存中获取缓存
  • 如果还获取失败,则从mRecyclerPool中获取
  • 如果仍获取失败,则重新创建ViewHolder

Glide的四级缓存

当我们使用Glide加载一张图片的时候,glide会依次的从活动缓存、内存缓存、磁盘缓存、数据来源,这四个地方查找是否拥有我们所需要加载的图片资源。

活动缓存:当我们要加载一张新图片的时候,就会先从活动缓存中,即ActiveResource中查找。对于我们正在使用的图片,那么它将会存在于ActiveResource中,活动缓存的大小是没有限制的,在ActiveResource里面,使用一个弱引用的WeakHashMap集合,来保存我们正在使用的图片。

内存缓存:如果ActiveResource里找不到资源,则从内存缓存中查找,如果内存缓存中有数据,则将数据剪切到活动缓存,然后再去显示图片。内存缓存是基于LRU机制来实现的。它跟活动缓存ActiveResource是互斥的,也就是说,在ActiveResource中的缓存,就不可能再内存缓存,在内存缓存中就不可能再活动缓存中。

磁盘缓存:如果内存缓存中也找不到,就从磁盘缓存中找,当我们要去加载一张图片的时候,在Android中展示一张图片之前,可能会对这种图片进行一些缩放操作,或者加圆角等操作,这些经过处理后的图片,在glide中可以把处理完的图片,单独放到一个文件里面去保存起来,这样的话,在我们下次想要以同样的规格加载这张图片的时候,就可以直接从磁盘缓存里直接加载,不需要再去做这个缩放或圆角等操作了。

数据来源:如果磁盘缓存找不到,就会从数据来源中找,也就是原始图片存在的地方,如果是从数据来源中取到的图片,还需要对图片进行一些相应的处理,相对来说,加载速度会比较慢。

那么,为什么要有活动缓存?

假设在没有活动缓存的情况下,要显示一张图片,将直接从内存缓存中取出,但是内存缓存是LRU机制,也就是说如果内存缓存一直有数据存入,最旧的缓存将被清除,而恰好这个要被清除的缓存正在被ImageView展示着,就会出现空指针的问题了。而如果中间加个活动缓存来存放正在显示的图片数据,就可以解决这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一场雪ycx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值