肢解Glide:LruBitmapPool如何具体操作Bitmap复用?

9,080 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

本篇核心主要是讲解Glide中Bitmap池复用中的一个小细节:如何通过宽度、高度和像素格式从对象池中获取/回收可以复用的Bitmap

文章灵感来源

最近由于项目优化需求,有一个RecycleView列表中的每个子item中可能会包括很多网络图片,由于项目这块没有使用Glide加载网络图片,单独写了一套网络图片的下载以及界面显示逻辑。

这样就产生了一个问题,当这个列表中存在很多图片的时候,就会从本地加载图片生成大量Bitmap,以至于存在引发OOM的风险。

所以就想到了从本地加载图片生成Bitmap时,可以借助于BitmapFactory.OptionsinBitmap属性实现Bitmap复用,减少RecycleView列表滑动过程中Bitmap频繁的创建以及引发的频繁GC回收。

由于Glide本身就实现了一套Bitmap复用机制,所以就找几篇文章了解下这块的实现原理:

BitmapPool 了解吗?Glide 是如何实现 Bitmap 复用的?

Coil 和 Glide 的 Bitmap 缓存复用机制

上面文章写的都很不错,很推荐大家阅读,但是发现这两篇文章都没有讲解一个细节:

比如在android4.4之下如何通过Bitmap的宽、高和像素格式从LruBitmapPool中拿到可以复用的Bitmap的,这中间经过了一个怎么样的过程。

所以接下来我们就来分析下这块原理,其中Glide中实现Bitmap复用的类为LruBitmapPool

LruBitmapPool如何操作Bitmap复用?

大家如果之前不了解Bitmap整体复用流程的,可以通过上面那两篇推荐的文章了解下,这里我们直接从AttributeStrategy(android4.4之LruBitmapPool下使用此类实现可复用Bitmap的读写)作为入口进行分析。

一.LruBitmapPool.put()存储可复用Bitmap

该方法就是为了存储可以复用的Bitmap

image.png

这个方法开头做一些Bitmap是否为空、是否被回收等相关校验,核心代码在于strategy.put(bitmap),其中这个strategy在Android4.4之下的实现类就是AttributeStrategy,接下来我们看下AttributeStrategyput()方法:

image.png

  1. 通过KeyPool生成一个Key,并传入要放到复用池的Bitmap的宽高像素格式信息,我们深入看下这块逻辑:

    image.png

    • 首先调用get()方法生成一个Key

      image.png

      一开始这个KeyPool肯定是空的,所以最终会调用create()方法创建一个新的Key对象

    • 将传入的宽高像素格式信息赋值给Key

      image.png
  2. 将生成的keyBitmap存放到GroupedLinkedMap<Key, Bitmap>类中

    image.png

    GroupedLinkedMap中存有一个Map集合,key就是Key类型,value则是一个集合,当调用put()方法时,首先从这个Map中通过key尝试获取之前写入的Bitmap集合,这就是关键核心了。

    Map调用其get()方法时,首先是计算传入keyhashCode,再根据这个hashcode真正从散列表中获取对应的value值,而Key就恰恰重写了hashCode()方法:

    image.png

    可以看到这个hashCode()就是借助上面传给KeyBitmap宽高像素格式计算的最终结果值。

    紧接着如果从Map中获取的集合不为空,就将我们这个传入的Key回收掉并放入KeyPool中,为什么会回收掉呢?

    因为当从Map中获取的集合不为空,代表Map中已经存在了和传入的KeyhashCode()相同的键值,所以这个就直接回收掉,方便进行复用。

    最终我们就实现了可复用的Bitmap写入到了Map散列表中,其中key就代表Bitmap的宽高像素格式。

二.LruBitmapPool.get()获取复用Bitmap

这里最终会调用到AttributeStrategyget()方法,并传入想要复用的Bitmap的宽高像素格式要求:

image.png

  1. 和上面的put()方法一样,获取一个Key类型的对象,如果KeyPool缓存池中存在可以复用的Key就直接获取,不存在就调用create()方法创建一个新的Key

  2. 调用GroupedLinkedMapget(Key)方法:

    image.png

    Map类型的keyToEntry通过key获取之前写入的Bitmap集合(可能不存在),关键这里,Map也是通过计算KeyhashCode()去获取对应value,再从这个value对应的集合中返回最终Bitmap(可能为空)。、

    同样这里也有一次回收Key的逻辑,原因同上。

总结

看这块复用代码的时候一开始就很奇怪:

image.png

在调用AttributeStrategyget()方法获取可复用的Bitmap时,每次都是创建一个key(暂不考虑key复用的情况)从上面的keyToEntry尝试获取可复用的Bitmap

这个时候我就很奇怪每次创建的都是新的Key对象,那怎么从Map中获取可复用的Bitmap的,毕竟键值都不相同,怎么可能拿到。

经过一番痛苦思考,才恍然大悟:

Map读写键值并不是通过传入的Key对象来实现,而是计算KeyhashCode()来实现的读写,再回头一看,Key本身就重写了hashCode()方法,并借助于传入的宽高像素格式计算出最终结果,这才明白过来。

所以上面的get()set()方法都会主动调用一次Keyinit()方法传入我们要存储或读取的Bitmap信息,这样就改变了KeyhashCode()结果值,从而去从Map中读写Bitmap时才不管你的Key对象是新的还是旧的,只要你的hashCode()对,就返回给你需要的Bitmap