Glide源码阅读之策略模式3【LruPoolStrategy】
前两篇是类内部实现,LruPoolStrategy的实现不同于前面两篇,它是继承实现的,先看看LruPoolStrategy的源码
LruPoolStrategy
包路径:com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy
interface LruPoolStrategy {
void put(Bitmap bitmap);
@Nullable
Bitmap get(int width, int height, Bitmap.Config config);
@Nullable
Bitmap removeLast();
String logBitmap(Bitmap bitmap);
String logBitmap(int width, int height, Bitmap.Config config);
int getSize(Bitmap bitmap);
}
这个接口没有注释,挺少见的。查阅了公开文档也没有找到这个接口的说明,
看接口定义的方法也好理解,这个没有使用public来修饰,也就不对外开发了。接着看看实现类有哪些?
实现类列表
- 单元测试应用
- MockStrategy
- AttributeStrategy
- SizeConfigStrategy
- SizeStrategy
单元测试应用
包路径:com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPoolTest
private MockStrategy strategy;
。。。
@Test
public void testBitmapLargerThanPoolIsNotAdded() {
strategy =
new MockStrategy() {
@Override
public int getSize(Bitmap bitmap) {
return 4;
}
};
pool = new LruBitmapPool(3, strategy, ALLOWED_CONFIGS);
pool.put(createMutableBitmap());
assertEquals(0, strategy.numRemoves);
assertEquals(0, strategy.numPuts);
}
MockStrategy
包路径:com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPoolTest.MockStrategy
private static class MockStrategy implements LruPoolStrategy {
private final ArrayDeque<Bitmap> bitmaps = new ArrayDeque<>();
private int numRemoves;
private int numPuts;
@Override
public void put(Bitmap bitmap) {
numPuts++;
bitmaps.add(bitmap);
}
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
return bitmaps.isEmpty() ? null : bitmaps.removeLast();
}
@Override
public Bitmap removeLast() {
numRemoves++;
return bitmaps.removeLast();
}
@Override
public String logBitmap(Bitmap bitmap) {
return null;
}
@Override
public String logBitmap(int width, int height, Bitmap.Config config) {
return null;
}
@Override
public int getSize(Bitmap bitmap) {
return 1;
}
}
这个是用来进行单元测试的桩实现。具体实现比较简单一些,这个放在前面会好理解一些。
AttributeStrategy
包路径:com.bumptech.glide.load.engine.bitmap_recycle.AttributeStrategy
一种重用位图的策略,需要任何返回的位图的尺寸来精确匹配这些请求
/**
* A strategy for reusing bitmaps that requires any returned bitmap's dimensions to exactly match
* those request.
*/
一种重用位图的策略,需要任何返回的位图的尺寸来精确匹配这些请求
class AttributeStrategy implements LruPoolStrategy {
private final KeyPool keyPool = new KeyPool();
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
@Override
public void put(Bitmap bitmap) {
final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
groupedMap.put(key, bitmap);
}
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
final Key key = keyPool.get(width, height, config);
return groupedMap.get(key);
}
@Override
public Bitmap removeLast() {
return groupedMap.removeLast();
}
@Override
public String logBitmap(Bitmap bitmap) {
return getBitmapString(bitmap);
}
@Override
public String logBitmap(int width, int height, Bitmap.Config config) {
return getBitmapString(width, height, config);
}
@Override
public int getSize(Bitmap bitmap) {
return Util.getBitmapByteSize(bitmap);
}
@Override
public String toString() {
return "AttributeStrategy:\n " + groupedMap;
}
private static String getBitmapString(Bitmap bitmap) {
return getBitmapString(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
}
@SuppressWarnings("WeakerAccess")
@Synthetic
static String getBitmapString(int width, int height, Bitmap.Config config) {
return "[" + width + "x" + height + "], " + config;
}
@VisibleForTesting
static class KeyPool extends BaseKeyPool<Key> {
Key get(int width, int height, Bitmap.Config config) {
Key result = get();
result.init(width, height, config);
return result;
}
@Override
protected Key create() {
return new Key(this);
}
}
@VisibleForTesting
static class Key implements Poolable {
private final KeyPool pool;
private int width;
private int height;
// Config can be null :(
private Bitmap.Config config;
public Key(KeyPool pool) {
this.pool = pool;
}
public void init(int width, int height, Bitmap.Config config) {
this.width = width;
this.height = height;
this.config = config;
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
return width == other.width && height == other.height && config == other.config;
}
return false;
}
@Override
public int hashCode() {
int result = width;
result = 31 * result + height;
result = 31 * result + (config != null ? config.hashCode() : 0);
return result;
}
@Override
public String toString() {
return getBitmapString(width, height, config);
}
@Override
public void offer() {
pool.offer(this);
}
}
单独看这个一个实现没发现什么特别的,接着看看下面的实现类
SizeConfigStrategy
包路径:com.bumptech.glide.load.engine.bitmap_recycle.SizeConfigStrategy
使用Bitmap. getallocationbytecount()和Bitmap键位图。从Bitmap.getConfig()返回的配置。
同时使用配置和字节大小允许我们安全地复用各种各样的位图,这提高了池的命中率,从而提高了应用程序的性能。这个类只允许复用每像素匹配字节数的位图,工作在#301左右。
/**
* Keys {@link android.graphics.Bitmap Bitmaps} using both {@link
* android.graphics.Bitmap#getAllocationByteCount()} and the {@link android.graphics.Bitmap.Config}
* returned from {@link android.graphics.Bitmap#getConfig()}.
*
* <p>Using both the config and the byte size allows us to safely re-use a greater variety of {@link
* android.graphics.Bitmap Bitmaps}, which increases the hit rate of the pool and therefore the
* performance of applications. This class works around #301 by only allowing re-use of {@link
* android.graphics.Bitmap Bitmaps} with a matching number of bytes per pixel.
*/
使用Bitmap. getallocationbytecount()和Bitmap键位图。从Bitmap.getConfig()返回的配置。
同时使用配置和字节大小允许我们安全地复用各种各样的位图,这提高了池的命中率,从而提高了应用程序的性能。这个类只允许复用每像素匹配字节数的位图,工作在#301左右。
@RequiresApi(Build.VERSION_CODES.KITKAT)
public class SizeConfigStrategy implements LruPoolStrategy {
private static final int MAX_SIZE_MULTIPLE = 8;
private static final Bitmap.Config[] ARGB_8888_IN_CONFIGS;
static {
Bitmap.Config[] result =
new Bitmap.Config[] {
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 = Arrays.copyOf(result, result.length + 1);
result[result.length - 1] = Config.RGBA_F16;
}
ARGB_8888_IN_CONFIGS = result;
}
private static final Bitmap.Config[] RGBA_F16_IN_CONFIGS = ARGB_8888_IN_CONFIGS;
// We probably could allow ARGB_4444 and RGB_565 to decode into each other, but ARGB_4444 is
// deprecated and we'd rather be safe.
private static final Bitmap.Config[] RGB_565_IN_CONFIGS =
new Bitmap.Config[] {Bitmap.Config.RGB_565};
private static final Bitmap.Config[] ARGB_4444_IN_CONFIGS =
new Bitmap.Config[] {Bitmap.Config.ARGB_4444};
private static final Bitmap.Config[] ALPHA_8_IN_CONFIGS =
new Bitmap.Config[] {Bitmap.Config.ALPHA_8};
private final KeyPool keyPool = new KeyPool();
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
@Override
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
Key key = keyPool.get(size, bitmap.getConfig());
groupedMap.put(key, bitmap);
NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
Integer current = sizes.get(key.size);
sizes.put(key.size, current == null ? 1 : current + 1);
}
@Override
@Nullable
public Bitmap get(int width, int height, Bitmap.Config config) {
int size = Util.getBitmapByteSize(width, height, config);
Key bestKey = findBestKey(size, config);
Bitmap result = groupedMap.get(bestKey);
if (result != null) {
// Decrement must be called before reconfigure.
decrementBitmapOfSize(bestKey.size, result);
result.reconfigure(width, height, config);
}
return result;
}
private Key findBestKey(int size, Bitmap.Config config) {
Key result = keyPool.get(size, config);
for (Bitmap.Config possibleConfig : getInConfigs(config)) {
NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
if (possibleSize != size
|| (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
keyPool.offer(result);
result = keyPool.get(possibleSize, possibleConfig);
}
break;
}
}
return result;
}
@Override
@Nullable
public Bitmap removeLast() {
Bitmap removed = groupedMap.removeLast();
if (removed != null) {
int removedSize = Util.getBitmapByteSize(removed);
decrementBitmapOfSize(removedSize, removed);
}
return removed;
}
private void decrementBitmapOfSize(Integer size, Bitmap removed) {
Bitmap.Config config = removed.getConfig();
NavigableMap<Integer, Integer> sizes = getSizesForConfig(config);
Integer current = sizes.get(size);
if (current == null) {
throw new NullPointerException(
"Tried to decrement empty size"
+ ", size: "
+ size
+ ", removed: "
+ logBitmap(removed)
+ ", this: "
+ this);
}
if (current == 1) {
sizes.remove(size);
} else {
sizes.put(size, current - 1);
}
}
private NavigableMap<Integer, Integer> getSizesForConfig(Bitmap.Config config) {
NavigableMap<Integer, Integer> sizes = sortedSizes.get(config);
if (sizes == null) {
sizes = new TreeMap<>();
sortedSizes.put(config, sizes);
}
return sizes;
}
@Override
public String logBitmap(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
return getBitmapString(size, bitmap.getConfig());
}
@Override
public String logBitmap(int width, int height, Bitmap.Config config) {
int size = Util.getBitmapByteSize(width, height, config);
return getBitmapString(size, config);
}
@Override
public int getSize(Bitmap bitmap) {
return Util.getBitmapByteSize(bitmap);
}
@Override
public String toString() {
StringBuilder sb =
new StringBuilder()
.append("SizeConfigStrategy{groupedMap=")
.append(groupedMap)
.append(", sortedSizes=(");
for (Map.Entry<Bitmap.Config, NavigableMap<Integer, Integer>> entry : sortedSizes.entrySet()) {
sb.append(entry.getKey()).append('[').append(entry.getValue()).append("], ");
}
if (!sortedSizes.isEmpty()) {
sb.replace(sb.length() - 2, sb.length(), "");
}
return sb.append(")}").toString();
}
@VisibleForTesting
static class KeyPool extends BaseKeyPool<Key> {
public Key get(int size, Bitmap.Config config) {
Key result = get();
result.init(size, config);
return result;
}
@Override
protected Key create() {
return new Key(this);
}
}
@VisibleForTesting
static final class Key implements Poolable {
private final KeyPool pool;
@Synthetic int size;
private Bitmap.Config config;
public Key(KeyPool pool) {
this.pool = pool;
}
@VisibleForTesting
Key(KeyPool pool, int size, Bitmap.Config config) {
this(pool);
init(size, config);
}
public void init(int size, Bitmap.Config config) {
this.size = size;
this.config = config;
}
@Override
public void offer() {
pool.offer(this);
}
@Override
public String toString() {
return getBitmapString(size, config);
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
return size == other.size && Util.bothNullOrEqual(config, other.config);
}
return false;
}
@Override
public int hashCode() {
int result = size;
result = 31 * result + (config != null ? config.hashCode() : 0);
return result;
}
}
@Synthetic
static String getBitmapString(int size, Bitmap.Config config) {
return "[" + size + "](" + config + ")";
}
private static Bitmap.Config[] getInConfigs(Bitmap.Config requested) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Bitmap.Config.RGBA_F16.equals(requested)) { // NOPMD - Avoid short circuiting sdk checks.
return RGBA_F16_IN_CONFIGS;
}
}
switch (requested) {
case ARGB_8888:
return ARGB_8888_IN_CONFIGS;
case RGB_565:
return RGB_565_IN_CONFIGS;
case ARGB_4444:
return ARGB_4444_IN_CONFIGS;
case ALPHA_8:
return ALPHA_8_IN_CONFIGS;
default:
return new Bitmap.Config[] {requested};
}
}
}
AttributeStrategy#put(Bitmap bitmap) VS SizeConfigStrategy#put(Bitmap bitmap)
AttributeStrategy
public void put(Bitmap bitmap) {
final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
groupedMap.put(key, bitmap);
}
SizeConfigStrategy
@Override
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
Key key = keyPool.get(size, bitmap.getConfig());
groupedMap.put(key, bitmap);
NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
Integer current = sizes.get(key.size);
sizes.put(key.size, current == null ? 1 : current + 1);
}
很显然算法策略输入参数不一样,
AttributeStrategy的key由bitmap的属性Width、Height、Config
SizeConfigStrategy的key由bitmap的size、config组成
相对比较一下SizeConfigStrategy安全性比较高一些。但计算性能方面会消耗大一些,其他方法实现与各自的put(Bitmap bitmap)保持一致,这里不具体说明了。
再看一个
SizeStrategy
包路径:com.bumptech.glide.load.engine.bitmap_recycle.SizeStrategy
这个策略并没有对外公开,但看其具体实现和SizeConfigStrategy相似度较高。和其他两个比较着看会好理解一些
基于Bitmap#reconfigure(int, int,Bitmap.Config)的位图复用策略
/**
* A strategy for reusing bitmaps that relies on {@link Bitmap#reconfigure(int, int,
* Bitmap.Config)}.
*
* <p>Requires {@link Build.VERSION_CODES#KITKAT KitKat} or higher.
*/
@RequiresApi(Build.VERSION_CODES.KITKAT)
final class SizeStrategy implements LruPoolStrategy {
private static final int MAX_SIZE_MULTIPLE = 8;
private final KeyPool keyPool = new KeyPool();
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
private final NavigableMap<Integer, Integer> sortedSizes = new PrettyPrintTreeMap<>();
@Override
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
final Key key = keyPool.get(size);
groupedMap.put(key, bitmap);
Integer current = sortedSizes.get(key.size);
sortedSizes.put(key.size, current == null ? 1 : current + 1);
}
@Override
@Nullable
public Bitmap get(int width, int height, Bitmap.Config config) {
final int size = Util.getBitmapByteSize(width, height, config);
Key key = keyPool.get(size);
Integer possibleSize = sortedSizes.ceilingKey(size);
if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) {
keyPool.offer(key);
key = keyPool.get(possibleSize);
}
// Do a get even if we know we don't have a bitmap so that the key moves to the front in the
// lru pool
final Bitmap result = groupedMap.get(key);
if (result != null) {
result.reconfigure(width, height, config);
decrementBitmapOfSize(possibleSize);
}
return result;
}
@Override
@Nullable
public Bitmap removeLast() {
Bitmap removed = groupedMap.removeLast();
if (removed != null) {
final int removedSize = Util.getBitmapByteSize(removed);
decrementBitmapOfSize(removedSize);
}
return removed;
}
private void decrementBitmapOfSize(Integer size) {
Integer current = sortedSizes.get(size);
if (current == 1) {
sortedSizes.remove(size);
} else {
sortedSizes.put(size, current - 1);
}
}
@Override
public String logBitmap(Bitmap bitmap) {
return getBitmapString(bitmap);
}
@Override
public String logBitmap(int width, int height, Bitmap.Config config) {
int size = Util.getBitmapByteSize(width, height, config);
return getBitmapString(size);
}
@Override
public int getSize(Bitmap bitmap) {
return Util.getBitmapByteSize(bitmap);
}
@Override
public String toString() {
return "SizeStrategy:\n " + groupedMap + "\n" + " SortedSizes" + sortedSizes;
}
private static String getBitmapString(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
return getBitmapString(size);
}
@Synthetic
static String getBitmapString(int size) {
return "[" + size + "]";
}
// Non-final for mocking.
@VisibleForTesting
static class KeyPool extends BaseKeyPool<Key> {
public Key get(int size) {
Key result = super.get();
result.init(size);
return result;
}
@Override
protected Key create() {
return new Key(this);
}
}
@VisibleForTesting
static final class Key implements Poolable {
private final KeyPool pool;
@Synthetic int size;
Key(KeyPool pool) {
this.pool = pool;
}
public void init(int size) {
this.size = size;
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
return size == other.size;
}
return false;
}
@Override
public int hashCode() {
return size;
}
// PMD.AccessorMethodGeneration: https://github.com/pmd/pmd/issues/807
@SuppressWarnings("PMD.AccessorMethodGeneration")
@Override
public String toString() {
return getBitmapString(size);
}
@Override
public void offer() {
pool.offer(this);
}
}
}
小计
算法策略果然是比较烧脑的,这个策略的实现结构简单,实现过程尽量理解,看不太懂没有关系,本章主要是丰富策略模式实现,面向对象编程偏重代码结构,具体的实现在具体的应用场景下进行调试体会即可。
扩展
本策略的实现方式提供了一种接口实现单元测试的方式。在后续的单元测试篇章中会再次丰富说明的。
注意:
后续的单元测试篇章是一种编程路径和编程习惯。这个很容易引起开发者读者的争议,但其实并没有给出提高生产力的技术实现应用,反而比较关注提高代码质量方面。很类似一种单元测试练习册似的篇章。因此后续所有单元测试的文章会全部设置为VIP可读或者付费可读的方式。我个人也不建议了解或理解单元测试的开发者去查阅。因为有一定单元测试理念参与到自己编程习惯里面后。对于他人的单元测试理念和习惯就会有一定看法上的影响。这样会在留言讨论过程中对其他读者产生误解。作者建议单元测试篇章对于不了解单元测试的开发者是一条练习的道路,但单元测试并没有提供技术实现应用经典。请读者客观理解。
自研产品推荐
历时一年半多开发终于smartApi-v1.0.0版本在2023-09-15晚十点正式上线
smartApi是一款对标国外的postman的api调试开发工具,由于开发人力就作者一个所以人力有限,因此v1.0.0版本功能进行精简,大功能项有:
- api参数填写
- api请求响应数据展示
- PDF形式的分享文档
- Mock本地化解决方案
- api列表数据本地化处理
- 再加上UI方面的打磨
为了更好服务大家把之前的公众号和软件激活结合,如有疑问请大家反馈到公众号即可,下个版本30%以上的更新会来自公众号的反馈。
嗯!先解释不上服务端原因,API调试工具的绝大多数时候就是一个数据模型、数据处理、数据模型理解共识的问题解决工具,所以作者结合自己十多年开发使用的一些痛点来打造的,再加上服务端开发一般是面向企业的,作者目前没有精力和时间去打造企业服务。再加上没有资金投入所以服务端开发会滞后,至于什么时候会进行开发,这个要看募资情况和用户反馈综合考虑。虽然目前国内有些比较知名的api工具了,但作者使用后还是觉得和实际使用场景不符。如果有相关吐槽也可以在作者的公众号里反馈蛤!
下面是一段smartApi使用介绍: