Glide源码解析(3) Glide的扩展

Glide源码解析(3) Glide的扩展

前言

前面一篇文章中提到了GeneratedAppGlideModule这个类,在Glide的创建方法initializeGlide中,还有manifestModules。这些都是Glide中的扩展模块,这篇文章就来分析下。

如果对Glide的扩展没啥了解的话,可以先看下官方文档说明,下面是中文版的链接:

官方文档: muyangmin.github.io/glide-docs-...

这里大概总结下Glide扩展的使用情形:

  • 使用一个或更多集成库
  • 修改 Glide 的配置(configuration)(磁盘缓存大小/位置,内存缓存大小等)
  • 扩展 Glide 的API。

下面我们就来体会下这些情形是如何实现的。

GlideModule

GlideModule创建

首先,要扩展Glide需要创建新的GlideModule。有两种GlideModule,对于应用程序来说是AppGlideModule,对于library则是LibraryGlideModule,而AppGlideModule继承了LibraryGlideModule。

下面我们先来定义一个GlideModule:

java 复制代码
  // 可自定定义模块的名称
  @GlideModule(glideName = "GlideAppExt")
  public class MyGlideModule extends AppGlideModule {

    @Override
    public void applyOptions(@NonNull Context context,
                             @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
        // 内存缓存相关,默认是20m
        int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
        builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
    }

    @Override
    public boolean isManifestParsingEnabled() {
        // 兼容V3版本,V3版本在manifest内用meta-data指定,而不是注解生成
        return false;
    }

    @Override
    public void registerComponents(@NonNull @NotNull Context context,
                                   @NonNull @NotNull Glide glide,
                                   @NonNull @NotNull Registry registry) {
        super.registerComponents(context, glide, registry);
    }
  }

这里对内存缓存做了个自定义操作,限制内存缓存的大小是20MB。

当加上GlideModule注解后,编译时会自动生成GeneratedAppGlideModuleImpl类,下面是注解自动生成的代码:

java 复制代码
@SuppressWarnings("deprecation")
final class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {
  private final MyGlideModule appGlideModule;

  public GeneratedAppGlideModuleImpl(Context context) {
    appGlideModule = new MyGlideModule();
  }

  @Override
  public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
    appGlideModule.applyOptions(context, builder);
  }

  @Override
  public void registerComponents(@NonNull Context context, @NonNull Glide glide,
      @NonNull Registry registry) {
    appGlideModule.registerComponents(context, glide, registry);
  }

  @Override
  public boolean isManifestParsingEnabled() {
    return appGlideModule.isManifestParsingEnabled();
  }

  @Override
  @NonNull
  public Set<Class<?>> getExcludedModuleClasses() {
    return Collections.emptySet();
  }

  @Override
  @NonNull
  GeneratedRequestManagerFactory getRequestManagerFactory() {
    // 这个也是自动生成的
    return new GeneratedRequestManagerFactory();
  }
}

实际就是实际就是一个代理类,里面通过创建我们自定义的MyGlideModule对象,去实现一些功能。

那我们这个生成的的GeneratedAppGlideModuleImpl会在哪里用到呢?回顾下第一篇文章Glide的创建过程:

java 复制代码
  @NonNull
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
    
      // 这里通过调用getAnnotationGeneratedGlideModules方法去获取GeneratedAppGlideModule
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModules(context.getApplicationContext());
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context, annotationGeneratedModule);
        }
      }
    }
    return glide;
  }

创建Glide的时候会调用getAnnotationGeneratedGlideModules方法去查找GeneratedAppGlideModule:

java 复制代码
  @Nullable
  @SuppressWarnings({"unchecked", "TryWithIdenticalCatches", "PMD.UnusedFormalParameter"})
  private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules(Context context) {
    GeneratedAppGlideModule result = null;
    
    try {
      // 通过反射创建的
      Class<GeneratedAppGlideModule> clazz =
          (Class<GeneratedAppGlideModule>)
              Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
      result =
          clazz.getDeclaredConstructor(Context.class).newInstance(context.getApplicationContext());
    } catch (ClassNotFoundException e) {
      // ...
      // These exceptions can't be squashed across all versions of Android.
    } catch (InstantiationException e) {
      throwIncorrectGlideModule(e);
    } catch (IllegalAccessException e) {
      throwIncorrectGlideModule(e);
    } catch (NoSuchMethodException e) {
      throwIncorrectGlideModule(e);
    } catch (InvocationTargetException e) {
      throwIncorrectGlideModule(e);
    }
    return result;
  }

里面会通过反射创建GeneratedAppGlideModuleImpl对象,并强制转换成GeneratedAppGlideModule。当然,如果我们没有自定义GlideModule,那这个反射是生成不了的。

小结下,也就是说当我们新建了一个MyGlideModule类,并给它加上AppGlideModule注解,它就会生成一个GeneratedAppGlideModuleImpl类,当Glide创建的时候,就会通过反射去创建这个类的对象,而这个类中又会创建我们自定义的MyGlideModule对象,并且把所有功能交给它处理。听起来有点复杂,但是对于开发来说却是简单了,加注解就行了。

问题解决

说起来很简单,参考别人的文章,可是我的自动生成的GeneratedAppGlideModuleImpl一直出不来,出现了几个问题,这里提一下:

  • 要添加注解处理器:

    java 复制代码
      annotationProcessor 'com.github.bumptech.glide:compiler:4.5.0'
      // kotlin版本使用kapt,记得添加'kotlin-kapt'插件
      kapt 'com.github.bumptech.glide:compiler:4.11.0'
  • kapt报错。这里好像需要JDK11; 这个也算我画蛇添足了,把项目换到JDK8了,后面kapt就不行了,然后还忘了,最后还是在StackOverflow上找到个回答,突然想起来,改回去就好了。

  • "build\generated\ap_generated_sources\debug\out"目录没内容。 其实在"build\generated\source\kapt\debug\com\bumptech\glide"目录。 这里也卡了我一会,后面双击shift直接搜的,发现kapt生成的目录不一样了

Glide扩展的使用

到这,我们实际上就可以使用自定义的扩展了,使用方法如下:

java 复制代码
  GlideAppExt.with(this).load(url).into(imgView);

好像没什么区别,但是别忘了我们在自定义的MyGlideModule中自定义了内存缓存,给它限制到了20MB。

java 复制代码
  // 可自定定义模块的名称
  @GlideModule(glideName = "GlideAppExt")
  public class MyGlideModule extends AppGlideModule {

    @Override
    public void applyOptions(@NonNull Context context,
                             @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
        // 内存缓存相关,默认是20m
        int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
        builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
    }
    // ...
  }

这里GlideAppExt是我们在GlideModule注解传的值,同时它也是一个自动生成的类:

java 复制代码
public final class GlideAppExt {
  private GlideAppExt() {}

  @Nullable
  public static File getPhotoCacheDir(@NonNull Context context) {
    return Glide.getPhotoCacheDir(context);
  }

  @Nullable
  public static File getPhotoCacheDir(@NonNull Context context, @NonNull String string) {
    return Glide.getPhotoCacheDir(context, string);
  }

  @NonNull
  public static Glide get(@NonNull Context context) {
    return Glide.get(context);
  }

  @Deprecated
  @VisibleForTesting
  @SuppressLint("VisibleForTests")
  public static void init(Glide glide) {
    Glide.init(glide);
  }

  @VisibleForTesting
  @SuppressLint("VisibleForTests")
  public static void init(@NonNull Context context, @NonNull GlideBuilder builder) {
    Glide.init(context, builder);
  }

  @VisibleForTesting
  @SuppressLint("VisibleForTests")
  public static void tearDown() {
    Glide.tearDown();
  }

  @NonNull
  public static GlideRequests with(@NonNull Context context) {
    return (GlideRequests) Glide.with(context);
  }

  @NonNull
  public static GlideRequests with(@NonNull Activity activity) {
    return (GlideRequests) Glide.with(activity);
  }

  @NonNull
  public static GlideRequests with(@NonNull FragmentActivity activity) {
    return (GlideRequests) Glide.with(activity);
  }

  @NonNull
  public static GlideRequests with(@NonNull Fragment fragment) {
    return (GlideRequests) Glide.with(fragment);
  }

  @Deprecated
  @NonNull
  public static GlideRequests with(@NonNull android.app.Fragment fragment) {
    return (GlideRequests) Glide.with(fragment);
  }

  @NonNull
  public static GlideRequests with(@NonNull View view) {
    return (GlideRequests) Glide.with(view);
  }
}

差不多就是Glide的一个入口吧,和Glide公开的方法差不多,里面还都是调用Glide去实现的,但是还是有不同的,需要注意下!

这里的with方法,调用Glide#with后,把RequestManager强制转换成了GlideRequests,GlideRequests继承了RequestManager,简单看下里面的代码:

java 复制代码
  public class GlideRequests extends RequestManager {
    public GlideRequests(@NonNull Glide glide, @NonNull Lifecycle lifecycle,
        @NonNull RequestManagerTreeNode treeNode, @NonNull Context context) {
      super(glide, lifecycle, treeNode, context);
    }
  
    @Override
    @CheckResult
    @NonNull
    public <ResourceType> GlideRequest<ResourceType> as(@NonNull Class<ResourceType> resourceClass) {
      // 把RequestManager中的RequestBuilder换成了GlideRequest,GlideRequest同样继承了RequestBuilder
      return new GlideRequest<>(glide, this, resourceClass, context);
    }
    
    //...
  }

也就是说使用GlideAppExt的时候,GlideRequests会扩展RequestManager,而GlideRequest会扩展RequestBuilder。

要问这两个类是干嘛的,我只能暂时说这两个类也是随着MyGlideModule加注解后自动生成的,暂时看不出什么区别,后面再看咯。

还有一个问题,为什么我们在MyGlideModule中自定义了内存缓存能生效呢?实际第一篇的文章中已经讲到了:

java 复制代码
  // Glide类中
  private static void initializeGlide(
    //...
    
    for (com.bumptech.glide.module.GlideModule module : manifestModules) {
      module.applyOptions(applicationContext, builder);
    }
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.applyOptions(applicationContext, builder);
    }
    //...
  }

在Glide对象创建的时候,我们的annotationGeneratedModule的applyOptions方法会被调用,然后中转到调用MyGlideModule的applyOptions方法:

java 复制代码
    @Override
    public void applyOptions(@NonNull Context context,
                             @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
        //内存缓存相关,默认是20m
        int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
        builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
    }

在GlideBuilder的build方法中,有这么一段,因为memoryCache已经设置了,所以就生效了:

less 复制代码
  @NonNull
  Glide build(@NonNull Context context) {
    // ...
    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
    
    // ...
  }

GlideExtension

上面说到了GlideModule以及它的使用,它可以让我们使用一个或更多集成库,前言也说到了Glide扩展还有修改Glide的配置、扩展Glide的API的功能,这就是GlideExtension的功能了。

GlideExtension支持两种扩展方式:

  • GlideOption: 为 RequestOptions添加或修改选项。
  • GlideType: 添加对新的资源类型的支持(GIF,SVG等等)。

GlideExtension的创建

创建

我们先来创建一个GlideExtension,再来分析它的源码:

java 复制代码
  // 可在Application和Library中被扩展,被GlideExtension注解的类应以工具类的思维编写。
  @GlideExtension
  public class MyAppGlideExtension {
      // 被注解的类可以含有静态变量,可以引用其他的类或对象。
      private static final int MINI_THUMB_SIZE = 100;
  
      // 这种类应该有一个私有的、空的构造方法,应为 final 类型,并且仅包含静态方法。
      private MyAppGlideExtension() {}
  
      @NonNull
      @GlideOption
      public static BaseRequestOptions<?> miniThumb(BaseRequestOptions<?> options) {
          // 给option增加默认设置,居中且大小为100
          return options
                  .fitCenter()
                  .override(MINI_THUMB_SIZE);
      }
  }

经过编译后,自动生成的GlideOptions类中就会为我们添加方法:

java 复制代码
  public final class GlideOptions extends RequestOptions implements Cloneable {
    // ...
    @CheckResult
    @NonNull
    public GlideOptions miniThumb() {
      return (GlideOptions) MyAppGlideExtension.miniThumb(this);
    }
  }

然后就可以像下面使用了:

java 复制代码
  GlideAppExt.with(context)
      .load(url)
      .miniThumb()
      .into(imgView);

如果不使用GlideAppExt,而是通过Glide去调用是会有问题的:

如果只看表象而不探讨源码的话,咱们这文章就不改叫源码解析了。下面我们分析下为什么。

原理

如果说这两份代码有何区别的话,其实前面Glide扩展的使用时已经有说到一点了,那就是两个load方法返回的对象不一样:

java 复制代码
  // 使用Glide时
  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }
  
  // 使用GlideAppExt时
  public GlideRequest<Drawable> load(@Nullable String string) {
    return (GlideRequest<Drawable>) super.load(string);
  }

也就是说实际是GlideRequest里面添加了我们的miniThumb,看下源码确实有:

less 复制代码
  /**
   * @see MyAppGlideExtension#miniThumb(BaseRequestOptions)
   */
  @SuppressWarnings("unchecked")
  @CheckResult
  @NonNull
  public GlideRequest<TranscodeType> miniThumb() {
    return (GlideRequest<TranscodeType>) MyAppGlideExtension.miniThumb(this);
  }

看下它的Structure,除了继承的方法,就多了这一个方法,也就是说GlideRequest和RequestBuilder区别就是这么一个方法么?也就是说这就是扩展结果。

GlideOption

使用

其实上面已经把GlideOption讲解的挺清楚了,这里再多提一下GlideOption注释的方法也是可以添加参数的,看下面代码:

java 复制代码
    @NonNull
    @GlideOption
    public static BaseRequestOptions<?> miniThumbSize(BaseRequestOptions<?> options, int size) {
        // 通过传递进来的size设置图片大小
        return options
                .fitCenter()
                .override(size);
    }

在之前的MyAppGlideExtension中加了个方法,编译一下,再来看下GlideRequest里面,果然多了个方法:

less 复制代码
  @CheckResult
  @NonNull
  public GlideRequest<TranscodeType> miniThumbSize(int size) {
    return (GlideRequest<TranscodeType>) MyAppGlideExtension.miniThumbSize(this, size);
  }

其实到这我还有个关于GlideOption的疑问,既然我们加的GlideOption注解方法是在GlideRequest生效的,那为什么还要修改自动生成的GlideOptions类呢?

探究

想到这里,我看了下GlideAppExt.with(context)返回对象的GlideRequests类中,添加了GlideOption注解它会有什么变化,果然在它的代码中我发现了问题:

java 复制代码
  @Override
  protected void setRequestOptions(@NonNull RequestOptions toSet) {
    if (toSet instanceof com.silencefly96.fundark.GlideOptions) {
      super.setRequestOptions(toSet);
    } else {
      super.setRequestOptions(new com.silencefly96.fundark.GlideOptions().apply(toSet));
    }
  }

也就是说,这里强制把RequestOptions换成了我们自动生成的GlideOptions,那么只要调用设置(初始化也算),GlideRequests(RequestManager)中保存的RequestOptions,已经是我们自己的GlideOptions:

java 复制代码
  @GuardedBy("this")
  private RequestOptions requestOptions;

这有什么作用呢?我是觉得好像没啥用,除非能加字段,只加方法的话很鸡肋吧。

小结下,加上GlideOption注解,会有两个结果:

  • 一个是把RequestBuilder变成GlideRequest,并增加我们的扩展方法,使得在load(url)后面,我们就能调用自定义的扩展方法。
  • 另一个是把RequestOptions变成GlideOptions,并增加我们的扩展方法,并且会使GlideRequests(RequestManager)中持有的RequestOptions变成GlideOptions类型。

第一个方便了我们使用,第二个改变了保存的变量的储存类型。

GlideType

ps: 下面GlideType是按官网例子写的,实际上并不太行,如果真的需要扩展的话可以看下一大节。

看完GlideOption,我们来看下GlideType,它能为Glide添加对新的资源类型的支持,包括指定默认选项。

使用

下面我们就按官方的例子,添加对 GIF 的支持,还是在我们的MyAppGlideExtension添加代码:

java 复制代码
  @GlideExtension
  public class MyAppGlideExtension {
    // ...
    private static final RequestOptions DECODE_TYPE_GIF
            = RequestOptions.decodeTypeOf(GifDrawable.class).lock();
    
    // 网上博文有问题。。。按下面代码吧!不要丢了NonNull,有return值,方法不能和已有方法重名
    @NonNull
    @GlideType(GifDrawable.class)
    public static RequestBuilder<GifDrawable> as2Gif(RequestBuilder<GifDrawable> requestBuilder) {
        return requestBuilder
                .transition(new DrawableTransitionOptions())
                .apply(DECODE_TYPE_GIF);
    }
  }

上面GlideOption注解会对GlideOptions和GlideRequest(RequestBuilder)做修改,而这里GlideType注解,则是修改GlideRequests(RequestManager),编译一下看代码:

java 复制代码
  /**
   * @see MyAppGlideExtension#as2Gif(RequestBuilder)
   */
  @NonNull
  @CheckResult
  public GlideRequest<GifDrawable> as2Gif() {
    // 注意asGif方法已经有了
    return (GlideRequest<GifDrawable>) MyAppGlideExtension.as2Gif(this.as(GifDrawable.class));
  }

看下GlideRequests的Structure,也是单独多了一个as2Gif方法:

接下来就可以使用了:

java 复制代码
  GlideApp.with(fragment)
    .as2Gif()
    .load(url)
    .into(imageView);

部分原理

前面为Glide增加了对Gif格式的支持,我们接下来简单看下里面的原理。

在瞄一眼这个方法:

java 复制代码
    private static final RequestOptions DECODE_TYPE_GIF
            = RequestOptions.decodeTypeOf(GifDrawable.class).lock();
            
    @NonNull
    @GlideType(GifDrawable.class)
    public static RequestBuilder<GifDrawable> as2Gif(RequestBuilder<GifDrawable> requestBuilder) {
        return requestBuilder
                .transition(new DrawableTransitionOptions())
                .apply(DECODE_TYPE_GIF);
    }

这里实际分了三步: decodeTypeOf、transition、apply。

  1. 第一步,通过RequestOptions.decodeTypeOf方法创建了一个RequestOptions:
java 复制代码
  @NonNull
  @CheckResult
  public static RequestOptions decodeTypeOf(@NonNull Class<?> resourceClass) {
    // 创建一个新的RequestOptions
    return new RequestOptions().decode(resourceClass);
  }
  
  @NonNull
  @CheckResult
  public T decode(@NonNull Class<?> resourceClass) {
    if (isAutoCloneEnabled) {
      // 复制一份再decode
      return clone().decode(resourceClass);
    }

    // 保存当前class
    this.resourceClass = Preconditions.checkNotNull(resourceClass);
    // 加上标记么?
    fields |= RESOURCE_CLASS;
    // 加上一层判断锁么,然后返回自身
    return selfOrThrowIfLocked();
  }
  
  @NonNull
  @SuppressWarnings("unchecked")
  private T selfOrThrowIfLocked() {
    if (isLocked) {
      throw new IllegalStateException("You cannot modify locked T, consider clone()");
    }
    return self();
  }
  
  private T self() {
    return (T) this;
  }

嗯,好像没什么有价值的东西,只是创建了一个RequestOptions,并把resourceClass、fields保存到变量域,再返回。

  1. 第二步,在transition方法中创建并传入了一个DrawableTransitionOptions对象:
java 复制代码
  @NonNull
  @CheckResult
  public RequestBuilder<TranscodeType> transition(
      @NonNull TransitionOptions<?, ? super TranscodeType> transitionOptions) {
    this.transitionOptions = Preconditions.checkNotNull(transitionOptions);
    isDefaultTransitionOptionsSet = false;
    return this;
  }

transition方法好像也只是记录下,DrawableTransitionOptions是动画效果,自带的,交叉淡化之类的。

  1. 第三步,在apply方法中传入了第一步的DECODE_TYPE_GIF:
java 复制代码
  @NonNull
  @CheckResult
  @Override
  public RequestBuilder<TranscodeType> apply(@NonNull BaseRequestOptions<?> requestOptions) {
    Preconditions.checkNotNull(requestOptions);
    // 就是更新了BaseRequestOptions中的数据
    return super.apply(requestOptions);
  }
  
  // BaseRequestOptions中
  @NonNull
  @CheckResult
  public T apply(@NonNull BaseRequestOptions<?> o) {
    // 设置了resourceClass
    if (isSet(other.fields, RESOURCE_CLASS)) {
      resourceClass = other.resourceClass;
    }
    
    // 把fields、options也保存进来
    fields |= other.fields;
    options.putAll(other.options);
    
    return selfOrThrowIfLocked();
  }

这里就是把新的requestOptions信息,保存到当前的requestOptions中吧,看名字apply也能理解。

三步看下来,还是一脸懵逼啊!大致就是把新的requestOptions(DECODE_TYPE_GIF),更新到当前的requestOptions中,还在transition方法传了个动画效果。

原理探究

本来花了很长时间去研究原理的,也写了几百行(下面删了),最终发现GifDrawable可以用,是因为内置了Gif格式的解码器、转码器等:

java 复制代码
  // 在Glide构造方法里面添加的:
  registry
    //...
    /* GIFs */
    .append(
        Registry.BUCKET_GIF,
        InputStream.class,
        GifDrawable.class,
        new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool))
    .append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder)
    .append(GifDrawable.class, new GifDrawableEncoder())
    /* GIF Frames */
    // Compilation with Gradle requires the type to be specified for UnitModelLoader here.
    .append(
        GifDecoder.class, GifDecoder.class, UnitModelLoader.Factory.<GifDecoder>getInstance())
    .append(
        Registry.BUCKET_BITMAP,
        GifDecoder.class,
        Bitmap.class,
        new GifFrameResourceDecoder(bitmapPool))

WTF?有点打击看源码的心情,后面觉得不能气馁,继续学了下Glide中到底如何扩展格式,这里写在了另一篇文章里面有兴趣的可以看一下: Glide加载自定义图片格式

研究完Glide中到底如何扩展格式,再回过来理解GlideType的原理,就明了多了,就是定制化RequestBuilder中的as方法:

java 复制代码
// 一般使用
Glide.with(context)
    .`as`(CustomDrawable::class.java)
    // .load("file:///android_asset/pic.custom")
    .load(R.raw.pic)
    .into(binding.image)

// 扩展使用 
GlideAppExt.with(it)
  .asCustom()
  .load(R.raw.pic)
  .into(binding.image)

说明GlideType注解确实只是一个标记,真正起作用的是ResourceDecoder。

于是,我去翻了下我的第一篇文章,在engine.load(...)节,有把数据处理成resource的方法:

java 复制代码
  private void decodeFromRetrievedData() {
    Resource<R> resource = null;
    try {
      // 处理数据
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    
    if (resource != null) {
      // 通知获取数据完成
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }

也就是说数据是在decodeFromData方法中解码的,接下来就一路追踪,过程太长就不写了,找到了下面方法:

java 复制代码
  // DecodeJob中
  private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
      throws GlideException {
    LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
    return runLoadPath(data, dataSource, path);
  }

在DecodeJob代码中第一次出现了Resource类中的R,也就是我们GlideType注解的类型,找下它怎么来的:

csharp 复制代码
  // 原来是泛型类
  class DecodeJob<R> ...
  // 来自init方法里面的transcodeClass
  DecodeJob<R> init(
      // ...
      Class<?> resourceClass,
      Class<R> transcodeClass,
      // ...) {

其实这个transcodeClass很眼熟啊,第一篇里面作为参数传递了很多次,我们这追踪回去,也能发现它最终来自于RequestBuilder啊:

csharp 复制代码
public class RequestBuilder<TranscodeType>

再看看RequestBuilder哪里来的,原来是as方法来的,其中的ResourceType来自resourceClass:

java 复制代码
  @NonNull
  @CheckResult
  public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

回去看《GlideType - 部分原理 - 3.第三步》,这不就闭环了吗?里面也设置了resourceClass:

java 复制代码
  // BaseRequestOptions中
  @NonNull
  @CheckResult
  public T apply(@NonNull BaseRequestOptions<?> o) {
    // 设置了resourceClass
    if (isSet(other.fields, RESOURCE_CLASS)) {
      resourceClass = other.resourceClass;
    }
    
    // 把fields、options也保存进来
    fields |= other.fields;
    options.putAll(other.options);
    
    return selfOrThrowIfLocked();
  }

在BaseRequestOptions中的apply方法设置了这个resourceClass。

探究ResourceDecoder

都看到这了,也顺便追踪下ResourceDecoder怎么来的吧。继续从上面decodeFromData出发,一路查找:

java 复制代码
  // DecodePath类中
  @NonNull
  private Resource<ResourceType> decodeResourceWithList(
      DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options,
      List<Throwable> exceptions) throws GlideException {
      
    Resource<ResourceType> result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decoders.size(); i < size; i++) {
    
      // 在这里获取了ResourceDecoder去解码,得到Resource<ResourceType>
      ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
      try {
        DataType data = rewinder.rewindAndGet();
        if (decoder.handles(data, options)) {
          data = rewinder.rewindAndGet();
          result = decoder.decode(data, width, height, options);
        }
      } catch (IOException | RuntimeException | OutOfMemoryError e) {
        exceptions.add(e);
      }
      if (result != null) { break; }
    }
    if (result == null) { throw new GlideException(failureMessage, new ArrayList<>(exceptions)); }
    return result;
  }

继续找一下这里的decoders是从哪里来的,找到最近创建ResourceDecoder数组的地方:

java 复制代码
  // ResourceDecoderRegistry类中
  // 这里的decoders是一个map,以bucket为键,保存的是decoder数组
  private final Map<String, List<Entry<?, ?>>> decoders = new HashMap<>();
    
  @NonNull
  @SuppressWarnings("unchecked")
  public synchronized <T, R> List<ResourceDecoder<T, R>> getDecoders(
      @NonNull Class<T> dataClass, @NonNull Class<R> resourceClass) {
      
    List<ResourceDecoder<T, R>> result = new ArrayList<>();
    
    // 和bucket什么有关
    for (String bucket : bucketPriorityList) {
      List<Entry<?, ?>> entries = decoders.get(bucket);
      if (entries == null) { continue; }
      for (Entry<?, ?> entry : entries) {
        if (entry.handles(dataClass, resourceClass)) {
          result.add((ResourceDecoder<T, R>) entry.decoder);
        }
      }
    }
    return result;
  }

这里迷糊了,又有什么bucket,又有以bucket为键的好多decoder数组,继续看下decoder到底哪里添加的,又经过几个方法,发现是在Registry类中下面方法里:

java 复制代码
  // Registry类中
  @NonNull
  public <Data, TResource> Registry append(
      @NonNull String bucket,
      @NonNull Class<Data> dataClass,
      @NonNull Class<TResource> resourceClass,
      @NonNull ResourceDecoder<Data, TResource> decoder) {
    decoderRegistry.append(bucket, decoder, dataClass, resourceClass);
    return this;
  }

如果看了我写的Glide加载自定义图片格式,那就很熟悉了,自定义格式的时候也是通过Registry的append添加上去的。

继续看哪里调用了这个方法:

居然是在Glide构造方法里面添加的:

java 复制代码
  registry
    //...
    /* GIFs */
    .append(
        Registry.BUCKET_GIF,
        InputStream.class,
        GifDrawable.class,
        new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool))
    .append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder)
    .append(GifDrawable.class, new GifDrawableEncoder())
    /* GIF Frames */
    // Compilation with Gradle requires the type to be specified for UnitModelLoader here.
    .append(
        GifDecoder.class, GifDecoder.class, UnitModelLoader.Factory.<GifDecoder>getInstance())
    .append(
        Registry.BUCKET_BITMAP,
        GifDecoder.class,
        Bitmap.class,
        new GifFrameResourceDecoder(bitmapPool))

到这,ResourceDecoder也有了个大致了解,是不是有种恍然大悟的感觉!

小结

本来以为拿着别人写好的关于扩展的文章,实践下,顺便研究下原理啥的,结果遇到了很多问题,拖了很久才把Glide扩展的内容大致了解了下,希望对读者有所帮助!

ps. 本来这篇文章要和Glide加载自定义图片格式一起发的,就剩十分钟结个尾了,结果忙了一段时间,又逢中秋国庆假期,现在才发,继续努力吧!

相关推荐
宝杰X71 小时前
Android Room3 多平台数据库
android·数据库
huibin1478523694 小时前
热缓解学习记录
android·学习
小田的博客7 小时前
SAP MM 供应商银行主数据更新报错!message R1228!
android·java·服务器
一笑的小酒馆8 小时前
Android智能猫砂盆视频加载慢卡顿问题分析
android
只会cv的小前端10 小时前
七巧低代码服务端脚本使用方法
android·低代码·rxjava
10 小时前
Android 自定义 View 实战:从零打造工业级全向摇杆控件(OmniJoystickView)
android·kotlin·自定义view·摇杆控件
雨白10 小时前
深入理解 Kotlin 协程 (十一):以逸待劳,探秘 select 多路复用与并发安全策略
android·kotlin
hunterandroid13 小时前
[Android 从零到一] Android 深度链接与 App Links:从 URI Scheme 到可验证的应用跳转
android
我命由我1234515 小时前
Jetpack Compose - Material Design 断点范围、WindowSizeClass、针对不同屏幕尺寸创建预览、四类导航栏
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime
2501_9151063215 小时前
Python爬虫从零开始:七天掌握HTTP协议、数据解析与反爬策略
android·ios·小程序·https·uni-app·iphone·webview