快捷搜索:  as

Android 性能优化系列 - 03 使用对象池优化内存

catalog.png

一. 概述

无意偶尔候 UI 卡顿是由于发生了频繁的 GC 造成的,频繁的 GC 着实对应的内存环境便是内存哆嗦,而发生内存哆嗦一样平常都是由于在轮回里面或者频繁被调用的措施(比如 View.onDraw() 措施)里面创建工具造成的,面对这种环境,一样平常有两种做法:

避免在轮回里面或者频繁被调用的措施里面创建工具,在轮回或者被频繁调用的措施之外创建工具

应用工具池缓存工具,在应用工具的时刻,直接从工具池中取工具即可,避免频繁的创建工具

一样平常来讲,实现工具池有三种措施,对应的详细的数据布局等于数组、链表和 Map,下面我将经由过程示例分手先容经由过程这三种数据布局实现的工具池

二. 工具池的实现

2.1 经由过程数组实现

在 Android 的 v4 包中有个类 Pools,此中定义了一个工具池接口 Pool,详细的实现有两个 SimplePool 和 SynchronizedPool,这两个实现顾名思义,一个长短线程安然的,另一个是线程安然的。

Pools 类统共代码也不长,加上注释不到 200 行,接下来看下代码

2.1.1 接口定义

首先是 Pool 接口定义,代码不难,如下所示,只有两个措施 acquire() 和 release(@NonNull T instance) 措施

acquire():从工具池中取一个工具,留意:有可能会返回 null

release(@NonNull T instance):将一个工具开释到工具池中,return true 表示工具成功放到工具池中了,return false 表示工具没有成功地放到工具池中,假如参数工具 instance 已经在工具池中,则会抛出 IllegalStateException 非常

Note:假如一味的只从工具池中获取工具(调用 acquire()),而不向工具池中放入工具(调用release(@NonNull T instance)),那么经由过程 acquire() 获得的工具,有可能为 null,也有可能是 new 出来的,并没有达到缓存工具的目的,以是在应用竣工具今后,要调用 release(@NonNull T instance) 及时地将工具开释到工具池中国

/**

* Interface for managing a pool of objects.

*

* @paramThe pooled type.

*/

public interface Pool {

/**

* @return An instance from the pool if such, null otherwise.

*/

@Nullable

T acquire();

/**

* Release an instance to the pool.

*

* @param instance The instance to release.

* @return Whether the instance was put in the pool.

*

* @throws IllegalStateException If the instance is already in the pool.

*/

boolean release(@NonNull T instance);

}

2.1.2 非线程安然类

接下来我们看下 Pool 接口的非线程安然的实现类 SimplePool,代码如下

此中声清楚明了两个类变量,数组 Object[] mPool 用于缓存工具,这也应证了我之条件到的在 Pools 中是应用数组实现工具池的;另一个变量 int mPoolSize 表示此时工具池中缓存了若干个工具

下面的 acquire() 和 release(@NonNull T instance) 措施实现也不难,我在注释中加以阐明即可

public static class SimplePool implements Pool {

private final Object[] mPool;

private int mPoolSize;

/**

* SimplePool 的构造措施,参数 maxPoolSize 是数组 mPool 的长度,也表示此工具池最大年夜可以缓存若干个工具,假如 maxPoolSize0");

}

mPool = new Object[maxPoolSize];

}

/**

* acquire() 措施,首先判断此时工具池中有没有缓存的工具

*假如有,则从 mPool 数组中取着末一个缓存工具并返回,同时将数组着末一个引用置为 null,mPoolSize --

*假如没有,则直接返回 null

*/

@Override

@SuppressWarnings("unchecked")

public T acquire() {

if (mPoolSize > 0) {

final int lastPooledIndex = mPoolSize - 1;

T instance = (T) mPool[lastPooledIndex];

mPool[lastPooledIndex] = null;

mPoolSize--;

return instance;

}

return null;

}

/**

* release(@NonNull T instance) 措施,首先判断此工具是不是已经缓存在工具池中,假如已经在工具池中则直接抛出非常

* 假如缓存的工具个数没有跨越 mPool 数组的长度,则将工具放到 mPool 数组的着末一位,并将 mPoolSize++ return true

* 假如缓存的工具个数跨越 mPool 数组的长度,则直接 return false,参数工具 instance 也并不会放入到缓存数据 mPool 中

*/

@Override

public boolean release(@NonNull T instance) {

if (isInPool(instance)) {

throw new IllegalStateException("Already in the pool!");

}

if (mPoolSize

2.1.3 线程安然类

线程安然的实现类长短线程安然的实现类 SimplePool 的子类,只是在构造措施、acquire() 和 release(@NonNull T element) 措施中,经由过程 synchronized 加锁实现线程安然的,没有太多可说的,代码如下所示

public static class SynchronizedPool extends SimplePool {

private final Object mLock = new Object();

public SynchronizedPool(int maxPoolSize) {

super(maxPoolSize);

}

@Override

public T acquire() {

synchronized (mLock) {

return super.acquire();

}

}

@Override

public boolean release(@NonNull T element) {

synchronized (mLock) {

return super.release(element);

}

}

}

2.1.4 应用示例

Pools 的应用主如果经由过程 SimplePool 和 SynchronizedPool 这两个实现类应用的,应用也很简单。他们都有泛型参数 T,以是可以充当随意率性工具的工具池,下面以 Pools 中的注释示例代码阐明

public class MyPooledClass {

private static final SynchronizedPool sPool = new SynchronizedPool(10);

public static MyPooledClass obtain() {

MyPooledClass instance = sPool.acquire();

return (instance != null) ? instance : new MyPooledClass();

}

public void recycle() {

// Clear state if needed.

sPool.release(this);

}

. . .

}

2.2 经由过程链表实现

上面先容了经由过程数组的要领实现工具池的 Pools 类,下面接着先容经由过程链表的要领实现工具池的例子 Message。

对付做 Android 开拓的同伙来说,Message 类应该是很认识了,也都知道经由过程 Message.obtain() 措施是从缓存(着实便是从 Message 工具池)中获取 Message 工具的,然则这样的理解是片面的,为什么这样说呢?由于假如只是经由过程 Message.obtain() 获得 Message 工具,然后应用 Message 工具,用完就完了,这样的应用要领也并没有达到缓存 Message 工具的感化,精确的做法是,在应用完 Message 工具今后,还应该主动地调用 message.recycle() 措施收受接收 Message 工具,将 Message 工具收受接收进 Message 工具池中,这样今后才可以经由过程 Message.obtain() 继承复用这个 Message 工具。

接下来,我们就阐发一下 Message 类中的工具池是若何实现的

2.2.1 Message 源码阐发

首先,我们来看一下 Message 类中和 Message 工具池相关的措施和变量。

obtain() 措施大年夜家应该很认识,它的感化是经由过程调用 obtain() 获得一个在 Message 工具池中的 Message 工具,避免经由过程 new 的要领新建 Message工具。底层是经由过程一个单链表 sPool 工具实现的,这个 sPool 单链表和我们日常平凡应用的单链表略有不合,不合之处在于 sPool 是在链表的头部插入和删除链表节点的,而不是在尾部

recycle() 措施,它的感化是将 Message 工具收受接收进 Message 工具池,着实真正起感化的是 recycleUnchecked() 措施,在 recycleUnchecked() 开始会将 Message 中的变量复位,包括 flags = FLAG_IN_USE 以注解此工具现在正在应用中,然后同步地将此 Message 工具插入到 sPool 链表的头部,并将头节点指向此 Message 工具,着末将 sPoolSize++

同步是经由过程 sPoolSync 变量和 synchronized 实现的

Note:此中几个关键的变量 链表 sPool、链表长度 sPoolSize 都是 static 的,注解在全部 Android 利用中共享此 Message 工具池,而且 Message 工具最大年夜长度是 50 个

public final class Message implements Parcelable {

......

/*package*/ Message next;

private static final Object sPoolSync = new Object();

private static Message sPool;

private static int sPoolSize = 0;

private static final int MAX_POOL_SIZE = 50;

......

public static Message obtain() {

synchronized (sPoolSync) {

if (sPool != null) {

Message m = sPool;

sPool = m.next;

m.next = null;

m.flags = 0; // clear in-use flag

sPoolSize--;

return m;

}

}

return new Message();

}

......

public void recycle() {

if (isInUse()) {

if (gCheckRecycle) {

throw new IllegalStateException("This message cannot be recycled because it "

+ "is still in use.");

}

return;

}

recycleUnchecked();

}

/**

* Recycles a Message that may be in-use.

* Used internally by the MessageQueue and Looper when disposing of queued Messages.

*/

void recycleUnchecked() {

// Mark the message as in use while it remains in the recycled object pool.

// Clear out all other details.

flags = FLAG_IN_USE;

what = 0;

arg1 = 0;

arg2 = 0;

obj = null;

replyTo = null;

sendingUid = -1;

when = 0;

target = null;

callback = null;

data = null;

synchronized (sPoolSync) {

if (sPoolSize

好了,经由过程链表的要领实现工具池的例子就先容到这里,接下来接着先容经由过程 Map 实现工具池的例子

2.3 Glide 中经由过程 Map 实现 BitmapPool

经由过程 Map 的要领实现工具池,这里用 Glide 中缓存 Bitmap 的 BitmapPool 阐明。

大年夜家都知道 Glide 是用于加载图片的库,提到图片肯定绕不开 Bitmap,而 Bitmap 又是吃内存的妙手,以是应用 BitmapPool 缓存 Bitmap 工具,避免重复创建 Bitmap 过渡耗损内存就变得十分紧张了。

先看一下 Glide 中的目录布局,如下图所示,从名字也可以看出,用血色框起来的几个类都是和 BitmapPool 相关的类,我们逐一加以阐发。

glide-catalog.png

2.3.1 BitmapPool 接口定义

首先看一下,BitmapPool 接口,其定义如下所示,下面有具体的注释阐明

public interface BitmapPool {

/**

* 返回当前 BitmapPool 的最大年夜容量,单位是 byte 字节

*/

long getMaxSize();

/**

* 可以经由过程此措施设置一个因子,此因子会乘以最开始设置的最大年夜容量,将结果作为新的最大年夜容量

* 上步谋略完成今后,假如 BitmapPool 确当前实际容量比当前最大年夜容量大年夜,则会清除 BitampPool 中的工具,直到当前实际容量小于当前最大年夜容量

* Note:开拓者可以经由过程此措施动态地、同步地调剂 BitmapPool 最大年夜容量

*/

void setSizeMultiplier(float sizeMultiplier);

/**

* 将此 Bitmap 工具添加到工具池中

* 假如相符前提,BitmapPool 会改动并重用,否则会调用 Bitmap.recycle() 措施扬弃此 Bitmap 工具

* Note:调用此措施将此 Bitmap 工具添加到工具池中今后,此 Bitmap 工具就不能再被应用了

*/

void put(Bitmap bitmap);

/**

* 经由过程此措施可以获得一个,width、height、config 参数都相符前提的 Bitmap,此 Bitmap 工具中只包孕透明度的色值

* 假如 BitmapPool 中没有相符此前提的 Bitmap 缓存工具,则会创建一个新的 Bitmap 工具并返回

* Note:经由过程此工具获得的 Bitmap 工具,会调用 bitmap.eraseColor(Color.TRANSPARENT) 措施清除 bitmap 中所有的像素,只保留透明度像素

*这也是和 `getDirty(int width, int height, Bitmap.Config config)` 措施的主要差别

*/

@NonNull

Bitmap get(int width, int height, Bitmap.Config config);

/**

* 见 get(int width, int height, Bitmap.Config config) 措施注释

*/

@NonNull

Bitmap getDirty(int width, int height, Bitmap.Config config);

/**

* 清除 BitmapPool 中的所有 Bitmap 缓存工具

*/

void clearMemory();

/**

* 根据给定的 level 参数,不合程度地清除 BitmapPool 中的大年夜小,比如:可以清除所有的 Bitmap 缓存工具,也可以清除一半的 Bitmap 缓存工具

*/

void trimMemory(int level);

}

2.3.2 BitmapPool 的实现类

BitmapPool 是个接口,定义了通用的措施,在 Glide 中有两个 BitmapPool 的实现类:BitmapPoolAdapter 和 LruBitmapPool。

BitmapPoolAdapter 类是一个空实现类,此中没有什么逻辑,对付每个添加进 BitmapPoolAdapter 缓存池的 Bitmap 都邑扬弃,从 BitmapPoolAdapter 缓存池中取到的每个 Bitmap 都是新创建的。

这里具体阐发一下 LruBitmapPool,在先容 LruBitmapPool 之前,首先先容别的几个类:LruPoolStrategy、Key 和 KeyPool。

2.3.2.1 LruPoolStrategy

LruPoolStrategy 是针对 LruBitmapPool 类定义的一个策略接口,此中包括一些寄放、获取、移除 Bitmap 的措施,详细如下所示

可以看到 LruPoolStrategy 接口是没有权限修饰符的,即阐明 LruPoolStrategy 只可以在 Glide 包内部应用

interface LruPoolStrategy {

// 寄放 Bitmap 工具

void put(Bitmap bitmap);

// 根据 width、height、config 属性获取对应的 Bitmap 工具

@Nullable

Bitmap get(int width, int height, Bitmap.Config config);

// 移除着末一个 Bitmap 工具

@Nullable

Bitmap removeLast();

// 获取 Bitmap 工具的用于打印的信息

String logBitmap(Bitmap bitmap);

// 同 logBitmap(Bitmap bitmap)

String logBitmap(int width, int height, Bitmap.Config config);

// 获得一个 Bitmap 工具的大年夜小

int getSize(Bitmap bitmap);

}

在 Glide 内部 LruPoolStrategy 接口有三个实现类,分手是 AttributeStrategy、SizeConfigStrategy 和 SizeStrategy 类,这三个类是经由过程不合维度的前提缓存 Bitmap 的,详细如下

AttributeStrategy 是经由过程 Bitmap 的 width、height、config 三个前提缓存 Bitmap 工具

SizeConfigStrategy 是经由过程 Bitmap 的 size、config 两个前提缓存 Bitmap 工具

SizeStrategy 是经由过程 Bitmap 的 size 前提缓存 Bitmap 工具

可能有人会有疑问了,在 BitmapPool 和 LruPoolStrategy 接口中定义的都是 Bitmap get(int width, int height, Bitmap.Config config) 措施,传入的都是 width、height、config 三个前提,为什么可以经由过程不合的前提缓存 Bitmap 工具呢?

2.3.2.2 Key & KeyPool

这个时刻就必要引入 Key 和 KeyPool 这两个类了。

BitmapPool 和 LruPoolStrategy 都只是接口定义,其底层的实现类着实都是应用了 Map 数据布局。大年夜家都知道 Map 因此键值对的形式在 K 和 V 之间形成逐一映射的,我们的 V 不用多说,自然便是 Bitmap 工具了,而 K 的话就可所以不合形式的了。在 Glide 中,详细的 K 是 Key 这样的一个类,而为了避免每次寄放、获取 Bitmap 工具而新建 Key 工具,引入了 KeyPool 的观点。

Glide 中,LruPoolStrategy 有三个不合的实现类 AttributeStrategy、SizeConfigStrategy 和 SizeStrategy,他们是经由过程不合维度的前提缓存 Bitmap,这三个不合维度的前提详细就体现在它们各自内部的实现类 Key 上。

以 AttributeStrategy 为例,AttributeStrategy 是经由过程 Bitmap 的 width、height、config 三个前提缓存 Bitmap 工具,以是 AttributeStrategy 内部类 Key 的实现如下

在 AttributeStrategy.Key 类中包孕 width、height 和 config 三个属性,且经由过程 init(int width, int height, Bitmap.Config config) 措施初始化

作为 Map 的 K,自然要重写 equals(Object o) 和 hashCode() 措施,且此中都用到了 width、height、config 三个属性

在 LruPoolStrategy 别的两个类 SizeConfigStrategy 和 SizeStrategy 中 Key 也是类似的实现,这里就不多说了

class AttributeStrategy implements LruPoolStrategy {

......

@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);

}

}

}

说完 Key,接下来说一下 KeyPool 类,Glide 为了避免每次寄放、获取 Bitmap 工具都新建 Key 工具,引入了 KeyPool 的观点

由于不合的缓存策略对应不合的 Key,不合的 Key 自然要对应不合的 KeyPool 了,有三种缓存策略,以是也有三种 Key 和 KeyPool 了。

先先容三种 KeyPool 的基类 BaseKeyPool 类,如下所示

BaseKeyPool 类是个泛型类,只如果实现了 Poolable 接口的类的工具,都可以经由过程 BaseKeyPool 工具池缓存

在 BaseKeyPool 类中是经由过程一个行列步队 Queue 实现缓存的,对多可以缓存 20 个工具

BaseKeyPool 也是默认修饰符的,只可以在 Glide 包内部应用

abstract class BaseKeyPool {

private static final int MAX_SIZE = 20;

private final Queue keyPool = Util.createQueue(MAX_SIZE);

// 经由过程 get() 措施可以获得一个 T 工具,T 工具有可能是从缓存行列步队中取的,也有可能是经由过程 create() 措施新建的

T get() {

T result = keyPool.poll();

if (result == null) {

result = create();

}

return result;

}

// 经由过程 offer(T key) 措施将 T 工具加入到缓存行列步队中,条件是缓存行列步队没有满

public void offer(T key) {

if (keyPool.size()

先容完 BaseKeyPool 基类今后,接着先容一下在 AttributeStrategy 中利用的 KeyPool 类,其定义如下

AttributeStrategy.KeyPool 中缓存的 Key 都是 AttributeStrategy.Key 工具

class AttributeStrategy implements LruPoolStrategy {

......

@VisibleForTesting

static class KeyPool extends BaseKeyPool {

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);

}

}

......

}

2.3.2.2 AttributeStrategy

着实先容完 AttributeStrategy.KeyPool 和 AttributeStrategy.Key 今后,AttributeStrategy 剩下的代码也不多了,其代码如下

GroupedLinkedMap 类似于 LinkedHashMap,其实用 HashMap、List 和双向链表实现了 LRU 算法,后面会具体先容。

可以看到在 put(Bitmap bitmap) & get(int width, int height, Bitmap.Config config) 时,都是先从 KeyPool 中获得对应的 Key 今后,再去操作 groupedMap 的

class AttributeStrategy implements LruPoolStrategy {

private final KeyPool keyPool = new KeyPool();

private final GroupedLinkedMap groupedMap = new GroupedLinkedMap {

......

}

@VisibleForTesting

static class Key implements Poolable {

......

}

}

2.3.2.3 LruBitmapPool

上面先容了 LruBitmapPool 相关的几个异常紧张的观点:LruPoolStrategy、Key 和 KeyPool,以及 LruPoolStrategy 实现类 AttributeStrategy,假如对上面先容的内容都理解了,那么理解 LruBitmapPool 就更轻易一些了

两个 public 的构造措施着末都邑调用默认修饰符的构造措施,传入的 LruPoolStrategy strategy 参数是经由过程 getDefaultStrategy() 措施获取的,可以看到在 API 19 及以上应用的是 SizeConfigStrategy 缓存策略,在 API 19 以下则应用的 AttributeStrategy 缓存策略

在 put(Bitmap bitmap) 措施中,首先会做需要的前提反省,然后会调用 strategy.put(bitmap),将 bitmap 工具缓存到工具池中,着末改动相关的参数,如 currentSize

在 get(int width, int height, Bitmap.Config config) 和 getDirty(int width, int height, Bitmap.Config config) 措施中,首先都邑调用 getDirtyOrNull(int width, int height, @Nullable Bitmap.Config config) 措施从 strategy.get(width, height, config != null ? config : DEFAULT_CONFIG) 缓存工具池中获取 Bitmap,假如获得的 Bitmap 工具为空,则调用 createBitmap(int width, int height, @Nullable Bitmap.Config config) 措施创建一个新的 Bitmap 工具并返回

public class LruBitmapPool implements BitmapPool {

private final LruPoolStrategy strategy;

private final long initialMaxSize;

......

private long maxSize;

private long currentSize;

// 默认修饰符的构造措施,别的两个构造措施着末都邑调用此构造措施

LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set allowedConfigs) {

this.initialMaxSize = maxSize;

this.maxSize = maxSize;

this.strategy = strategy;

this.allowedConfigs = allowedConfigs;

this.tracker = new NullBitmapTracker();

}

public LruBitmapPool(long maxSize) {

this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());

}

public LruBitmapPool(long maxSize, Set allowedConfigs) {

this(maxSize, getDefaultStrategy(), allowedConfigs);

}

@Override

public long getMaxSize() {

return maxSize;

}

@Override

public synchronized void setSizeMultiplier(float sizeMultiplier) {

maxSize = Math.round(initialMaxSize * sizeMultiplier);

evict();

}

@Override

public synchronized void put(Bitmap bitmap) {

if (bitmap == null) {

throw new NullPointerException("Bitmap must not be null");

}

if (bitmap.isRecycled()) {

throw new IllegalStateException("Cannot pool recycled bitmap");

}

if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize

|| !allowedConfigs.contains(bitmap.getConfig())) {

if (Log.isLoggable(TAG, Log.VERBOSE)) {

Log.v(TAG, "Reject bitmap from pool"

+ ", bitmap: " + strategy.logBitmap(bitmap)

+ ", is mutable: " + bitmap.isMutable()

+ ", is allowed config: " + allowedConfigs.contains(bitmap.getConfig()));

}

bitmap.recycle();

return;

}

final int size = strategy.getSize(bitmap);

strategy.put(bitmap);// 关键,缓存进 strategy 工具池中

tracker.add(bitmap);

puts++;

currentSize += size;

if (Log.isLoggable(TAG, Log.VERBOSE)) {

Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));

}

dump();

evict();

}

private void evict() {

trimToSize(maxSize);

}

@Override

@NonNull

public Bitmap get(int width, int height, Bitmap.Config config) {

Bitmap result = getDirtyOrNull(width, height, config);

if (result != null) {

// Bitmaps in the pool contain random data that in some cases must be cleared for an image

// to be rendered correctly. we shouldn't force all consumers to independently erase the

// contents individually, so we do so here. See issue #131.

result.eraseColor(Color.TRANSPARENT);// 关键,将 Bitmap 工具的像素否复位为透明的

} else {

result = createBitmap(width, height, config);// 关键,若从缓存工具池中获得的 Bitmap 工具为空,则经由过程 createBitmap 措施创建新的 Bitmap 工具

}

return result;

}

@NonNull

@Override

public Bitmap getDirty(int width, int height, Bitmap.Config config) {

Bitmap result = getDirtyOrNull(width, height, config);

if (result == null) {

result = createBitmap(width, height, config);// 关键,创建新的 Bitmap 工具

}

return result;

}

@NonNull

private static Bitmap createBitmap(int width, int height, @Nullable Bitmap.Config config) {

return Bitmap.createBitmap(width, height, config != null ? config : DEFAULT_CONFIG);

}

@Nullable

private synchronized Bitmap getDirtyOrNull(

int width, int height, @Nullable Bitmap.Config config) {

assertNotHardwareConfig(config);

// Config will be null for non public config types, which can lead to transformations naively

// passing in null as the requested config here. See issue #194.

final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);// 关键,从 strategy 中获取缓存的 Bitmap 工具

if (result == null) {

if (Log.isLoggable(TAG, Log.DEBUG)) {

Log.d(TAG, "Missing bitmap=" + strategy.logBitmap(width, height, config));

}

misses++;

} else {

hits++;

currentSize -= strategy.getSize(result);

tracker.remove(result);

normalize(result);

}

if (Log.isLoggable(TAG, Log.VERBOSE)) {

Log.v(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config));

}

dump();

return result;

}

......

private static LruPoolStrategy getDefaultStrategy() {

final LruPoolStrategy strategy;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

strategy = new SizeConfigStrategy();

} else {

strategy = new AttributeStrategy();

}

return strategy;

}

......

}

2.3.3 GroupedLinkedMap 实现 LRU 算法

LruPoolStrategy 是缓存策略接口,详细的实现类有 AttributeStrategy、SizeConfigStrategy 和 SizeStrategy,这三个类是经由过程不合的前提来缓存 Bitmap 的,底层详细的实现都应用了 GroupedLinkedMap 数据布局,而 GroupedLinkedMap 实现了 LRU 算法,现在就具体先容下它是若何实现 LRU 算法的

GroupedLinkedMap 的类定义如下所示,此中有两个异常紧张的属性 Map> keyToEntry 和 LinkedEntry head

GroupedLinkedMap.png

LinkedEntry 是 GroupedLinkedMap 的一个内部类,代码实实际在并不繁杂

此中的 K key 属性对应于其在 GroupedLinkedMap 的 K

List values 则是真正存储 V 的聚拢

LinkedEntry next 和 LinkedEntry prev 则用于实现双向链表,而双向链表恰是实现 LRU 算法真正的数据布局

class GroupedLinkedMap {

......

private static class LinkedEntry {

@Synthetic final K key;

private List values;

LinkedEntry next;

LinkedEntry prev;

// Used only for the first item in the list which we will treat specially and which will not

// contain a value.

LinkedEntry() {

this(null);

}

LinkedEntry(K key) {

next = prev = this;

this.key = key;

}

@Nullable

public V removeLast() {

final int valueSize = size();

return valueSize > 0 ? values.remove(valueSize - 1) : null;

}

public int size() {

return values != null ? values.size() : 0;

}

public void add(V value) {

if (values == null) {

values = new ArrayList

先容完 LinkedEntry 之后,再接着先容一下 GroupedLinkedMap 中异常紧张的几个措施,如下所示

put(K key, V value) 措施,首先根据 K key 从 Map> keyToEntry = new HashMap 中查找对应的 LinkedEntry,假如查找结果为 null,则新建一个 LinkedEntry 工具,并将此新建的 LinkedEntry 工具插入到双向链表的尾部,然后将 V value(也便是 Bitmap 工具) 插入到 LinkedEntry 工具的 List values 中

调用 get(K key) 措施获取 Bitmap 工具时,会先经由过程 K key 从 Map> keyToEntry 查找对应的 LinkedEntry 工具,假如查找结果为 null,则新建一个 LinkedEntry 工具,着末将此 LinkedEntry 工具插入到双向链表的头部,然后从此 LinkedEntry 工具的 List values 中取一个 Bitmap 工具并返回

V removeLast() 措施,则从双向链表尾部节点的 LinkedEntry 中的 List values 中移除着末一个 Bitmap 工具

从上面对 put(K key, V value) 措施和 get(K key) 措施阐发可以明白,应用 LinkedEntry 双向链表是为了支持 LRU 算法,近来应用过的 K key 以及对应的 LinkedEntry 工具都在双向链表的头部,应用次数越少越接近双向链表的尾部,假如要从此链表中移除一个 Bitmap,也是调用 removeLast() 措施从双向链表尾部节点的 LinkedEntry 中的 List values 中移除着末一个 Bitmap 工具

class GroupedLinkedMap {

private final LinkedEntry head = new LinkedEntry> keyToEntry = new HashMap entry = keyToEntry.get(key);

if (entry == null) {

entry = new LinkedEntry entry = keyToEntry.get(key);

if (entry == null) {

entry = new LinkedEntry last = head.prev;

while (!last.equals(head)) {

V removed = last.removeLast();

if (removed != null) {

return removed;

} else {

// We will clean up empty lru entries since they are likely to have been one off or

// unusual sizes and

// are not likely to be requested again so the gc thrash should be minimal. Doing so will

// speed up our

// removeLast operation in the future and prevent our linked list from growing to

// arbitrarily large

// sizes.

removeEntry(last);

keyToEntry.remove(last.key);

last.key.offer();

}

last = last.prev;

}

return null;

}

......

}

着末贴一张 GroupedLinkedMap 的示意图,信托对理解双向链表、Map、以及 List 实现 GroupedLinkedMap 的要领会有所赞助

GroupedLinkedMap1.png

Glide 源码阐发解读-缓存模块-基于最新版Glide 4.9.0

2.3.4 Glide 阐发小结

由于在 Glide 中缓存的是 Bitmap 工具,而 Bitmap 工具又有不合的属性,比如 width、height、config(这些属性统称为 Key),以是就必要将属性 Key 和 Bitmap 实例工具逐一映射。在 Glide 中也拥有两个工具池,分手是属性 Key 工具池和 Bitamp 实例工具池

属性 Key 工具池实现对照简单,是经由过程行列步队 Queue keyPool = new ArrayDeque 实现的,对多可以缓存 20 个 Key 工具

Bitmap 工具池实现就相对繁杂,定义了 BitmapPool 接口,着实现类是 LruBitmapPool,而在 LruBitmapPool 中又可以经由过程不合的策略 LruPoolStrategy 缓存工具,LruPoolStrategy 有三个实现类 AttributeStrategy、SizeConfigStrategy 和 SizeStrategy,分手根据 Bitmap 的不合属性 Key(width、height、config)缓存 Bitmap 工具的。必要留意的是,在 API 19 及之后应用 SizeConfigStrategy 缓存策略,在 API 19 之前应用 AttributeStrategy 缓存策略

而在 AttributeStrategy、SizeConfigStrategy 和 SizeStrategy 这三个类中,底层的实现又依附于 GroupedLinkedMap,它经由过程双向链表的措施实现了 LRU 算法,近来应用的在双向链表的头部,应用次数越少的 Key 越接近双向链表的尾部

着末,再贴一张 Glide 中 BitmapPool 的类关系图,信托对理解 Glide 中的 BitmapPool 常识会有所赞助

Glide.png

拆 Glide 系列之 - Bitmap 复用

三. 总结

本文从数据布局的角度,分手先容了工具池的实现要领,数组、链表、Map 都可以实现工具池,可以根据不合的利用处景机动地应用详细的数据布局来实现工具池。在 Glide 中利用的可以说是淋漓尽致,单说 BitmapPool 的实现就异常让人称颂了。

对付工具池,小我觉得有以下几点是异常必要留意的

工具池的大年夜小。工具池的目的是用于在内存中缓存工具,然则不能所有的工具都缓存,假如所有的工具都缓存,无疑对内存也是一个异常伟大年夜的损耗。以是每个工具池都必要设置一个大年夜小,从上面三个例子中也可以看到每个工具池都有默认的大年夜小的,或者必要在调用构建措施的时刻就设置大年夜小

抽象 & 泛型。为了通用性,工具池一样平常都邑先定义一个接口或者抽象基类,而且是泛型的接口或者基类,上面先容的 Pool、LruPoolStrategy、BaseKeyPool 便是最好的例证,这两点有什么好处呢?接口或者抽象基类有利于扩展,实现不合的寄放、获取的措施,而泛型则可以存储不合的工具

抽象措施定义。工具池一样平常有三个动作寄放 put、获取 get 以及新建 create,以是在接口或者抽象基类中一样平常包孕这三个措施即可

先容了这么多关于工具池的内容,着实终极的目的都是为了避免频繁的创建和收受接收工具,从而维持内存的稳定。在优化内存时,工具池真是一个异常紧张的武器。

四. 参考

拆 Glide 系列之 - Bitmap 复用

Glide 源码阐发解读-缓存模块-基于最新版Glide 4.9.0

工具池Pools优化

您可能还会对下面的文章感兴趣: