Fork me on GitHub

Android Glide 三种池子

问题

1
2
3
4
5
6
7
Glide 中 LruPoolStrategy 是如何设计的

三个子类 SizeConfigStrategy AttributeStrategy SizeStrategy 的区别

从精细度管理的角度,对三种池子进行排序

如果图片都使用 `ARGB_8888` 应该使用哪个池子

LruPoolStrategy 接口设计

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
/**
* 定义了在LRU(最近最少使用)缓存中管理可重用位图池的策略接口。
* 该接口的实现负责定义位图的存储、检索和根据缓存策略及位图的属性(如大小和配置)的逐出机制。
*/
internal interface LruPoolStrategy {

/**
* 将位图放入池中。实现应根据其大小、配置或其他属性决定如何将位图添加到池中。
*
* @param bitmap 要添加到池中的位图。
*/
fun put(bitmap: Bitmap)

/**
* 尝试从池中检索并返回与指定宽度、高度和配置匹配的位图。如果没有找到合适的位图,此方法返回null。
*
* @param width 请求的位图宽度。
* @param height 请求的位图高度。
* @param config 所需位图的Bitmap.Config配置。可以为null。
* @return 匹配请求属性的位图,如果没有合适的位图可用则返回null。
*/
operator fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap?

/**
* 从池中移除并返回最近最少使用的位图。如果池为空,此方法返回null。
*
* @return 最近最少使用的位图,如果池为空则返回null。
*/
fun removeLast(): Bitmap?

/**
* 生成并返回指定位图属性的日志友好型字符串表示,如其大小和配置。
*
* @param bitmap 要为其生成日志字符串的位图。
* @return 位图属性的字符串表示,用于日志记录目的。
*/
fun logBitmap(bitmap: Bitmap): String?

/**
* 根据指定的宽度、高度和配置生成并返回一个位图属性的日志友好型字符串表示。
*
* @param width 位图的宽度。
* @param height 位图的高度。
* @param config 位图的Bitmap.Config配置。可以为null。
* @return 指定属性的字符串表示,用于日志记录目的。
*/
fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String?

/**
* 返回指定位图的大小(以字节为单位)。实现应根据位图的尺寸和配置计算大小。
*
* @param bitmap 要计算大小的位图。
* @return 位图的字节大小。
*/
fun getSize(bitmap: Bitmap): Int
}

SizeConfigStrategy

SizeConfigStrategy 是一个实现了 LruPoolStrategy 接口的类,用于管理和重用 Bitmap 对象,其核心目标是通过控制 Bitmap 对象的尺寸和配置来优化内存使用。

这个策略通过将 Bitmap 对象分类存储在一个组织良好的结构中,并在需要时提供快速访问,以减少内存分配和回收的开销。下面是对这个类的关键部分的详细解析:

核心数据结构和方法

  • ARGB_8888_IN_CONFIGS

  • RGA_F16_IN_CONFIGS

  • RGB_565_IN_CONFIGS

  • ARGB_4444_IN_CONFIGS

  • ALPHA_8_IN_CONFIGS
    这些数组定义了在不同 Android 版本下,根据请求的 Bitmap.Config 所能接受的配置类型。例如,如果请求的是 ARGB_8888,那么可能接受的配置就包括了 ARGB_8888RGBA_F16(在支持的 Android 版本上)。

  • keyPool:
    KeyPool 实例,用于重用 Key 对象。每个 Key 对象代表一个 Bitmap 的尺寸和配置,这样可以减少内存分配。

  • groupedMap:
    GroupedLinkedMap<Key?, Bitmap> 实例,用于根据 Key 存储和检索 Bitmap 对象。这种结构支持快速查找、插入和删除操作。

  • sortedSizes:
    NavigableMap<Int, Int> 的映射,用于跟踪每种配置下不同大小的 Bitmap 数量。这对于找到最匹配的 Bitmap 尺寸非常有用。

方法

  • put(bitmap: Bitmap):
    Bitmap 添加到池中。这个方法计算 Bitmap 的字节大小,创建或获取一个对应的 Key,并更新 groupedMapsortedSizes

  • get(width: Int, height: Int, config: Bitmap.Config?):
    尝试根据提供的宽度、高度和配置从池中获取一个最匹配的 Bitmap。这涉及到查找一个尺寸合适、配置兼容的 Bitmap,如果找到,就对其进行重新配置并返回。

  • removeLast():
    移除并返回池中最后一个 Bitmap,这通常是最近最少使用的一个。这个方法还会更新 sortedSizes 以反映 变化。

  • logBitmap(bitmap: Bitmap)logBitmap(width: Int, height: Int, config: Bitmap.Config?):
    生成表示 Bitmap 尺寸和配置的字符串,用于日志记录和调试。

内部类

  • KeyPoolKey:
    这些类支持 Bitmap 尺寸和配置的高效存储和检索。KeyPool 用于管理 Key 对象的池,以减少创建新对象的需要。Key 对象表示一个 Bitmap 的尺寸和配置,用作 groupedMap 中的键。

整体设计思路

SizeConfigStrategy 的设计旨在通过细致管理 Bitmap 对象的存储和重用来优化内存使用。它通过精确匹配请求的 Bitmap 尺寸和配置,尽量减少创建新 Bitmap 对象的需要,从而降低了内存压力和提高了性能。这个策略特别适用于图片密集型的应用,比如图片浏览器或社交媒体应用,其中频繁地加载和显示图片。

AttributeStrategy

这段代码定义了一个名为 AttributeStrategy 的内部类,实现了 LruPoolStrategy 接口,用于管理位图(Bitmap)的缓存策略。这个策略通过位图的宽度、高度和配置(Bitmap.Config)来唯一标识和管理位图。下面是对这个类的主要组成部分和逻辑的解析:

类的主要组成部分

  1. KeyPool 类:一个用于管理 Key 对象池的内部类。它通过重写 create() 方法来创建新的 Key 对象,并提供了一个获取 Key 的方法,该方法接受位图的宽度、高度和配置作为参数,用于初始化 Key

  2. Key 类:一个内部类,实现了 Poolable 接口。每个 Key 对象包含位图的宽度、高度和配置属性。Key 类提供了 init 方法来设置这些属性,equalshashCode 方法被重写以确保 Key 对象可以根据其宽度、高度和配置被唯一地标识和比较。

  3. groupedMap:一个 GroupedLinkedMap 对象,用于根据 Key(位图的宽度、高度和配置)分组存储和管理位图。

类的主要方法

  • put(bitmap: Bitmap):将位图添加到缓存中。首先通过 keyPool 获取与位图尺寸和配置对应的 Key,然后将位图和 Key 添加到 groupedMap 中。

  • get(width: Int, height: Int, config: Bitmap.Config?):尝试获取一个符合指定尺寸和配置的位图。首先通过 keyPool 获取与指定尺寸和配置对应的 Key,然后从 groupedMap 中查找和返回相应的位图。

  • removeLast():移除并返回最近最少使用的位图。这是通过从 groupedMap 中移除最后一个位图来实现的。

  • logBitmap(bitmap: Bitmap)logBitmap(width: Int, height: Int, config: Bitmap.Config?):用于生成表示位图尺寸和配置的日志字符串。

  • getSize(bitmap: Bitmap):返回位图占用的字节大小。

特点和用途

AttributeStrategy 通过精确地考虑位图的尺寸和配置来管理位图缓存,使其能够更有效地利用内存并提高缓存的效率。通过使用对象池来管理 Key 对象,还可以减少内存分配和垃圾回收的压力。这种策略特别适用于需要存储和管理多种尺寸和配置位图的应用场景。

SizeStrategy

这段代码是一个用于管理位图(Bitmap)缓存策略的内部类 SizeStrategy,它实现了 LruPoolStrategy 接口。这个策略的核心是通过位图大小来管理和回收位图资源,以优化内存使用。下面是对这个类和它的主要组成部分的分析:

成员变量介绍

  • keyPool: 一个 KeyPool 对象,用于管理 Key 对象的池。每个 Key 对象都与一个特定大小的位图相关联。

  • groupedMap: 一个 GroupedLinkedMap<Key, Bitmap> 对象,用于根据 Key 分组存储 Bitmap 对象。这允许快速查找和回收特定大小的位图。

  • sortedSizes: 一个 NavigableMap<Int?, Int> 对象,存储每个大小的位图数量。这是一个 PrettyPrintTreeMap,可能是为了便于调试和打印。

    方法解析

  • put(bitmap: Bitmap): 将一个位图添加到缓存中。它计算位图的大小,获取或创建相应大小的 Key,将位图和 Key 添加到 groupedMap 中,并更新 sortedSizes 中对应大小的计数。

  • get(width: Int, height: Int, config: Bitmap.Config?): 尝试获取一个符合指定宽度、高度和配置的位图。它计算所需位图的大小,查找是否有足够大的可用位图,如果有,则从 groupedMap 中取出并返回该位图。

  • removeLast(): 移除并返回最近最少使用(LRU)的位图。这是通过从 groupedMap 中移除最后一个位图来实现的,并更新 sortedSizes 中的计数。

  • decrementBitmapOfSize(size: Int?): 减少特定大小的位图数量。如果该大小的位图只有一个,则从 sortedSizes 中移除该大小;否则,减少其计数。

    辅助类

  • KeyPool: 用于管理 Key 对象池的类。它重写了 create() 方法来生成新的 Key 对象,并提供了一个重载的 get(size: Int) 方法来获取或创建一个初始化了特定大小的 Key

  • Key: 实现了 Poolable 接口的类,表示与位图大小相关联的键。包含一个 size 属性和 init(size: Int) 方法来设置键的大小。重写了 equals()hashCode() 方法以支持正确的键比较和哈希操作。

常量和辅助方法

  • MAX_SIZE_MULTIPLE: 一个常量,定义了在查找时可以接受的最大位图大小倍数。
  • getBitmapString(bitmap: Bitmap)getBitmapString(size: Int): 辅助方法,用于生成表示位图大小的字符串。

这个类的设计目的是提高位图缓存的效率和灵活性,通过精细地管理不同大小的位图来优化内存使用。通过维护一个有序的大小映射和一个根据大小分组的位图映射,它可以快速地存取和回收位图资源。

SizeConfigStrategyAttributeStrategy、和SizeStrategy是Glide图像加载库用于位图缓存管理的三种不同策略,它们在位图的存储、查找和回收方式上各有特点。这些策略优化了内存使用,并改善了图像加载的性能。以下是它们的区别和各自适用的场景:

适用场景

SizeConfigStrategy

特点SizeConfigStrategy使用位图的大小(以字节为单位)和Bitmap.Config配置作为键来管理缓存。这种方法允许区分具有相同像素大小但不同像素配置的位图,如ARGB_8888RGB_565

适用场景:这种策略适用于需要根据位图的内存大小和配置精细管理缓存的应用。例如,如果应用中同时使用了不同配置的位图(以优化显示质量和内存使用),SizeConfigStrategy能有效地区分和管理这些位图。

AttributeStrategy

特点AttributeStrategy基于位图的宽度、高度和配置(Bitmap.Config)来识别和管理位图。这种方法提供了对缓存的精确控制,允许缓存系统区分尺寸相同但配置不同的位图。

适用场景:当应用需要在相同的尺寸下缓存不同配置的位图,且这些配置对位图的使用和性能有明显影响时,AttributeStrategy非常适用。它确保了即使是细微的配置差异也能被正确管理,适合对图像质量和性能有高要求的应用。

SizeStrategy

特点SizeStrategy仅基于位图占用的内存大小来管理缓存,不考虑位图的配置或尺寸。这种策略通过一种更简单的方式来回收和重用位图内存,忽略了位图的其他属性。

适用场景:对于那些不需要考虑位图配置差异,主要关注于减少内存占用和简化缓存管理的应用,SizeStrategy是一个理想的选择。它适合内存使用更为紧张,或者位图配置较为统一的场景。

总结

SizeConfigStrategyAttributeStrategy 提供了更细粒度的缓存管理,能够根据位图的具体特征(如配置和尺寸)进行优化,适合需要高度优化内存使用和图像质量的场景。

SizeStrategy 通过一个更简单的方法来管理缓存,适用于对缓存管理的要求相对简单,更关注于减少内存占用的应用。

选择哪种策略取决于应用的具体需求,包括对内存管理的敏感度、图像的多样性以及性能的要求。

三种池子的特点

SizeConfigStrategy

  • 内存大小:使用位图的内存大小作为缓存的关键因素之一。
  • 配置敏感:考虑了Bitmap.Config,区分了相同大小但配置不同的位图。
  • 精细管理:允许对缓存的位图进行更精细的管理,适用于内存和显示质量都很重要的场景。

AttributeStrategy

  • 尺寸配置:基于位图的宽度、高度和Bitmap.Config来管理位图。
  • 高度区分:能够精确区分尺寸相同但配置不同的位图。
  • 细节控制:提供对位图缓存的细节控制,适用于对图像显示细节有高要求的应用。

SizeStrategy

  • 简化内存:仅基于位图占用的内存大小来管理缓存,简化了缓存管理。
  • 统一处理:不区分位图的尺寸或配置,统一处理所有位图。
  • 内存优化:优先考虑内存使用效率,适用于内存敏感且配置统一的应用场景。

这些关键字概括了每种策略的核心特点和适用场景,有助于在实际开发中根据应用的需求选择最合适的位图缓存管理策略。

附件

SizeConfigStrategy

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
class SizeConfigStrategy : LruPoolStrategy {
companion object {
private const val MAX_SIZE_MULTIPLE = 8
private val ARGB_8888_IN_CONFIGS: Array<Bitmap.Config?>

init {
var result = arrayOf(
Bitmap.Config.ARGB_8888, // The value returned by Bitmaps with the hidden Bitmap config.
null
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
result = result.copyOf(result.size + 1)
result[result.size - 1] = Bitmap.Config.RGBA_F16
}
ARGB_8888_IN_CONFIGS = result
}

private val RGBA_F16_IN_CONFIGS = ARGB_8888_IN_CONFIGS
private val RGB_565_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.RGB_565)
private val ARGB_4444_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.ARGB_4444)
private val ALPHA_8_IN_CONFIGS = arrayOf<Bitmap.Config?>(Bitmap.Config.ALPHA_8)
fun getBitmapString(size: Int, config: Bitmap.Config?): String {
return "[$size]($config)"
}

private fun getInConfigs(requested: Bitmap.Config?): Array<Bitmap.Config?> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Bitmap.Config.RGBA_F16 == requested) { // NOPMD - Avoid short circuiting sdk checks.
return RGBA_F16_IN_CONFIGS
}
}
return when (requested) {
Bitmap.Config.ARGB_8888 -> ARGB_8888_IN_CONFIGS
Bitmap.Config.RGB_565 -> RGB_565_IN_CONFIGS
Bitmap.Config.ARGB_4444 -> ARGB_4444_IN_CONFIGS
Bitmap.Config.ALPHA_8 -> ALPHA_8_IN_CONFIGS
else -> arrayOf(requested)
}
}
}

private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key?, Bitmap>()
private val sortedSizes: MutableMap<Bitmap.Config?, NavigableMap<Int, Int>> = EnumMap(Bitmap.Config::class.java)
override fun put(bitmap: Bitmap) {
val size = getBitmapByteSize(bitmap)
val key = keyPool[size, bitmap.config]
groupedMap.put(key, bitmap)
val sizes = getSizesForConfig(bitmap.config)
val current = sizes[key!!.size]
sizes[key.size] = if (current == null) 1 else current + 1
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val size = getBitmapByteSize(width, height, config)
val bestKey = findBestKey(size, config)
val result = groupedMap[bestKey]
if (result != null) {
decrementBitmapOfSize(bestKey!!.size, result)
result.reconfigure(width, height, config)
}
return result
}

private fun findBestKey(size: Int, config: Bitmap.Config?): Key? {
var result = keyPool[size, config]
for (possibleConfig in getInConfigs(config)) {
val sizesForPossibleConfig = getSizesForConfig(possibleConfig)
val possibleSize = sizesForPossibleConfig.ceilingKey(size)
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
if (possibleSize != size
|| if (possibleConfig == null) config != null else possibleConfig != config
) {
keyPool.offer(result)
result = keyPool[possibleSize, possibleConfig]
}
break
}
}
return result
}

override fun removeLast(): Bitmap? {
val removed = groupedMap.removeLast()
if (removed != null) {
val removedSize = getBitmapByteSize(removed)
decrementBitmapOfSize(removedSize, removed)
}
return removed
}

private fun decrementBitmapOfSize(size: Int, removed: Bitmap) {
val config = removed.config
val sizes = getSizesForConfig(config)
val current = sizes[size]?: throw NullPointerException(
"Tried to decrement empty size"
+ ", size: "
+ size
+ ", removed: "
+ logBitmap(removed)
+ ", this: "
+ this
)
if (current == 1) {
sizes.remove(size)
} else {
sizes[size] = current - 1
}
}

private fun getSizesForConfig(config: Bitmap.Config?): NavigableMap<Int, Int> {
var sizes = sortedSizes[config]
if (sizes == null) {
sizes = TreeMap()
sortedSizes[config] = sizes
}
return sizes
}

override fun logBitmap(bitmap: Bitmap): String {
val size = getBitmapByteSize(bitmap)
return getBitmapString(size, bitmap.config)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
val size = getBitmapByteSize(width, height, config)
return getBitmapString(size, config)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
val sb = StringBuilder()
.append("SizeConfigStrategy{groupedMap=")
.append(groupedMap)
.append(", sortedSizes=(")
for ((key, value) in sortedSizes) {
sb.append(key).append('[').append(value).append("], ")
}
if (sortedSizes.isNotEmpty()) {
sb.replace(sb.length - 2, sb.length, "")
}
return sb.append(")}").toString()
}

@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(size: Int, config: Bitmap.Config?): Key? {
val result = get()
result!!.init(size, config)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
var size = 0
private var config: Bitmap.Config? = null

@VisibleForTesting
constructor(pool: KeyPool, size: Int, config: Bitmap.Config?) : this(pool) {
init(size, config)
}

fun init(size: Int, config: Bitmap.Config?) {
this.size = size
this.config = config
}

override fun offer() {
pool.offer(this)
}

override fun toString(): String {
return getBitmapString(size, config)
}

override fun equals(other: Any?): Boolean {
if (other is Key) {
return if (size == other.size && config == null) other.config == null else config == other.config
}
return false
}

override fun hashCode(): Int {
var result = size
result = 31 * result + if (config != null) config.hashCode() else 0
return result
}
}
}

AttributeStategy

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.max.hbbitmappool.pool.impl

import android.graphics.Bitmap
import androidx.annotation.VisibleForTesting
import com.max.hbbitmappool.utils.getBitmapByteSize

internal class AttributeStrategy : LruPoolStrategy {
private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key?, Bitmap>()
override fun put(bitmap: Bitmap) {
val key = keyPool[bitmap.width, bitmap.height, bitmap.config]
groupedMap.put(key, bitmap)
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val key = keyPool[width, height, config]
return groupedMap[key]
}

override fun removeLast(): Bitmap? {
return groupedMap.removeLast()
}

override fun logBitmap(bitmap: Bitmap): String {
return getBitmapString(bitmap)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
return getBitmapString(width, height, config)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
return "AttributeStrategy:\n $groupedMap"
}

@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(width: Int, height: Int, config: Bitmap.Config?): Key? {
val result = get()
result?.init(width, height, config)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
private var width = 0
private var height = 0
private var config: Bitmap.Config? = null
fun init(width: Int, height: Int, config: Bitmap.Config?) {
this.width = width
this.height = height
this.config = config
}

override fun equals(other: Any?): Boolean {
if (other is Key) {
return width == other.width && height == other.height && config == other.config
}
return false
}

override fun hashCode(): Int {
var result = width
result = 31 * result + height
result = 31 * result + if (config != null) config.hashCode() else 0
return result
}

override fun toString(): String {
return getBitmapString(width, height, config)
}

override fun offer() {
pool.offer(this)
}
}

companion object {
private fun getBitmapString(bitmap: Bitmap): String {
return getBitmapString(bitmap.width, bitmap.height, bitmap.config)
}

fun getBitmapString(width: Int, height: Int, config: Bitmap.Config?): String {
return "[" + width + "x" + height + "], " + config
}
}
}

SizeStrategy

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
internal class SizeStrategy : LruPoolStrategy {
private val keyPool = KeyPool()
private val groupedMap = GroupedLinkedMap<Key, Bitmap>()
private val sortedSizes: NavigableMap<Int?, Int> = PrettyPrintTreeMap()
override fun put(bitmap: Bitmap) {
val size = getBitmapByteSize(bitmap)
val key = keyPool[size]
groupedMap.put(key, bitmap)
val current = sortedSizes[key.size]
sortedSizes[key.size] = if (current == null) 1 else current + 1
}

override fun get(width: Int, height: Int, config: Bitmap.Config?): Bitmap? {
val size = getBitmapByteSize(width, height, config)
var key = keyPool[size]
val possibleSize = sortedSizes.ceilingKey(size)
if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) {
keyPool.offer(key)
key = keyPool[possibleSize]
}

val result = groupedMap[key]
if (result != null) {
result.reconfigure(width, height, config)
decrementBitmapOfSize(possibleSize)
}
return result
}

override fun removeLast(): Bitmap? {
val removed = groupedMap.removeLast()
if (removed != null) {
val removedSize = getBitmapByteSize(removed)
decrementBitmapOfSize(removedSize)
}
return removed
}

private fun decrementBitmapOfSize(size: Int?) {
val current = sortedSizes[size]
if (current == 1) {
sortedSizes.remove(size)
} else {
sortedSizes[size] = current!! - 1
}
}

override fun logBitmap(bitmap: Bitmap): String {
return getBitmapString(bitmap)
}

override fun logBitmap(width: Int, height: Int, config: Bitmap.Config?): String {
val size = getBitmapByteSize(width, height, config)
return getBitmapString(size)
}

override fun getSize(bitmap: Bitmap): Int {
return getBitmapByteSize(bitmap)
}

override fun toString(): String {
return "SizeStrategy:\n $groupedMap\n SortedSizes$sortedSizes"
}

// Non-final for mocking.
@VisibleForTesting
internal class KeyPool : BaseKeyPool<Key?>() {
operator fun get(size: Int): Key {
val result = super.get()!!
result.init(size)
return result
}

override fun create(): Key {
return Key(this)
}
}

@VisibleForTesting
internal class Key(private val pool: KeyPool) : Poolable {
var size = 0
fun init(size: Int) {
this.size = size
}

override fun equals(o: Any?): Boolean {
if (o is Key) {
return size == o.size
}
return false
}

override fun hashCode(): Int {
return size
}

// PMD.AccessorMethodGeneration: https://github.com/pmd/pmd/issues/807
override fun toString(): String {
return getBitmapString(size)
}

override fun offer() {
pool.offer(this)
}
}

companion object {
private const val MAX_SIZE_MULTIPLE = 8
private fun getBitmapString(bitmap: Bitmap): String {
val size = getBitmapByteSize(bitmap)
return getBitmapString(size)
}

fun getBitmapString(size: Int): String {
return "[$size]"
}
}
}

三种池子精细化管理排序

  1. AttributeStrategy
  2. SizeConfigStrategy
  3. SizeStrategy

1. AttributeStrategy

  • 精细化等级:最高
  • 原因AttributeStrategy基于位图的宽度、高度和Bitmap.Config配置来管理位图,提供了最细致的控制。这允许它区分具有相同像素数量但不同尺寸或配置的位图,实现了对位图缓存的高度精细化管理。

2. SizeConfigStrategy

  • 精细化等级:中等
  • 原因SizeConfigStrategy结合了位图的内存大小和配置(如ARGB_8888RGB_565等)来管理位图。虽然它不如AttributeStrategy能够精确到位图的具体尺寸,但通过考虑配置信息,它在位图的管理上提供了比仅基于大小更精细的控制。

3. SizeStrategy

  • 精细化等级:最低
  • 原因SizeStrategy仅基于位图占用的内存大小来管理位图,完全忽略了位图的尺寸和配置信息。这种策略提供了最简单的管理方式,适合于那些内存使用效率是主要关注点、对位图的具体属性(如尺寸和配置)关注较少的场景。

总结来说,如果需要对缓存中的位图进行非常精细化的管理,优先选择AttributeStrategy
如果希望在精细化管理和简化逻辑之间取得平衡,SizeConfigStrategy是一个好的选择;
而如果主要关注简化缓存管理和优化内存使用,SizeStrategy将是最合适的策略。

,