预加载下一个页面的图片
Glide 的preload 应该有不少人都用过,但应该多数情况下都是在一个列表页中preload。 有一种情况的preaload 会复杂一些
列表页A ---> 详情页B。 例如小红书 你会发现列表页到详情页的时候 多数情况下,详情页的图片加载是瞬间就好了的。
有人会说,这个不是很简单吗,我在列表页中预下载一下详情页的图片。 但其实你如果自己做的时候试试看, 仅仅用预下载这个机制 是完全做不到小红书的效果的。
拿Glide 这个框架来举例,详情页B 第一次加载图片的时候 就算你的图片已经下载好了,但是依旧能看到有一个loading的过程,稍微录个屏 就能明显看到activity 在切换过程中的 图片加载过程
但是你如果退出页面再进入一次详情页,你就会发现这个图片加载的过程没了
其实这两次的区别很好理解,第二次进入详情页的时候,Glide 是直接从内存中取的目标Bitmap。 所以很快
截取两张图片看一下
我们第一次进入详情页的时候
能够明显看出来 文字渲染出来的时候,图片还没渲染出来 注意图片其实根本没有下载的过程,因为图片本身已经在列表页下载好了, 但是没有在内存中而已。
当我们退出详情页,第二次再进入该页面时
明显能看到 文字渲染出来的时候 图片也是直接展示的,几乎没有任何loading的时间
何时才能命中
前面分析过 加载差异的原因,那么对于glide来说,我们到底要通过哪个地方的函数调用才能知道 我的内存缓存到底命中了没有?
Engine类的load方法中 有一个 loadFromMemory的方法调用
简单来说,这个loadFromMemory 如果返回null 则意味着 你的内存缓存没有命中,
这个方法内部实现我就不说了,有兴趣的自己去看下就好,其实也没啥特殊的,无非就是 Glide的内存缓存做了两层的分级
preload的正确用法 到底是什么?
我们大概搞清楚了 预加载图片的逻辑以后,可以自己尝试写一下了,经过一番搜索后得出的代码如下:
写法1
scss
Glide.with(this)
.load(url)
.preload()
这个方法 肯定是不行的,看上去preload了,但是内存缓存并没有命中
再搜索一下
写法2
kotlin
Glide.with(this)
.load(url)
.preload(dpToPx(this,200f), dpToPx(this,200f))
这个看上去有点靠谱了,大家都知道glide的内存缓存 其实缓存的是 decode之后的图片,也就是说 你原图是200200,但是你展示区域是100100 那么最终缓存的是100*100的图片
我们这个写法 preload写死了 宽高,那总能命中了吧。。。
试了一下,我擦 还是不行
再接着换各种关键词搜索,答案五花八门,还有的说什么要设置DiskStrag之类的,越来越离谱,
话说你们这些写blog的人自己都不试一下 自己的代码真的能生效吗?
为啥内存缓存不命中?
其实最关键的就是
这个key 其实在后续的流程中 主要是作为 map的key使用, 刷过面试八股文的同学 还记得map的 get方法实现吗?
我们看下这个key的equals的方法实现
这个model 你就理解成图片url 就好,其他的宽高也都好理解 注意了这个宽高得是imagview的宽高
除此之外 最关键的就是
看到这2个 应该能反应出来 大概是啥东西了吧
glide的apply方法 传递的就是这些关键影响因子
那是不是我们设置了apply 就一定没问题?
scss
RequestOptions()
.placeholder(placeHolderPic)
.error(placeHolderPic)
比如说随便设置一下上面的options, 这样的key能一样吗?
其实还是不行 这里主要是因为preload 没有涉及到具体的imageview,但是你真正加载图片的时候 一定是会有into(imageview)的过程的,这个过程中 glide会给你默认加一下 transformations (如果你没有设置过transformation的话)
这就会导致 你preload的 key和你实际加载的key 还是无法一致 最终导致缓存不一致
改成下面的写法 就可以完美命中了
scss
val options2 = RequestOptions()
.transform(CenterCrop(), RoundedCorners(20))
总结下预加载并且内存命中的几个必要因素
- preload必须指定宽高,宽高要和最终imageview的宽高保持一致,单位是px
- 必须applay options
- RequestOptions 必须指定 transform
如何衡量收益?
虽然肉眼看上去,体验有一些收益,但是老板如果问你技术指标的具体收益呢,总不能拍脑门吧 这里其实可以考虑用 内存缓存命中率这个指标 但好像 glide 并未对外暴露这个接口?
看Engine这个类的实现, 这个类虽然是public的,而且可以通过setEngie这个方法来替换glide 的engine具体实现
但其实关键的东西 都无法重写,比如这个loadFromMemeory这个方法 他是private的 我extends Engine以后 并没有办法调用或者重写这个方法
有人说 哪你可以重写load方法,这个方法是公开的啊
但是这个EngineKey 不是public的 我还得自己custom一个key 然后替换掉原来的key
感觉有点套娃了,这样写负担太大,如果一定要这样做的话,我建议还是直接基于glide 的源码自己直接修改好了,
这样灵活定制效果会好很多。