携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情
本篇核心主要是讲解
Glide中Bitmap池复用中的一个小细节:如何通过宽度、高度和像素格式从对象池中获取/回收可以复用的Bitmap。
文章灵感来源
最近由于项目优化需求,有一个RecycleView列表中的每个子item中可能会包括很多网络图片,由于项目这块没有使用Glide加载网络图片,单独写了一套网络图片的下载以及界面显示逻辑。
这样就产生了一个问题,当这个列表中存在很多图片的时候,就会从本地加载图片生成大量Bitmap,以至于存在引发OOM的风险。
所以就想到了从本地加载图片生成Bitmap时,可以借助于BitmapFactory.Options的inBitmap属性实现Bitmap复用,减少RecycleView列表滑动过程中Bitmap频繁的创建以及引发的频繁GC回收。
由于Glide本身就实现了一套Bitmap复用机制,所以就找几篇文章了解下这块的实现原理:
BitmapPool 了解吗?Glide 是如何实现 Bitmap 复用的?
上面文章写的都很不错,很推荐大家阅读,但是发现这两篇文章都没有讲解一个细节:
比如在android4.4之下如何通过
Bitmap的宽、高和像素格式从LruBitmapPool中拿到可以复用的Bitmap的,这中间经过了一个怎么样的过程。
所以接下来我们就来分析下这块原理,其中Glide中实现Bitmap复用的类为LruBitmapPool。
LruBitmapPool如何操作Bitmap复用?
大家如果之前不了解Bitmap整体复用流程的,可以通过上面那两篇推荐的文章了解下,这里我们直接从AttributeStrategy(android4.4之LruBitmapPool下使用此类实现可复用Bitmap的读写)作为入口进行分析。
一.LruBitmapPool.put()存储可复用Bitmap
该方法就是为了存储可以复用的Bitmap。
这个方法开头做一些Bitmap是否为空、是否被回收等相关校验,核心代码在于strategy.put(bitmap),其中这个strategy在Android4.4之下的实现类就是AttributeStrategy,接下来我们看下AttributeStrategy的put()方法:
-
通过
KeyPool生成一个Key,并传入要放到复用池的Bitmap的宽高像素格式信息,我们深入看下这块逻辑:-
首先调用
get()方法生成一个Key一开始这个
KeyPool肯定是空的,所以最终会调用create()方法创建一个新的Key对象 -
将传入的宽高像素格式信息赋值给
Key
-
-
将生成的
key和Bitmap存放到GroupedLinkedMap<Key, Bitmap>类中GroupedLinkedMap中存有一个Map集合,key就是Key类型,value则是一个集合,当调用put()方法时,首先从这个Map中通过key尝试获取之前写入的Bitmap集合,这就是关键核心了。Map调用其get()方法时,首先是计算传入key的hashCode,再根据这个hashcode真正从散列表中获取对应的value值,而Key就恰恰重写了hashCode()方法:可以看到这个
hashCode()就是借助上面传给Key的Bitmap宽高像素格式计算的最终结果值。紧接着如果从
Map中获取的集合不为空,就将我们这个传入的Key回收掉并放入KeyPool中,为什么会回收掉呢?因为当从
Map中获取的集合不为空,代表Map中已经存在了和传入的Key的hashCode()相同的键值,所以这个就直接回收掉,方便进行复用。最终我们就实现了可复用的
Bitmap写入到了Map散列表中,其中key就代表Bitmap的宽高像素格式。
二.LruBitmapPool.get()获取复用Bitmap
这里最终会调用到AttributeStrategy的get()方法,并传入想要复用的Bitmap的宽高像素格式要求:
-
和上面的
put()方法一样,获取一个Key类型的对象,如果KeyPool缓存池中存在可以复用的Key就直接获取,不存在就调用create()方法创建一个新的Key; -
调用
GroupedLinkedMap的get(Key)方法:从
Map类型的keyToEntry通过key获取之前写入的Bitmap集合(可能不存在),关键这里,Map也是通过计算Key的hashCode()去获取对应value,再从这个value对应的集合中返回最终Bitmap(可能为空)。、同样这里也有一次回收
Key的逻辑,原因同上。
总结
看这块复用代码的时候一开始就很奇怪:
在调用AttributeStrategy的get()方法获取可复用的Bitmap时,每次都是创建一个key(暂不考虑key复用的情况)从上面的keyToEntry尝试获取可复用的Bitmap。
这个时候我就很奇怪每次创建的都是新的Key对象,那怎么从Map中获取可复用的Bitmap的,毕竟键值都不相同,怎么可能拿到。
经过一番痛苦思考,才恍然大悟:
Map读写键值并不是通过传入的Key对象来实现,而是计算Key的hashCode()来实现的读写,再回头一看,Key本身就重写了hashCode()方法,并借助于传入的宽高像素格式计算出最终结果,这才明白过来。
所以上面的
get()和set()方法都会主动调用一次Key的init()方法传入我们要存储或读取的Bitmap信息,这样就改变了Key的hashCode()结果值,从而去从Map中读写Bitmap时才不管你的Key对象是新的还是旧的,只要你的hashCode()对,就返回给你需要的Bitmap。