朋友圈中的图片缓存系统
作者每天都要刷几遍朋友圈,获取最新资讯的同时,也顺便看看周围同事都在哪些国家玩耍,哪些同事又后半夜下班打不到车,最近周围同事的孩子都成长得怎么样。人生百态,每天在朋友圈里悉数上演。本节,作者主要分析朋友圈的图片缓存系统,并介绍一个缓存系统的设计要素。
缓存的概念是什么呢?缓是“临时”的意思,存是“存储”的意思,所以缓存的概念就是“临时存储”。
了解了缓存的概念,再一起回顾使用朋友圈的过程中的一些现象:
(1)刷了几页之后回到顶部,会发现看过的图片依旧在显示,并没有出现先显示占位符,再显示图片内容的情况,这表明图片一直在内存中,随时可被直接展示。
(2)点击一条新内容的图片时,会展示一张缩略图,然后出现loading图标旋转的动画,过一会儿一张清晰大图展现出来,表明这张图片是刚刚从网络拉取的。
(3)当用户翻到几天前的内容时,再次点击图片,有时还需要从网络拉取,有时却瞬间打开,这是为什么呢?因为有可能高清图片已经被缓存系统删除,所以需要从网络重新拉取,也有可能高清图片还在缓存系统中,可以被快速加载,从而“瞬间打开”。
这几种情况基本涵盖了一个 APP 所使用缓存系统的所有场景,只不过这个举例以图片为主,其他资源的缓存系统原理类似。缓存系统通常分两级,称为一级缓存和二级缓存。一级缓存也叫内存缓存,二级缓存也叫磁盘缓存(在硬盘或者SD卡上的缓存)。显然,一级缓存存取速度最快,会多占一些内存,这是非常合理的一种以空间换取时间的程序设计,数据随着程序退出而消失。二级缓存容量更大,存取速度要慢一些,程序下次启动时,依然可以使用缓存内容。现在来模拟整个朋友圈的图片缓存流程。进入朋友圈,图片占用的内存空间不断增加,如果用户往回滑动,会发现刚才的图片都还在,因为这时一级缓存还没满,所有被缓存的图片都能正常满足业务需求。
如果我们持续刷新朋友圈的内容,直至一级缓存的空间被完全占用,就必然要对缓存的图片进行淘汰,目前业界主要采用LRU(Least Recently Used)算法进行淘汰,也就是近期最少被使用的图片被淘汰。按照这种规则,第一张图片会被淘汰出一级缓存,继而被安放到二级缓存,即存储到硬盘上。注意,这里的“淘汰”,也仅仅是将图片从一级缓存迁移到二级缓存,并没有完全丢弃。假设用户滑动页面回到第一张图片所在的位置,这时内存没有这张图片,技术上我们称为没有命中一级缓存。一级缓存像一个老好人,会继续询问二级缓存:“第一张图片在你那里吗?”二级缓存就回答:“是的,在我这里。”这时,一级缓存又按照刚才的算法,淘汰其他最近很少被使用的图片,以保证第一张图片能够被重新加载到一级缓存,紧接着,APP 会从一级缓存中获得这张图片并显示。
如果继续刷新下去,久而久之,硬盘的空间也有可能被撑满,所以二级缓存也会进行淘汰工作,但由于它是最下面的一层,所以只被动地接收一级缓存塞入的图片,同时进行自身内容的淘汰。
把这套机制扩展到一个新闻客户端乃至任何一个应用程序,原理都是一样的,只不过对参数的配置略有不同,比如有的应用想提供更好的看图体验,把一级缓存设计得比较大,使用户能够同时浏览更多的图片,不用经常换入换出,但同时也耗用更多内存,给程序的稳定性带来挑战。二级缓存也可以设计得比较大,这样就可以不用再从网络上拉取那些经常被使用的场景图片,但是如果二级缓存过大,系统的清理软件就会提示该应用程序占用了大量的磁盘空间,需要删除部分内容,这样反而得不偿失。在计算机程序中时刻充满博弈,想占用更多的资源,就会面临更多的风险,永远要衡量用户体验和程序性能,虽然二者存在矛盾,但一定存在一个最适合我们所做业务的方案。
再举个例子,在微信的通讯录页面中,当快速滑动列表时,很多头像都是默认的灰头像,为什么呢?因为滑动时再去读相应的头像,并且对图片解码,会使整个列表的滑动掉帧卡顿。一些新闻客户端的做法又不同,例如滑动的过程中,图片就一张接着一张出来,明显感觉是有卡顿的,但是慢慢滑动会对这种卡顿产生缓解。通讯录放弃了对真实图片的快速显示,进而保证了列表快速滑动的流畅性。新闻客户端为了保证内容的快速加载而舍弃了快速滑动的性能。两个场景的策略之所以不同,取决于用户的使用习惯:通讯录需要快速滑动检索联系人,而新闻客户端的主场景是用户慢慢滑动,阅读一条一条的新闻内容。