客户端支持带过期时间的缓存

背景


复杂列表下发时,考虑到接口性能,服务端并不会将全部数据封装返回,客户端拿到列表后按需请求加载,当用户快速滑入滑出列表时,伴有大量重复请求发出,图片频繁闪烁,体验很不友好。

另外这些数据可能具有时效性,比如商品价格可能随着活动开启或结束动态调整。



灵感来源


后端开发工程师常会用到Memcached缓存,这是一种基于内存的key-value存储,用来存储小块的任意数据,支持设置缓存有效时间。

基于以上业务场景和对Memcached的理解,在客户端实现类似缓存会很有价值。



实现方式


提及客户端缓存用的最多的是LruCache,先来了解一下,下面注释片段摘自 Android sdk LruCache 源码:

1
2
3
4
5
6
7
8
9
10
/**
- A cache that holds strong references to a limited number of values. Each time
- a value is accessed, it is moved to the head of a queue. When a value is
- added to a full cache, the value at the end of that queue is evicted and may
- become eligible for garbage collection.

......

- This class appeared in Android 3.1 (Honeycomb MR1)
*/

LruCache是从 Android 3.1 开始提供的缓存类,用来实现内存缓存很合适;
LRU采用近期最少使用算法,当缓存满时, 优先淘汰最近最少使用的缓存对象。 具体实现可查看开发者官网-LRU



缓存数据结构


1
2
3
4
5
6
7
8
9
10
11
12
13
import java.lang.ref.SoftReference

/**
* 缓存 item
*
* 使用软引用持有缓存,防止内存紧张时无法回收
* 添加 aliveTime 标记缓存到期时间
*
* @author littlegrow
*/
class CacheItem<K, V>(val key: K, value: V, val aliveTime: Long) {
val valueRef: SoftReference<V> = SoftReference(value)
}



缓存实现


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import android.util.LruCache

/**
* 使用LruCache实现支持过期时间的缓存
*
* @author littlegrow
*/

class MemCache<K, V>(maxSize: Int) {
companion object {
// 默认有效时间5分钟
private const val DEFAULT_DURING = (5 * 60 * 1000).toLong()
}

private val lruCache: LruCache<K, CacheItem<K, V>> = LruCache(maxSize)

/**
* 根据key获取缓存
*
* @param key key
* @return 缓存不存在或者失效返回 null
*/
fun get(key: K): V? {
val cacheItem = lruCache.get(key) ?: return null
return if (isCacheItemAlive(cacheItem)) {
// 缓存内容被回收则移除缓存
if (cacheItem.valueRef.get() == null) {
remove(key)
}
cacheItem.valueRef.get()
} else {
lruCache.remove(key)
null
}
}

/**
* 设置缓存,支持自定义缓存存活时间
*
* @param key key
* @param value value
* @param aliveTime 存活时间
* @return 如果缓存存在更新缓存时间,并返回缓存值,否则返回 null
*/
@JvmOverloads
fun put(key: K, value: V, aliveTime: Long = DEFAULT_DURING): V? {
if (aliveTime < 0) {
throw IllegalArgumentException("aliveTime should >= 0")
}
val time = System.currentTimeMillis()
val cacheItem = CacheItem(key, value, time + aliveTime)
val previous = lruCache.put(key, cacheItem)
return previous?.valueRef?.get()
}

/**
* 移除缓存
*
* @param key key
* @return 如果有缓存则返回缓存内容,否则返回 null
*/
fun remove(key: K): V? {
return lruCache.remove(key)?.valueRef?.get()
}

/**
* 判断缓存是否存活
*/
private fun isCacheItemAlive(cacheItem: CacheItem<K, V>): Boolean {
return System.currentTimeMillis() <= cacheItem.aliveTime
}
}



效果


应用此缓存后,列表滑动过程中减少了大量的无效请求,杜绝了图片加载闪烁的问题。

如果您有类似业务场景,可考虑采用。






文章目录
  1. 1. 背景
  2. 2. 灵感来源
  3. 3. 实现方式
    1. 3.1. 缓存数据结构
    2. 3.2. 缓存实现
  4. 4. 效果