Android葵花宝典程序猿葵花宝典性能优化

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

2019-06-09  本文已影响1454人  lijiankun24
catalog.png

一. 概述

有时候 UI 卡顿是因为发生了频繁的 GC 造成的,频繁的 GC 其实对应的内存情况就是内存抖动,而发生内存抖动一般都是因为在循环里面或者频繁被调用的方法(比如 View.onDraw() 方法)里面创建对象造成的,面对这种情况,一般有两种做法:

  1. 避免在循环里面或者频繁被调用的方法里面创建对象,在循环或者被频繁调用的方法之外创建对象
  2. 使用对象池缓存对象,在使用对象的时候,直接从对象池中取对象即可,避免频繁的创建对象

一般来讲,实现对象池有三种方法,对应的具体的数据结构即是数组、链表和 Map,下面我将通过示例分别介绍通过这三种数据结构实现的对象池

二. 对象池的实现

2.1 通过数组实现

在 Android 的 v4 包中有个类 Pools,其中定义了一个对象池接口 Pool<T>,具体的实现有两个 SimplePool<T>SynchronizedPool<T>,这两个实现顾名思义,一个是非线程安全的,另一个是线程安全的。
Pools 类总共代码也不长,加上注释不到 200 行,接下来看下代码

2.1.1 接口定义

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

    /**
     * Interface for managing a pool of objects.
     *
     * @param <T> The pooled type.
     */
    public interface Pool<T> {

        /**
         * @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<T> 接口的非线程安全的实现类 SimplePool<T>,代码如下
其中声明了两个类变量,数组 Object[] mPool 用于缓存对象,这也应证了我之前提到的在 Pools 中是使用数组实现对象池的;另一个变量 int mPoolSize 表示此时对象池中缓存了多少个对象
下面的 acquire()release(@NonNull T instance) 方法实现也不难,我在注释中加以说明即可

    public static class SimplePool<T> implements Pool<T> {
        private final Object[] mPool;

        private int mPoolSize;

        /**
         * SimplePool 的构造方法,参数 maxPoolSize 是数组 mPool 的长度,也表示此对象池最大可以缓存多少个对象,如果 maxPoolSize <= 0 则会抛出异常
         */
        public SimplePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            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 < mPool.length) {
                mPool[mPoolSize] = instance;
                mPoolSize++;
                return true;
            }
            return false;
        }

        /**
         * 通过遍历数组 mPool 的方式,判断参数 instance 对象是否已经在对象池中,这个方法是 private 的,只在 release(@NonNull T instance) 方法中用到了
         */
        private boolean isInPool(@NonNull T instance) {
            for (int i = 0; i < mPoolSize; i++) {
                if (mPool[i] == instance) {
                    return true;
                }
            }
            return false;
        }
    }

2.1.3 线程安全类

线程安全的实现类是非线程安全的实现类 SimplePool<T> 的子类,只是在构造方法、acquire()release(@NonNull T element) 方法中,通过 synchronized 加锁实现线程安全的,没有太多可说的,代码如下所示

    public static class SynchronizedPool<T> extends SimplePool<T> {
        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<T>SynchronizedPool<T> 这两个实现类使用的,使用也很简单。他们都有泛型参数 T,所以可以充当任意对象的对象池,下面以 Pools 中的注释示例代码说明

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

    . . .

}

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 对象池相关的方法和变量。

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 < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
}

好了,通过链表的方式实现对象池的例子就介绍到这里,接下来接着介绍通过 Map 实现对象池的例子

2.3 Glide 中通过 Map 实现 BitmapPool

通过 Map 的方式实现对象池,这里用 Glide 中缓存 BitmapBitmapPool 说明。

大家都知道 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 的实现类:BitmapPoolAdapterLruBitmapPool

2.3.2.1 LruPoolStrategy

LruPoolStrategy 是针对 LruBitmapPool 类定义的一个策略接口,其中包括一些存放、获取、移除 Bitmap 的方法,具体如下所示

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 接口有三个实现类,分别是 AttributeStrategySizeConfigStrategySizeStrategy 类,这三个类是通过不同维度的条件缓存 Bitmap 的,具体如下

可能有人会有疑问了,在 BitmapPoolLruPoolStrategy 接口中定义的都是 Bitmap get(int width, int height, Bitmap.Config config) 方法,传入的都是 width、height、config 三个条件,为什么可以通过不同的条件缓存 Bitmap 对象呢?

2.3.2.2 Key & KeyPool

这个时候就需要引入 KeyKeyPool 这两个类了。

BitmapPoolLruPoolStrategy 都只是接口定义,其底层的实现类其实都是使用了 Map 数据结构。大家都知道 Map 是以 <K, V> 键值对的形式在 K 和 V 之间形成一一映射的,我们的 V 不用多说,自然就是 Bitmap 对象了,而 K 的话就可以是不同形式的了。在 Glide 中,具体的 K 是 Key 这样的一个类,而为了避免每次存放、获取 Bitmap 对象而新建 Key 对象,引入了 KeyPool 的概念。

Glide 中,LruPoolStrategy 有三个不同的实现类 AttributeStrategySizeConfigStrategySizeStrategy,他们是通过不同维度的条件缓存 Bitmap,这三个不同维度的条件具体就体现在它们各自内部的实现类 Key 上。

AttributeStrategy 为例,AttributeStrategy 是通过 Bitmap 的 width、height、config 三个条件缓存 Bitmap 对象,所以 AttributeStrategy 内部类 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 了,有三种缓存策略,所以也有三种 KeyKeyPool 了。

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

abstract class BaseKeyPool<T extends Poolable> {
  private static final int MAX_SIZE = 20;
  private final Queue<T> 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() < MAX_SIZE) {
      keyPool.offer(key);
    }
  }

  // 因为不同的缓存对象有不同形式的新建方式,所以 create() 方法需要是抽象的
  abstract T create();
}

介绍完 BaseKeyPool 基类以后,接着介绍一下在 AttributeStrategy 中应用的 KeyPool 类,其定义如下

class AttributeStrategy implements LruPoolStrategy {

  ......

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

  ......

}
2.3.2.2 AttributeStrategy

其实介绍完 AttributeStrategy.KeyPoolAttributeStrategy.Key 以后,AttributeStrategy 剩下的代码也不多了,其代码如下

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

  ......

  @VisibleForTesting
  static class KeyPool extends BaseKeyPool<Key> {
    ......
  }

  @VisibleForTesting
  static class Key implements Poolable {
        ......
  }
}
2.3.2.3 LruBitmapPool

上面介绍了 LruBitmapPool 相关的几个非常重要的概念:LruPoolStrategyKeyKeyPool,以及 LruPoolStrategy 实现类 AttributeStrategy,如果对上面介绍的内容都理解了,那么理解 LruBitmapPool 就更容易一些了

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<Bitmap.Config> 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<Bitmap.Config> 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<K extends Poolable, V> 实现 LRU 算法

LruPoolStrategy 是缓存策略接口,具体的实现类有 AttributeStrategySizeConfigStrategySizeStrategy,这三个类是通过不同的条件来缓存 Bitmap 的,底层具体的实现都使用了 GroupedLinkedMap<K extends Poolable, V> 数据结构,而 GroupedLinkedMap<K extends Poolable, V> 实现了 LRU 算法,现在就详细介绍下它是如何实现 LRU 算法的

GroupedLinkedMap<K extends Poolable, V> 的类定义如下所示,其中有两个非常重要的属性 Map<K, LinkedEntry<K, V>> keyToEntryLinkedEntry<K, V> head

GroupedLinkedMap.png

LinkedEntry<K, V>GroupedLinkedMap<K extends Poolable, V> 的一个内部类,代码实现其实并不复杂

class GroupedLinkedMap<K extends Poolable, V> {

  ......

  private static class LinkedEntry<K, V> {
    @Synthetic final K key;
    private List<V> values;
    LinkedEntry<K, V> next;
    LinkedEntry<K, V> 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<>();
      }
      values.add(value);
    }
  }
}

介绍完 LinkedEntry<K, V> 之后,再接着介绍一下 GroupedLinkedMap<K extends Poolable, V> 中非常重要的几个方法,如下所示

class GroupedLinkedMap<K extends Poolable, V> {
  private final LinkedEntry<K, V> head = new LinkedEntry<>();
  private final Map<K, LinkedEntry<K, V>> keyToEntry = new HashMap<>();

  public void put(K key, V value) {
    LinkedEntry<K, V> entry = keyToEntry.get(key);

    if (entry == null) {
      entry = new LinkedEntry<>(key);
      makeTail(entry);
      keyToEntry.put(key, entry);
    } else {
      key.offer();
    }

    entry.add(value);
  }

  @Nullable
  public V get(K key) {
    LinkedEntry<K, V> entry = keyToEntry.get(key);
    if (entry == null) {
      entry = new LinkedEntry<>(key);
      keyToEntry.put(key, entry);
    } else {
      key.offer();
    }

    makeHead(entry);

    return entry.removeLast();
  }

  @Nullable
  public V removeLast() {
    LinkedEntry<K, V> 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<K extends Poolable, V> 的示意图,相信对理解双向链表、Map、以及 List 实现 GroupedLinkedMap<K extends Poolable, V> 的方式会有所帮助

GroupedLinkedMap1.png

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

2.3.4 Glide 分析小结

最后,再贴一张 Glide 中 BitmapPool 的类关系图,相信对理解 Glide 中的 BitmapPool 知识会有所帮助

Glide.png

拆 Glide 系列之 - Bitmap 复用

三. 总结

本文从数据结构的角度,分别介绍了对象池的实现方式,数组、链表、Map 都可以实现对象池,可以根据不同的应用场景灵活地使用具体的数据结构来实现对象池。在 Glide 中应用的可以说是淋漓尽致,单说 BitmapPool 的实现就非常让人称赞了。
对于对象池,个人认为有以下几点是非常需要注意的

介绍了这么多关于对象池的内容,其实最终的目的都是为了避免频繁的创建和回收对象,从而保持内存的稳定。在优化内存时,对象池真是一个非常重要的武器。

四. 参考

拆 Glide 系列之 - Bitmap 复用
Glide 源码分析解读-缓存模块-基于最新版Glide 4.9.0
对象池Pools优化

上一篇下一篇

猜你喜欢

热点阅读