在Glide的设计中应用了大量对象池的概念,我们就来学习一下对象池
Android对象池的支持
Android中为了方便使用,官方提供了一个工具类Pools,让大家可以方便的实现对象池,这里不对源码详细解释了,大家可以自己看,很简单。简单说一下实现:就是采用一个Object数组(Android 的Message对象池是采用链表实现的,大小是50个),数组大小外部指定,acquire方法获取对象池中一个对象,没有就返回null,release回收这个对象,前提是数组没满,存取都是对数组末尾操作的。
代码实现很简单也很纯粹,只负责存取,不负责创建对象和对象自身状态更新,后面我们会给使用的方式,到时就明白了。
对象池中对象的自身状态
我们知道可以从对象池中取(acquire)对象使用,对象使用完毕,可以把这个对象放到对象池中(release)。
有时对象的使用过程是复杂的或者生命周期很长,可能在某些操作的时候,这个对象被对象池回收了,但是在某些操作的时候,又用到这个对象,这样就会出问题。所以,对象本身要知道自己有没有被回收是很重要的,这就是我们要说的对象自身的验证。
对象除了要知道自己是否被回收,有时知道自己被回收了要做一些操作,比如抛出一个异常。
因为对象池可以放各种类型的对象,为了通用化,我们抽象出一个接口Poolable,这个接口提供方法几个方法,一个方法是让设置对象是否被回收了,另一个方法如果对象被回收了就做一些操作。放到对象池的的类,实现Poolable接口就好了。
还有一个问题,如果Poolable的方法里面都是一些相同的操作,不同的类实现了Poolable都要写一些一样的代码,这样会产生大量重复代码。这个时候就体现面向对象三大特性之一的“封装性”了。我们把Poolable里面的方法封装到另一个接口StateVerifier里面,StateVerifier可以有各种实现类,Poolable只需要对外暴露一个getVerifier方法。这样,对象回收相关的操作都被封装到StateVerifier里面了,同时还方便以后进行扩展。很棒的设计。
上面说的这些就是Glide里面Poolable和StateVerifier的设计思想,大家再看源码的时候,就很容易理解了。
使用方式
方式一:
Pools给的例子中有一种使用方式,和Message中对象池暴露的接口是一样的。就是对象池做为类字段static的,类方法obtain去对象池中获取对象,没有创建一个;对象方法recycle对对象进行回收。我们看一下demo
public class MyPooledClass {
private static final SynchronizedPool<MyPooledClass> sPool =
new SynchronizedPool<MyPooledClass>(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);
}
. . .
}
上面这种方式简单直观,可是有个问题,如果我们定制化对象创建的过程呢?上面的实现方案就很难做到,不够灵活了。
Glide给出了另一种方案,我们可以看看,主要类是FactoryPools,相关的接口和类都封装到了里面
方式二:
package com.bumptech.glide.util.pool;
import android.support.annotation.NonNull;
import android.support.v4.util.Pools.Pool;
import android.support.v4.util.Pools.SimplePool;
import android.support.v4.util.Pools.SynchronizedPool;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Provides implementations of {@link Pool} never return {@code null}, log when new instances are
* created, and that can use the {@link com.bumptech.glide.util.pool.FactoryPools.Poolable}
* interface to ensure objects aren't used while inside the pool.
*/
public final class FactoryPools {
private static final String TAG = "FactoryPools";
private static final int DEFAULT_POOL_SIZE = 20;
private static final Resetter<Object> EMPTY_RESETTER = new Resetter<Object>() {
@Override
public void reset(@NonNull Object object) {
// Do nothing.
}
};
private FactoryPools() { }
/**
* Returns a non-thread safe {@link Pool} that never returns {@code null} from
* {@link Pool#acquire()} and that contains objects of the type created by the given
* {@link Factory} with the given maximum size.
*
* <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will
* be used to create a new instance.
*
* @param <T> The type of object the pool will contains.
*/
@NonNull
public static <T extends Poolable> Pool<T> simple(int size, @NonNull Factory<T> factory) {
return build(new SimplePool<T>(size), factory);
}
/**
* Returns a new thread safe {@link Pool} that never returns {@code null} from
* {@link Pool#acquire()} and that contains objects of the type created by the given
* {@link Factory} with the given maximum size.
*
* <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will
* be used to create a new instance.
*
* @param <T> The type of object the pool will contains.
*/
@NonNull
public static <T extends Poolable> Pool<T> threadSafe(int size, @NonNull Factory<T> factory) {
return build(new SynchronizedPool<T>(size), factory);
}
/**
* Returns a new {@link Pool} that never returns {@code null} and that contains {@link List Lists}
* of a specific generic type with a standard maximum size of 20.
*
* <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be
* created.
*
* @param <T> The type of object that the {@link List Lists} will contain.
*/
@NonNull
public static <T> Pool<List<T>> threadSafeList() {
return threadSafeList(DEFAULT_POOL_SIZE);
}
/**
* Returns a new thread safe {@link Pool} that never returns {@code null} and that contains
* {@link List Lists} of a specific generic type with the given maximum size.
*
* <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be
* created.
*
* @param <T> The type of object that the {@link List Lists} will contain.
*/
// Public API.
@SuppressWarnings("WeakerAccess")
@NonNull
public static <T> Pool<List<T>> threadSafeList(int size) {
return build(new SynchronizedPool<List<T>>(size), new Factory<List<T>>() {
@NonNull
@Override
public List<T> create() {
return new ArrayList<>();
}
}, new Resetter<List<T>>() {
@Override
public void reset(@NonNull List<T> object) {
object.clear();
}
});
}
@NonNull
private static <T extends Poolable> Pool<T> build(@NonNull Pool<T> pool,
@NonNull Factory<T> factory) {
return build(pool, factory, FactoryPools.<T>emptyResetter());
}
@NonNull
private static <T> Pool<T> build(@NonNull Pool<T> pool, @NonNull Factory<T> factory,
@NonNull Resetter<T> resetter) {
return new FactoryPool<>(pool, factory, resetter);
}
@NonNull
@SuppressWarnings("unchecked")
private static <T> Resetter<T> emptyResetter() {
return (Resetter<T>) EMPTY_RESETTER;
}
/**
* Creates new instances of the given type.
*
* @param <T> The type of Object that will be created.
*/
public interface Factory<T> {
T create();
}
/**
* Resets state when objects are returned to the pool.
*
* @param <T> The type of Object that will be reset.
*/
public interface Resetter<T> {
void reset(@NonNull T object);
}
/**
* Allows additional verification to catch errors caused by using objects while they are in
* an object pool.
*/
public interface Poolable {
@NonNull
StateVerifier getVerifier();
}
private static final class FactoryPool<T> implements Pool<T> {
private final Factory<T> factory;
private final Resetter<T> resetter;
private final Pool<T> pool;
FactoryPool(@NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {
this.pool = pool;
this.factory = factory;
this.resetter = resetter;
}
@Override
public T acquire() {
T result = pool.acquire();
if (result == null) {
result = factory.create();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Created new " + result.getClass());
}
}
if (result instanceof Poolable) {
((Poolable) result).getVerifier().setRecycled(false /*isRecycled*/);
}
return result;
}
@Override
public boolean release(@NonNull T instance) {
if (instance instanceof Poolable) {
((Poolable) instance).getVerifier().setRecycled(true /*isRecycled*/);
}
resetter.reset(instance);
return pool.release(instance);
}
}
}
主要看内部静态类FactoryPool,实现了Pool接口。其实说白了,就是复用了Pool的acquire和release方法,对真正的对象池进行了包装,这样我们就可以灵活的做自己想做的事了,比如,在acquire的采用外部传进来的工厂类创建对象,同时可以更新对象自身状态。这种包装技术在实际开发中很常用。
我们看看FactoryPool是怎么使用的
static class EngineJobFactory {
@Synthetic final GlideExecutor diskCacheExecutor;
@Synthetic final GlideExecutor sourceExecutor;
@Synthetic final GlideExecutor sourceUnlimitedExecutor;
@Synthetic final GlideExecutor animationExecutor;
@Synthetic final EngineJobListener listener;
@Synthetic final Pools.Pool<EngineJob<?>> pool =
FactoryPools.threadSafe(
JOB_POOL_SIZE,
new FactoryPools.Factory<EngineJob<?>>() {
@Override
public EngineJob<?> create() {
return new EngineJob<>(
diskCacheExecutor,
sourceExecutor,
sourceUnlimitedExecutor,
animationExecutor,
listener,
pool);
}
});
EngineJobFactory(
GlideExecutor diskCacheExecutor,
GlideExecutor sourceExecutor,
GlideExecutor sourceUnlimitedExecutor,
GlideExecutor animationExecutor,
EngineJobListener listener) {
this.diskCacheExecutor = diskCacheExecutor;
this.sourceExecutor = sourceExecutor;
this.sourceUnlimitedExecutor = sourceUnlimitedExecutor;
this.animationExecutor = animationExecutor;
this.listener = listener;
}
@VisibleForTesting
void shutdown() {
Executors.shutdownAndAwaitTermination(diskCacheExecutor);
Executors.shutdownAndAwaitTermination(sourceExecutor);
Executors.shutdownAndAwaitTermination(sourceUnlimitedExecutor);
Executors.shutdownAndAwaitTermination(animationExecutor);
}
@SuppressWarnings("unchecked")
<R> EngineJob<R> build(
Key key,
boolean isMemoryCacheable,
boolean useUnlimitedSourceGeneratorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache) {
EngineJob<R> result = Preconditions.checkNotNull((EngineJob<R>) pool.acquire());
return result.init(
key,
isMemoryCacheable,
useUnlimitedSourceGeneratorPool,
useAnimationPool,
onlyRetrieveFromCache);
}
}
FactoryPool是被一个工厂对象应用的,这个工厂对象在Glide中是一个单例,只有这样对象池才有意义。EngineJob的acquire我们看到了,释放(回收)在哪里呢?FactoryPool传给了EngineJob,当EngineJob执行完自己的工作,就会释放
private synchronized void release() {
if (key == null) {
throw new IllegalArgumentException();
}
cbs.clear();
key = null;
engineResource = null;
resource = null;
hasLoadFailed = false;
isCancelled = false;
hasResource = false;
decodeJob.release(/*isRemovedFromQueue=*/ false);
decodeJob = null;
exception = null;
dataSource = null;
pool.release(this);
}
SingleRequest也使用了对象池,也很简单,SingleRequest里面有个静态变量是FactoryPool。
Glide中Bitmap对象池地key,也采用了对象池,对象池基础数据结构是队列,看如下代码
package com.bumptech.glide.load.engine.bitmap_recycle;
import com.bumptech.glide.util.Util;
import java.util.Queue;
abstract class BaseKeyPool<T extends Poolable> {
private static final int MAX_SIZE = 20;
private final Queue<T> keyPool = Util.createQueue(MAX_SIZE);
T get() {
T result = keyPool.poll();
if (result == null) {
result = create();
}
return result;
}
public void offer(T key) {
if (keyPool.size() < MAX_SIZE) {
keyPool.offer(key);
}
}
abstract T create();
}
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);
}
}
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;
}
}