Glide中优秀代码总结

Glide单例初始化

 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
private static volatile Glide glide;
public static Glide get(@NonNull Context context) {
  if (glide == null) {
    GeneratedAppGlideModule annotationGeneratedModule =
        getAnnotationGeneratedGlideModules(context.getApplicationContext());
    synchronized (Glide.class) {
      if (glide == null) {
        checkAndInitializeGlide(context, annotationGeneratedModule);
      }
    }
  }
  return glide;
}

private static volatile boolean isInitializing;
static void checkAndInitializeGlide(
    @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) {
  if (isInitializing) {
    throw new IllegalStateException(
        "Glide has been called recursively, this is probably an internal library error!");
  }
  isInitializing = true;
  try {
    initializeGlide(context, generatedAppGlideModule);
  } finally {
    isInitializing = false;
  }
}

上面Glide对象初始化使用DCL双重锁判断,并且checkAndInitializeGlide方法中使用了isInitializing变量表示是否正在初始化。这个isInitializing变量解决单线程递归调用Glide.get方法会创建多个Glide实例。因为在DCL模式中,单线程是能重入synchronized锁的。为了避免单线程能多次创建Glide实例,所以加了标志表示是否正在加载。

Builder建造者模式

建造者模式是属于创建型中的一种,用于创建复杂对象,将对象的构建与表示分离,以便相同的构建过程可以创建不同的表示。

  • GlideBuilder
    • 用于创建Glide对象,因为Glide对象比较复杂,所以将Glide的创建交给了GlideBuilder,同样的构建过程,可以表示不同的Glide对象。比如指定sourceExecutor(加载网络资源的线程池)、diskCacheExecutor(加载本地磁盘缓存的线程池)等。
  • RequestBuilder
    • 它是继承自BaseRequestOptions,因此在构建request过程中,它是充当了builder创建模式来创建request,比如配置override大小、placeholder等。

原型模式

RequestBuilder的clone

在RequestBuilder中的into方法里面每次先clone一份RequestOptions出来:

 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
class RequestBuilder{
    public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {

        BaseRequestOptions<?> requestOptions = this;
        if (!requestOptions.isTransformationSet()
            && requestOptions.isTransformationAllowed()
            && view.getScaleType() != null) {
            switch (view.getScaleType()) {
            case CENTER_CROP:
                requestOptions = requestOptions.clone().optionalCenterCrop();
                break;
            case CENTER_INSIDE:
                requestOptions = requestOptions.clone().optionalCenterInside();
                break;
            case FIT_CENTER:
            case FIT_START:
            case FIT_END:
                requestOptions = requestOptions.clone().optionalFitCenter();
                break;
            case FIT_XY:
                requestOptions = requestOptions.clone().optionalCenterInside();
                break;
            case CENTER:
            case MATRIX:
            default:
            }
        }

        return into(
            glideContext.buildImageViewTarget(view, transcodeClass),
            /* targetListener= */ null,
            requestOptions,
            Executors.mainThreadExecutor());
    }
    
    public T clone() {
        try {
            BaseRequestOptions<?> result = (BaseRequestOptions<?>) super.clone();
            result.options = new Options();
            result.options.putAll(options);
            result.transformations = new CachedHashCodeArrayMap<>();
            result.transformations.putAll(transformations);
            result.isLocked = false;
            result.isAutoCloneEnabled = false;
            return (T) result;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

requestOptions通过clone方法构造一个新的BaseRequestOptions,原型模式的作用是通过已有的对象属性构建一个新的对象,好处是创建新对象时复用已有配置,避免重复设置。新对象与原对象相互独立,互不影响。此处好处有如下几点:

  • 支持 RequestBuilder 复用
    • RequestBuilder是可以复用的,因为load方法会返回requestBuilder,而into可以作用到不同的imageView上,如果先作用到imageView1上,它的scaleType是fitCenter,而后又作用到imageView2上,此时imageView是centerCrop模式展示。如果没有clone的话,imageView2加载完后会影响到imageView1的展示。因为scaleType的不同会给requestOptions设置strategy和transformation不同。
  • 防止后续修改影响已发起的请求
    • 还是拿两个图片共用一个RequestBuilder来说,比如imageView1需要通过requestOptions设置override(100,100)的大小,此时imageView1还在请求,而此时imageView2通过requestOptions设置override(200,200),那么此时如果没有clone出一个新的requestOptions会导致imageView1加载的大小不对应。
  • 多线程安全问题
    • into方法如果在不同的线程被调用,如果没有clone出一个新的requestOptions,那么会存在线程安全问题。

工厂模式

DiskCache的创建

glide中使用的工厂模式分为工厂方法模式和抽象工厂模式,首先看下哪些地方用到了工厂方法模式:

  • DiskCache的创建
    • 在GlideBuilder中默认初始化了DiskCache.Factory,他其实是一个InternalCacheDiskCacheFactory:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public interface DiskCache {
  interface Factory {
    @Nullable
    DiskCache build();
  }
}  
public final class GlideBuilder {
  private DiskCache.Factory diskCacheFactory;
  Glide build(){
    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }
  }
}

接着将diskCacheFactory给到Engine,在Engine中会将diskCacheFactory给到LazyDiskCacheProvider,它是实现了DecodeJob.DiskCacheProvider接口,可以看出来它是在DecodeJob中需要使用到该DiskCache.Factory,我们设计接口的时候,需要将接口保持最小范围,如果只在外部类中使用该接口,将接口定义在外部类中,体现了设计原则中的封装原则,从而减少对外暴露。第二点体现了高内聚原则,将相关的接口和类放在一起,提高内聚性。

1
2
3
4
5
class DecodeJob{
  interface DiskCacheProvider {
    DiskCache getDiskCache();
  }
}

并且LazyDiskCacheProvider的定义是在Engine中定义,这也体现了高内聚和封装原则,不仅是接口,类与类之间也能体现出来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Engine{
  private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
    private final DiskCache.Factory factory;
    private volatile DiskCache diskCache;
    LazyDiskCacheProvider(DiskCache.Factory factory) {
      this.factory = factory;
    }
    @Override
    public DiskCache getDiskCache() {
      if (diskCache == null) {
        synchronized (this) {
          if (diskCache == null) {
            diskCache = factory.build();
          }
          if (diskCache == null) {
            diskCache = new DiskCacheAdapter();
          }
        }
      }
      return diskCache;
    }
  }
}

LazyDiskCacheProvider从字面意思也能看出来,它是一个延迟获取DiskCache的封装类。如果没有它,那就得在Glide初始化的时候创建DiskCache了。因为DiskCache是一个重量级的类,里面涉及到文件目录的创建等。并且在LazyDiskCacheProvider中保证了线程安全来创建DiskCache。

RequestOptions的创建

  • 它也是通过工厂方法模式来创建的:
1
2
3
4
5
6
class Glide{
  public interface RequestOptionsFactory {
    @NonNull
    RequestOptions build();
  }
}

它是在Glide外部类中定义的接口,也是和上面类似,再来看下它的接口实现类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class GlideBuilder{
  private RequestOptionsFactory defaultRequestOptionsFactory =
      new RequestOptionsFactory() {
        @NonNull
        @Override
        public RequestOptions build() {
          return new RequestOptions();
        }
      };
}

在Glide创建的时候,直接把defaultRequestOptionsFactory传给了Glide对象,也没有判断外界有没有传入RequestOptionsFactory,可以看出来它无需外界进行扩展,这点和DiskCache.Factory不太一样。由于RequestOptions是一个轻量级的,所以它也无需延迟初始化,它是在RequestManager初始化中调用GlideContext的getDefaultRequestOptions方法来创建的:

1
2
3
4
5
6
7
8
class GlideContext{
  public synchronized RequestOptions getDefaultRequestOptions() {
    if (defaultRequestOptions == null) {
      defaultRequestOptions = defaultRequestOptionsFactory.build().lock();
    }
    return defaultRequestOptions;
  }
}

Registry的创建

  • Registry是通过GlideSupplier接口创建的,也是一个工厂方法模式:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class GlideSuppliers {
  public interface GlideSupplier<T> {
    T get();
  }

  private GlideSuppliers() {}
  public static <T> GlideSupplier<T> memorize(final GlideSupplier<T> supplier) {
    return new GlideSupplier<T>() {
      private volatile T instance;
      @Override
      public T get() {
        if (instance == null) {
          synchronized (this) {
            if (instance == null) {
              instance = Preconditions.checkNotNull(supplier.get());
            }
          }
        }
        return instance;
      }
    };
  }
}

也是一个类中内部接口,不过该接口是一个泛型。外界需要调用memorize方法,并传入一个GlideSupplier的实现类,在前面介绍Registry创建过程中,它是在GlideContext的getRegistry方法进行获取的,而默认是调用memorize方法获取到GlideSupplier对象,等到需要Registry时候才会调用GlideSupplier的get方法,显而易见这是一个延迟初始化,并且这里使用了静态代理,将Registry的最终获取交给了外界传入的Registry。同时匿名内部类的GlideSupplier通过双重锁保证了线程安全,这里其实和上面的DiskCache的创建有点异曲同工之妙。

ModelLoader的创建

  • ModelLoader是通过ModelLoaderFactory接口来创建的:
1
2
3
4
public interface ModelLoaderFactory<T, Y> {
  @NonNull
  ModelLoader<T, Y> build(@NonNull MultiModelLoaderFactory multiFactory);
}

在RegistryFactory中先把modelClass、dataClass、ModelLoaderFactory注册到MultiModelLoaderFactory中,组转成一个个的Entry对象,等到要使用ModelLoader时候,通过modelClass和dataClass过滤出ModelLoaderFactory,然后通过ModelLoaderFactory创建出ModelLoader,接着获取到LoadData,最后获取到DataFetcher。 在MultiModelLoader中装载了ModelLoader的集合,包含了多个子节点,在获取LoadData的时候使用了责任链模式,依次让每个ModelLoader尝试进行获取LoadData,同时这里也体现出了迭代器模式。

DataRewinder的创建

  • DataRewinder是一个数据重置器,它也是通过方法工厂来创建的
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public interface DataRewinder<T> {

  interface Factory<T> {
    @NonNull
    DataRewinder<T> build(@NonNull T data);

    @NonNull
    Class<T> getDataClass();
  }

  @NonNull
  T rewindAndGet() throws IOException;
}

也是在外部接口中定义内部接口。在前面介绍InputStream转化成File的时候讲过,先把各种DataRewinder.Factory注册到DataRewinderRegistry中,然后通过传入的class类型获取到对应的DataRewinder.Factory,接着通过build方法获取到DataRewinder。

Encoder的获取

Encoder是编码器的接口类,Encoder的创建虽然没有用到Factory,但是它是和上面DataRewinder的创建形成了对比,Encoder它是直接在Registry阶段直接包装成Entry,然后在获取的时候直接从Entry中获取到Encoder。 Encoder为什么它不需要Factory创建呢?而DataRewinder需要通过Factory来创建?

特性 Encoder DataRewinder
状态 无状态 有状态
数据依赖 数据通过方法参数传入 数据在构造时注入
实例复用 ✅ 可复用,一个实例处理多个请求 ❌ 每次需要新实例
生命周期 应用级别,长期存活 请求级别,用完释放
资源管理 不需要 cleanup 需要 cleanup() 释放资源

比如在前面讲的将原始图片保存到本地的时候说过StreamEncoder:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class StreamEncoder implements Encoder<InputStream>{
  public boolean encode(@NonNull InputStream data, @NonNull File file, @NonNull Options options) {
    byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    boolean success = false;
    OutputStream os = new FileOutputStream(file);
    int read;
    while ((read = data.read(buffer)) != -1) {
      os.write(buffer, 0, read);
    }
    os.close();
    success = true;
    byteArrayPool.put(buffer);
    return success;
  }
}

它不持有数据,所以是无状态的。数据是通过方法参数传入。并且StreamEncoder是应用级别的,它无需每次进行创建。所以它无需通过factory进行延迟初始化。

而在DataRewinder中它是需要持有数据的,比如前面讲的InputStreamRewinder:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public final class InputStreamRewinder implements DataRewinder<InputStream> {
  // 5MB.
  private static final int MARK_READ_LIMIT = 5 * 1024 * 1024;

  private final RecyclableBufferedInputStream bufferedStream;

  @Synthetic
  public InputStreamRewinder(InputStream is, ArrayPool byteArrayPool) {
    bufferedStream = new RecyclableBufferedInputStream(is, byteArrayPool);
    bufferedStream.mark(MARK_READ_LIMIT);
  }

  @NonNull
  @Override
  public InputStream rewindAndGet() throws IOException {
    bufferedStream.reset();
    return bufferedStream;
  }
}

它需要持有数据,因为它需要多次对流进行重置位置。并且每次获取图片都需要创建一个新的DataRewinder,所以它需要通过factory来创建不同的DataRewinder,因为它是跟数据绑定在一块的,因此用到了延迟初始化。

StreamEncoder中的ArrayPool使用: 在StreamEncoder中,byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class)这行代码非常关键。如果不使用ArrayPool,每次encode操作都会:

  1. 创建新的byte数组new byte[ArrayPool.STANDARD_BUFFER_SIZE_BYTES],这会在堆上分配内存
  2. 增加GC压力:每次encode完成后,这个byte数组就会成为垃圾,需要GC回收
  3. 内存抖动:频繁的内存分配和回收会导致内存抖动,影响性能

使用ArrayPool的好处:

  • 内存复用:从池中获取已存在的byte数组,避免重复创建
  • 减少GC:用完后再放回池中,不会被当作垃圾回收
  • 性能提升:减少了内存分配和回收的开销
  • 线程安全:ArrayPool内部实现了线程安全的获取和归还机制

如果不使用ArrayPool会怎样?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 不使用ArrayPool的版本
public boolean encode(@NonNull InputStream data, @NonNull File file, @NonNull Options options) {
    byte[] buffer = new byte[ArrayPool.STANDARD_BUFFER_SIZE_BYTES]; // 每次都创建新数组
    boolean success = false;
    OutputStream os = new FileOutputStream(file);
    int read;
    while ((read = data.read(buffer)) != -1) {
        os.write(buffer, 0, read);
    }
    os.close();
    success = true;
    // buffer这里会成为垃圾,等待GC回收
    return success;
}

在高并发场景下,这种实现会导致大量的内存分配和GC,严重影响性能。

解码(静态代理)

  • 首先了解几个概念,dataClass:数据源类型,比如从网络获取的数据类型是byteBuffer.class类型。resourceClass:解码后的资源类型,比如bitmap的解码,它是BitmapDrawable.class,也就是bitmap的drawable包装。transcodeClass:最终转换类型,比如要展示到imageView上的时候,此时是Drawable.class类型。
  • 在注册的时候,会按照bucket、dataClass、resourceClass、decoder四个参数组转成Entry,并且该Entry是定义在ResourceDecoderRegistry的私有内部类中。保证高内聚,低耦合。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private static class Entry<T, R> {
  private final Class<T> dataClass;
  @Synthetic final Class<R> resourceClass;
  @Synthetic final ResourceDecoder<T, R> decoder;
  public Entry(
      @NonNull Class<T> dataClass,
      @NonNull Class<R> resourceClass,
      ResourceDecoder<T, R> decoder) {
    this.dataClass = dataClass;
    this.resourceClass = resourceClass;
    this.decoder = decoder;
  }
  public boolean handles(@NonNull Class<?> dataClass, @NonNull Class<?> resourceClass) {
    return this.dataClass.isAssignableFrom(dataClass)
        && resourceClass.isAssignableFrom(this.resourceClass);
  }
}

最终将这些Entry装载到ResourceDecoderRegistry的decoders这个map中。比如在前面介绍的BitmapDrawableDecoder,它就是按照bucket= “BitmapDrawable”,dataClass=“ByteBuffer.class”,resourceClass=“BitmapDrawable.class”,decoder=BitmapDrawableDecoder组成的entry来获取的。并且在BitmapDrawableDecoder中使用了代理模式,实际代理到ByteBufferBitmapDecoder,最终会调用到Downsampler中。

策略模式

DiskCacheStrategy — 磁盘缓存策略

DiskCacheStrategy代表的是硬盘缓存策略,先看下DiskCacheStrategy抽象类有哪几个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public abstract class DiskCacheStrategy {
  //是否可以存储原始图片到磁盘中
  public abstract boolean isDataCacheable(DataSource dataSource);
  //是否可以存储解码后的图片到磁盘中
  public abstract boolean isResourceCacheable(
    boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);
  //是否可以获取磁盘中解码后的图片缓存
  public abstract boolean decodeCachedResource();
  //是否可以获取磁盘中原始图片缓存
  public abstract boolean decodeCachedData();
}

其中DiskCacheStrategy的子类有5种,它们都定义在DiskCacheStrategy抽象类中。分别是ALL、NONE、DATA、RESOURCE、AUTOMATIC这5种策略。拿ALL来说:

 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
public static final DiskCacheStrategy ALL =
    new DiskCacheStrategy() {
      @Override
      public boolean isDataCacheable(DataSource dataSource) {
        //如果图片来自于网络则直接存储原始图片到磁盘中
        return dataSource == DataSource.REMOTE;
      }

      @Override
      public boolean isResourceCacheable(
          boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
            //如果图片不是来自解码后的磁盘缓存并且不是来自内存缓存则可以存储解码后的图片到磁盘中
        return dataSource != DataSource.RESOURCE_DISK_CACHE
            && dataSource != DataSource.MEMORY_CACHE;
      }

      @Override
      public boolean decodeCachedResource() {
        //可以获取磁盘中解码后的图片缓存
        return true;
      }

      @Override
      public boolean decodeCachedData() {
        //可以获取磁盘中原始图片缓存
        return true;
      }
    };

glide中默认使用的策略是AUTOMATIC。它和上面的ALL区别是isResourceCacheable方法,也就是是否存储解码后的图片到磁盘中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static final DiskCacheStrategy AUTOMATIC =
    new DiskCacheStrategy() {
      @Override
      public boolean isResourceCacheable(
          boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
        return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
                || dataSource == DataSource.LOCAL)
            && encodeStrategy == EncodeStrategy.TRANSFORMED;
      }
    }

如果是加载缩略图并且资源来自于磁盘原始数据,或者资源来自于本地资源。并且带有转换的策略,也就是会经过缩放、裁切,举例说明:

  • 本地资源+已转换
    1
    2
    3
    4
    5
    
    // 例如:加载本地图片并进行了裁剪
    Glide.with(context)
      .load(R.drawable.local_image)  // DataSource.LOCAL
      .circleCrop()                  // TRANSFORMED 转换
      .into(imageView);              // 会缓存转换后的结果
    
  • 从原始缓存生成缩略图+已转换
    1
    2
    3
    4
    5
    6
    
    // 缩略图场景,从已缓存的原始数据生成
    Glide.with(context)
      .load(url)
      .thumbnail(0.1f)              // 可能触发 isFromAlternateCacheKey
      .circleCrop()                  // TRANSFORMED 转换
      .into(imageView);
    
  • 比如加载网络的资源就不缓存解码后的图片:
    1
    2
    3
    4
    
    // 网络图片默认不会缓存转换后的结果
    Glide.with(context)
      .load("http://example.com/image.jpg")  // DataSource.REMOTE
      .into(imageView);                      // 不缓存转换后结果
    
  • 加载本地资源的时候,未经转换:
    1
    2
    3
    4
    5
    
    // 本地图片但没有转换操作
    Glide.with(context)
      .load(R.drawable.local_image)  // DataSource.LOCAL
      // 没有转换操作,encodeStrategy 可能不是 TRANSFORMED
      .into(imageView);              // 可能不缓存
    

Transformation自带的transform也是一种策略体现

1
2
3
4
public interface Transformation<T> extends Key {
  Resource<T> transform(
      @NonNull Context context, @NonNull Resource<T> resource, int outWidth, int outHeight);
}

在RequestBuilder的into方法中构造不同的Transformation,有CenterCrop、CenterInside、FitCenter这几类都是属于Transformation接口。

LruPoolStrategy — 内存池策略

LruPoolStrategy是Glide中用于管理Bitmap对象池的策略接口,它实现了LRU(Least Recently Used)算法的不同实现方式,让LruBitmapPool能够灵活切换不同的内存管理策略:

1
2
3
4
5
6
7
// LruPoolStrategy接口定义
interface LruPoolStrategy {
  void put(Bitmap bitmap);
  Bitmap get(int width, int height, Bitmap.Config config);
  int getSize(Bitmap bitmap);
  void clear();
}

LruBitmapPool中使用了策略模式,通过LruPoolStrategy接口来管理Bitmap的存储和获取:

 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
public class LruBitmapPool implements BitmapPool {
  private final LruPoolStrategy strategy;
  private long currentSize;
  private final long maxSize;

  @Override
  public void put(Bitmap bitmap) {
    if (bitmap.getAllocationByteCount() > maxSize) {
      //如果单个bitmap大于池子的最大值,直接回收
      bitmap.recycle();
      return;
    }
    strategy.put(bitmap);
    currentSize += bitmap.getAllocationByteCount();
    //超出最大值就淘汰
    trimToSize(maxSize);
  }

  @Override
  public Bitmap getDirty(int width, int height, Bitmap.Config config) {
    Bitmap result = strategy.get(width, height, config);
    if (result == null) {
      //没有可复用的,创建新的
      result = createBitmap(width, height, config);
    }
    return result;
  }
}

SizeConfigStrategy是LruPoolStrategy的具体实现类,它按照Bitmap的字节大小和Config组合作为key进行匹配。下面详细解释SizeConfigStrategy是如何根据width、height、config找到对应的Bitmap的:

数据结构设计

SizeConfigStrategy使用了三个核心数据结构:

1
2
3
4
5
6
7
8
class SizeConfigStrategy implements LruPoolStrategy {
  private final KeyPool keyPool = new KeyPool();           // Key对象池,避免重复创建Key对象
  private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();  // LRU缓存容器
  //按照大小排序的TreeMap,用于快速查找大于等于目标大小的bitmap
  //外层Map:key是Bitmap.Config,value是该Config对应的TreeMap
  //内层TreeMap:key是Bitmap的字节大小(Integer),value是数量(Integer)
  private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
}

TreeMap是按照Bitmap的字节大小(Integer)来排序的,即按照width × height × 每个像素的字节数进行升序排列。例如:40000字节 < 80000字节 < 160000字节。

为什么用GroupedLinkedMap而不是LinkedHashMap?

GroupedLinkedMap是Glide自定义的一个数据结构,它和LinkedHashMap的主要区别在于:

1. LinkedHashMap的局限性

  • LinkedHashMap只能按照插入顺序访问顺序排序
  • 它只有一条链表,所有元素都在同一条链路上
  • 当需要淘汰时,只能从链表头部(最久未使用)移除元素

2. GroupedLinkedMap的优势 — 分组LRU

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class GroupedLinkedMap<Key, Value> {
  private final LinkedHashMap<Key, Value> cache = new LinkedHashMap<>(100, 0.75f, true);
  private final Map<Key, LinkedHashMap<Key, Value>> groups = new HashMap<>();
  
  // put时:将元素放入对应的group中
  public void put(Key key, Value value) {
    // 支持分组:同一个大小的Bitmap放在同一个group中
  }
  
  // get时:更新访问顺序
  public Value get(Key key) {
    return cache.get(key);
  }
  
  // evictMostRecentlyUsed:淘汰时优先淘汰最近最少使用的
  public void evictMostRecentlyUsed() {
    // 从最久未使用的group中淘汰最久未使用的元素
  }
}

3. 核心区别:分组管理

特性 LinkedHashMap GroupedLinkedMap
分组 不支持 支持分组
淘汰策略 单一链表,全局LRU 分组内LRU
场景 通用缓存 适合多规格缓存池

4. 在SizeConfigStrategy中的实际作用

SizeConfigStrategy中使用GroupedLinkedMap的原因是:同一个size的Bitmap可能有多张(比如多次解码产生相同尺寸的Bitmap),它们应该被归为一组进行管理。

1
2
3
// 场景:池中有多个100x100的Bitmap
// GroupedLinkedMap会将它们放在同一个group中
// 淘汰时:优先淘汰最近最少使用的那个group中的元素

总结:

  • LinkedHashMap:所有元素共用一条LRU链表,淘汰时全局最久未使用的
  • GroupedLinkedMap:按Key分组,每组有独立的LRU链,淘汰时在最近最少使用的组内淘汰

这种设计更适合Bitmap对象池的场景,因为相同尺寸的Bitmap会被复用,它们应该作为一组进行LRU管理,而不是和不同尺寸的Bitmap混在一起。

查找流程解析

第一步:计算目标大小

1
2
3
int size = Util.getBitmapByteSize(width, height, config);
// 计算公式:width * height * 每个像素的字节数
// ARGB_8888 = 4字节/pixel,RGB_565 = 2字节/pixel

第二步:构建Key并查找

1
2
Key bestKey = findBestKey(size, config);
Bitmap result = groupedMap.get(bestKey);

第三步:核心查找算法findBestKey

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private Key findBestKey(int size, Bitmap.Config config) {
  // 1. 从sortedSizes中获取该Config对应的TreeMap
  NavigableMap<Integer, Integer> sizesForConfig = sortedSizes.get(config);
  
  // 2. 使用TreeMap的ceilingKey方法找到大于等于目标大小的最小key
  //    这是算法的核心,时间复杂度O(logN)
  Integer sizeKey = sizesForConfig != null ? sizesForConfig.ceilingKey(size) : null;
  
  // 3. 如果没找到,尝试从其他Config中查找(允许Config不匹配)
  if (sizeKey == null) {
    // 遍历其他Config...
  }
  
  // 4. 用找到的sizeKey构建Key对象
  Key key = keyPool.get(sizeKey, config);
  return key;
}

复用Bitmap的核心:reconfigure

1
2
3
4
if (result != null) {
  // 关键步骤:将找到的Bitmap重新配置为目标尺寸
  result.reconfigure(width, height, config);
}

为什么可以复用更大的Bitmap?

Glide的策略是:只要 Bitmap.getAllocationByteCount() >= 目标大小,就可以复用

例如:

  • 需要:100x100, ARGB_8888 = 40000字节
  • 池中有:200x200, ARGB_8888 = 160000字节
  • 结果:复用200x200的Bitmap,通过reconfigure变成100x100

这种设计的好处:

  1. 提高复用率:不严格要求尺寸完全匹配,只要够大就能复用
  2. 减少内存碎片:大Bitmap被复用后,小Bitmap可以被新请求使用
  3. 高效查找:利用TreeMap的ceilingKey以O(logN)时间复杂度找到最佳匹配

put流程:维护sortedSizes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Override
public void put(Bitmap bitmap) {
  // 1. 计算Bitmap大小
  int size = bitmap.getAllocationByteCount();
  Bitmap.Config config = bitmap.getConfig();
  
  // 2. 更新sortedSizes计数
  NavigableMap<Integer, Integer> sizesForConfig = sortedSizes.get(config);
  if (sizesForConfig == null) {
    sizesForConfig = new TreeMap<>();
    sortedSizes.put(config, sizesForConfig);
  }
  Integer count = sizesForConfig.get(size);
  sizesForConfig.put(size, count == null ? 1 : count + 1);
  
  // 3. 存入groupedMap(LRU容器)
  Key key = keyPool.get(size, config);
  groupedMap.put(key, bitmap);
}

策略模式的核心特征

  1. 定义策略接口LruPoolStrategy接口定义了put、get、clear等操作
  2. 多实现切换:可以通过实现LruPoolStrategy接口来提供不同的内存管理策略
  3. 运行时切换:LruBitmapPool可以在运行时切换不同的策略实现
  4. 解耦:将"如何存储和获取Bitmap"的算法封装在不同的策略实现中

这种设计使得Glide可以灵活地添加新的内存管理策略,而不需要修改LruBitmapPool的核心逻辑。

AttributeStrategy — 精确匹配策略

AttributeStrategyLruPoolStrategy的另一种实现,它与SizeConfigStrategy的核心区别在于:要求返回的Bitmap尺寸必须完全匹配请求的宽高。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class AttributeStrategy implements LruPoolStrategy {
  private final KeyPool keyPool = new KeyPool();
  private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();

  @Override
  public void put(Bitmap bitmap) {
    // 创建Key时使用精确的width、height、config
    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) {
    // 精确匹配:width、height、config必须完全相同
    final Key key = keyPool.get(width, height, config);
    return groupedMap.get(key);
  }
  
  // ...
}

Key的equals方法:

1
2
3
4
5
6
7
8
9
@Override
public boolean equals(Object o) {
  if (o instanceof Key) {
    Key other = (Key) o;
    // Bitmaps are matched if their width, height, and config are all exactly the same.
    return width == other.width && height == other.height && config == other.config;
  }
  return false;
}

特点:

  • 精确匹配:只有当Bitmap的width、height、config完全相同时才能复用
  • 简单直接:不需要复杂的计算和查找算法
  • 复用率低:无法复用稍大或稍小的Bitmap

SizeConfigStrategy — 尺寸灵活匹配策略

SizeConfigStrategy是Glide默认使用的策略,它的特点是:不要求尺寸完全匹配,只要字节大小足够即可复用

 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
class SizeConfigStrategy implements LruPoolStrategy {
  // 最大允许放大的倍数
  private static final int MAX_SIZE_MULTIPLE = 8;

  @Override
  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) {
      // 关键:通过reconfigure调整Bitmap尺寸
      result.reconfigure(width, height, config);
    }
    return result;
  }
  
  private Key findBestKey(int size, Bitmap.Config config) {
    // 使用TreeMap的ceilingKey找到大于等于目标大小的最小值
    Integer possibleSize = sizesForConfig.ceilingKey(size);
    // 但不能超过MAX_SIZE_MULTIPLE倍
    if (possibleSize <= size * MAX_SIZE_MULTIPLE) {
      // 找到匹配的Bitmap
    }
  }
}

特点:

  • 高复用率:可以复用比需求更大的Bitmap
  • 减少内存碎片:大Bitmap被复用后,小Bitmap可以被新请求使用
  • 高效查找:利用TreeMap的ceilingKey以O(logN)时间复杂度找到最佳匹配
  • 实现复杂:需要维护sortedSizes等额外数据结构

AttributeStrategy vs SizeConfigStrategy 核心区别

特性 AttributeStrategy SizeConfigStrategy
匹配方式 精确匹配(width、height、config完全相同) 灵活匹配(字节大小 >= 目标大小)
复用能力 只能复用完全相同尺寸的Bitmap 可以复用更大的Bitmap
内存利用率 较低,可能造成内存浪费 较高,减少内存碎片
数据结构 只需GroupedLinkedMap 需要GroupedLinkedMap + sortedSizes
查找算法 直接HashMap查找O(1) TreeMap的ceilingKey查找O(logN)
reconfigure 不需要 需要调用reconfigure调整尺寸
Config兼容性 完全相同 允许部分Config之间复用

Config兼容性详解

SizeConfigStrategy允许不同Config之间的复用,但有严格限制:

1
2
3
4
5
private static final Bitmap.Config[] ARGB_8888_IN_CONFIGS = 
    {Bitmap.Config.ARGB_8888, null};  // ARGB_8888 和 null 可以复用

private static final Bitmap.Config[] RGB_565_IN_CONFIGS = 
    {Bitmap.Config.RGB_565};  // RGB_565 只能复用RGB_565

复用规则:

  • ARGB_8888 ↔ ARGB_8888:✅ 可以
  • ARGB_8888 ↔ RGB_565:❌ 不可以(每像素字节数不同)
  • RGB_565 ↔ RGB_565:✅ 可以

何时使用哪种策略?

  1. 使用AttributeStrategy的场景:

    • 对内存布局有严格要求的场景
    • 需要确保Bitmap尺寸完全一致
    • 旧版本Glide或特定设备兼容性考虑
  2. 使用SizeConfigStrategy的场景(默认):

    • 追求更高的Bitmap复用率
    • 减少内存分配和GC压力
    • 大多数Android应用(API 19+)

Glide默认使用SizeConfigStrategy,因为它能显著提高Bitmap复用率,减少内存抖动,提升性能。

装饰者模式

装饰者模式都是在不修改原有类的结构情况下,通过装饰者类来给原来类添加功能的一种模式。装饰者和被装饰者实现同一个接口,对于客户端调用来说,它是通过装饰者类调用被装饰对象。

  • DrawableTransformation
 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
public class DrawableTransformation implements Transformation<Drawable> {
  private final Transformation<Bitmap> wrapped;
  public Resource<Drawable> transform(
      @NonNull Context context, @NonNull Resource<Drawable> resource, int outWidth, int outHeight) {
    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();
    Drawable drawable = resource.get();
    Resource<Bitmap> bitmapResourceToTransform =
        DrawableToBitmapConverter.convert(bitmapPool, drawable, outWidth, outHeight);
    if (bitmapResourceToTransform == null) {
      if (isRequired) {
        throw new IllegalArgumentException("Unable to convert " + drawable + " to a Bitmap");
      } else {
        return resource;
      }
    }
    Resource<Bitmap> transformedBitmapResource =
        wrapped.transform(context, bitmapResourceToTransform, outWidth, outHeight);

    if (transformedBitmapResource.equals(bitmapResourceToTransform)) {
      transformedBitmapResource.recycle();
      return resource;
    } else {
      return newDrawableResource(context, transformedBitmapResource);
    }
  }
}

在上面DrawableTransformation中wrapped是一个被装饰的Transformation对象,而DrawableTransformation本身它只会将Drawable类型的资源转化成Bitmap,等到被装饰的wrapped对象完成转化后,接着又将bitmap转回到drawable。DrawableTransformation是在传入Uri类型的model时候,然后解码返回的drawable类型的数据,接着通过DrawableTransformation完成transform过程。

管道模式(修正说明)

transform 可以无限叠加,比如:transform(CenterCrop(), RoundedCorners()),会将所有的 Transformation 放到 MultiTransformation 中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class MultiTransformation<T> implements Transformation<T> {
  private final Collection<? extends Transformation<T>> transformations;

  public Resource<T> transform(
      @NonNull Context context, @NonNull Resource<T> resource, int outWidth, int outHeight) {
    Resource<T> previous = resource;
    for (Transformation<T> transformation : transformations) {
      Resource<T> transformed = transformation.transform(context, previous, outWidth, outHeight);
      if (previous != null && !previous.equals(resource) && !previous.equals(transformed)) {
        previous.recycle();
      }
      previous = transformed;
    }
    return previous;
  }
}

⚠️ 重要修正: 之前将此模式误称为"责任链模式",经过深入分析,更准确的说法应该是管道模式(Pipeline Pattern)装饰器模式(Decorator Pattern)

为什么不是责任链模式? 传统责任链模式(如Java Web过滤器)的核心特征是:

  • 请求沿链传递,直到某个对象处理成功就终止
  • 有中断机制,处理成功后不再继续传递
  • 目的是"找到第一个能处理的处理器"

而Glide的Transform叠加机制:

  • 所有transform都必须执行,没有终止机制
  • 前一个transform的输出作为下一个的输入
  • 目的是"组合多个处理效果",不是"找到能处理的处理器"

管道模式的核心特征:

  1. 链式处理:每个 Transformation 依次处理前一个的输出结果
  2. 依次传递:数据像链条一样依次传递给下一个处理器
  3. 必须全量执行:所有处理器都要执行,没有中断机制
  4. 组合效果:通过多个处理阶段组合成最终效果

与责任链模式的本质区别:

特性 传统责任链模式 Glide Transform叠加
流程控制 处理成功就终止 所有处理器都执行
结果传递 可能不传递 必须依次传递
中断机制 有中断逻辑 无中断逻辑
目的 找到一个能处理的处理器 组合多个处理效果

与装饰器模式的关系: Transform叠加也符合装饰器模式的特征:

  • 动态地给资源添加额外的处理效果
  • 每个Transformation在原有资源基础上添加新的视觉效果
  • 可以嵌套多层装饰,形成复杂的组合效果

总结: Glide的Transform叠加机制体现了管道模式的思想:数据流依次通过多个处理阶段,每个阶段对数据进行处理并传递给下一阶段,所有阶段都必须执行,最终组合成完整的处理效果。这种设计使得多个图片处理效果可以灵活组合,形成复杂的视觉处理流程。

代理模式

在StringLoader中持有了另外一个ModelLoader,当uriLoader的handles方法通过后,才会去去执行uriLoader的buildLoadData方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class StringLoader<Data> implements ModelLoader<String, Data> {
  private final ModelLoader<Uri, Data> uriLoader;
  @Override
  public LoadData<Data> buildLoadData(
      @NonNull String model, int width, int height, @NonNull Options options) {
    Uri uri = parseUri(model);
    if (uri == null || !uriLoader.handles(uri)) {
      return null;
    }
    return uriLoader.buildLoadData(uri, width, height, options);
  }
}

这是一个静态代理模式的体现,在静态代码中代理类主要控制被代理类的访问权限,此处通过判断uriLoader的handles方法是否通过,如果通过的话,才会去访问uriLoader这个被代理类。这也正符合静态代理模式的特征,主要是控制被代理类的访问权限。

GlideExecutor的创建 线程池部分分为sourceExecutor和diskCacheExecutor,其中它两都可以通过外接传入进来,它两都是GlideExecutor类型。GlideExecutor的创建可以通过传入自定义的ExecutorService的,而ExecutorService又可以控制线程池的个数等参数,方便了扩展。此处也是用到了静态代理模式:

1
2
3
4
public final class GlideExecutor implements ExecutorService {
  private final ExecutorService delegate;
  //所有内部的方法都代理调用了delegate的同名方法
}

组合模式

通过根结点包含子节点来达到组合模式,其中根结点和子节点实现共同的接口。以表示整体和部分的关系,比如在MultiModelLoader中包含了ModelLoader的集合:

 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
class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {
  private final List<ModelLoader<Model, Data>> modelLoaders;
  @Override
  public LoadData<Data> buildLoadData(
      @NonNull Model model, int width, int height, @NonNull Options options) {
    Key sourceKey = null;
    int size = modelLoaders.size();
    List<DataFetcher<Data>> fetchers = new ArrayList<>(size);
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0; i < size; i++) {
      ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);
      if (modelLoader.handles(model)) {
        LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options);
        if (loadData != null) {
          sourceKey = loadData.sourceKey;
          fetchers.add(loadData.fetcher);
        }
      }
    }
    return !fetchers.isEmpty() && sourceKey != null
        ? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool))
        : null;
  }

  @Override
  public boolean handles(@NonNull Model model) {
    for (ModelLoader<Model, Data> modelLoader : modelLoaders) {
      if (modelLoader.handles(model)) {
        return true;
      }
    }
    return false;
  }
}

适配器模式

适配器模式主要是将目标类的接口适配成业务需要的接口,被适配类和适配器无需实现同一个接口,适配类持有了被适配接口引用或对象引用。

  • Target接口
 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
//适配接口
public interface Target<R> extends LifecycleListener {
  void onLoadStarted(@Nullable Drawable placeholder);
  void onLoadFailed(@Nullable Drawable errorDrawable);
  void onResourceReady(@NonNull R resource, @Nullable Transition<? super R> transition);
  void onLoadCleared(@Nullable Drawable placeholder);
}

//适配 ImageView 的基础类
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z>
    implements Transition.ViewAdapter {
      //在适配器类中持有了ImageView被适配对象
      public ImageViewTarget(ImageView view) {
        super(view);
      }

  @Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
      //子类中去调用imageView的setImageBitmap的方法
      setResourceInternal(resource);
    } else {
      maybeUpdateAnimatable(resource);
    }
  }    
}

桥接模式

桥接模式主要是将主体作为抽象和实现进行分离,这样抽象能和实现随意组合,互相不影响。在glide中使用桥接模式主要体现在ModelLoader和DataFetcher之间的联系,ModelLoader作为数据输入和输出的抽象定义,DataFetcher作为数据加载的实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public interface ModelLoader<Model, Data> {
  @Nullable
  LoadData<Data> buildLoadData(
      @NonNull Model model, int width, int height, @NonNull Options options);
  boolean handles(@NonNull Model model);
}

public interface DataFetcher<T> {
  void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);
}

他两看似没有什么联系,中间通过loadData进行关联上,loadData中持有了对应的DataFetcher,比如通过url的地址请求图片的时候通过StringLoader和HttpUrlFetcher进行组合,StringLoader中指定了modelClass是String.class,dataClass是InputStream.class,而HttpUrlFetcher则是最终实现网络请求的实现角色。通过中间的LoadData进行桥接。

线程池控制

  • sourceExecutor:它是网络请求或本地资源加载的线程池,它的线程个数控制是在4和cpu核数两个取最小值。
  • diskCacheExecutor:它是图片的原始本地硬盘缓存和解码后的本地硬盘缓存的读写所使用的线程池。它的线程个数是1。
    • 上面两个线程池都是处理io操作,网络请求的线程的个数要比硬盘缓存的个数要多。这个是因为网络请求的并发量比较大,所以要求的数量要高,并且不存在多线程并发问题。而在硬盘缓存读写的时候,并发量不会很高,而且需要要求多线程安全问题,因此只分配一个线程。
  • 线程池所用的阻塞队列是PriorityBlockingQueue队列,它是一个优先级队列,添加进来的任务会实现好Comparable接口,然后按照优先级高的先从队列中取出,然后交给线程去执行。

生命周期监听

前面讲过glide主流程加载的时候,有讲过一个lifecycle对应一个RequestManager,然后将lifecycle和RequestManager进行绑定。此lifecycle实际是一个LifecycleLifecycle对象,它实现了LifecycleObserver接口,它需要一个androidx.lifecycle.Lifecycle对象,实际将LifecycleLifecycle作为lifecycle的生命周期监听。在LifecycleObserver中持有了LifecycleListener的集合,正好RequestManager又实现了LifecycleListener接口。其实此处用到了观察者模式,LifecycleLifecycle是被观察者,LifecycleListener是观察者接口,RequestManager是具体观察者。

  1. 角色分析:
    1. LifecycleLifecycle - 被观察者/主题(Subject)
    2. LifecycleListener - 观察者接口(Observer)
    3. RequestManager - 具体观察者(Concrete Observer)
  2. 核心实现:
    1. LifecycleLifecycle 持有 LifecycleListener 集合
    2. 当生命周期方法被调用时,遍历集合通知所有监听器
    3. 多个 LifecycleListener 可以订阅同一个生命周期
  3. 观察者模式的典型特征: ✅ 一对多的依赖关系(一个 Lifecycle 对应多个 Listener) ✅ 被观察者状态改变时自动通知所有观察者 ✅ 观察者和被观察者松耦合 ✅ 支持广播通信 比如在RequestManager中收到onStop事件的时候,会调用TargetTracker的onStop方法,最终会调用了target的onStop,因为TargetTracker它是持有了requestManager中的所有target。接着会调用了RequestTracker的pauseRequests方法,接着调用到request的pause方法移除相关的监听。
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计