浅析 Glide 原理

902 阅读9分钟

依赖:

    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'

用法:

        Glide.with(this).load(url).into(imageView)

上面这行代码,是 Glide 最简单的使用方式了,下面我们来一个个拆解下。

with

with 就是根据传入的 context 来获取图片请求管理器 RequestManager,用来启动和管理图片请求。

  public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
  }

context 可以传入 Application,Activity 和 Fragment,这关系着图片请求的生命周期。通常使用当前页面的 context,这样当我们打开一个页面加载图片,然后退出页面时,图片请求会跟随页面的销毁而被取消,而不是继续加载浪费资源。

当 context 是 Application 时,获得的 RequestManager 是一个全局单例,图片请求的生命周期会跟随整个 APP 。

如果 with 发生在子线程,不管 context 是谁,都返回应用级别的 RequestManager 单例。

  private RequestManager getApplicationManager(@NonNull Context context) {
    // Either an application context or we're on a background thread.
    if (applicationManager == null) {
      synchronized (this) {
        if (applicationManager == null) {
          // Normally pause/resume is taken care of by the fragment we add to the fragment or
          // activity. However, in this case since the manager attached to the application will not
          // receive lifecycle events, we must force the manager to start resumed using
          // ApplicationLifecycle.

          // TODO(b/27524013): Factor out this Glide.get() call.
          Glide glide = Glide.get(context.getApplicationContext());
          applicationManager =
              factory.build(
                  glide,
                  new ApplicationLifecycle(),
                  new EmptyRequestManagerTreeNode(),
                  context.getApplicationContext());
        }
      }
    }

    return applicationManager;
  }

当 context 是 Activity 时,会创建一个无界面的 Fragment 添加到 Activity,用于感知 Activity 的生命周期,同时创建 RequestManager 给该 Fragment 持有。

  private RequestManager supportFragmentGet(
      @NonNull Context context,
      @NonNull FragmentManager fm,
      @Nullable Fragment parentHint,
      boolean isParentVisible) {
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      // This is a bit of hack, we're going to start the RequestManager, but not the
      // corresponding Lifecycle. It's safe to start the RequestManager, but starting the
      // Lifecycle might trigger memory leaks. See b/154405040
      if (isParentVisible) {
        requestManager.onStart();
      }
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

load

load 方法会得到一个图片请求构建器 RequestBuilder,用来创建图片请求。

  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }

into

首先是根据 ImageView 的 ScaleType,来配置参数.

  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    BaseRequestOptions<?> requestOptions = this;
    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
      // Clone in this method so that if we use this RequestBuilder to load into a View and then
      // into a different target, we don't retain the transformation applied based on the previous
      // View's scale type.
      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:
          // Do nothing.
      }
    }

    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
  }

继续跟进 into,会创建图片请求,获取 Target 载体已有的请求,对比两个请求,如果等效,启动异步请求,然后,图片载体绑定图片请求,也就是 ImageView setTag 为 request 。

  private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      // If the request is completed, beginning again will ensure the result is re-delivered,
      // triggering RequestListeners and Targets. If the request is failed, beginning again will
      // restart the request, giving it another chance to complete. If the request is already
      // running, we can let it continue running without interruption.
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        // Use the previous request rather than the new one to allow for optimizations like skipping
        // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
        // that are done in the individual Request.
        previous.begin();
      }
      return target;
    }

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
  }

继续跟进异步请求 requestManager.track(target, request)

  synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
  }
  public void runRequest(@NonNull Request request) {
    requests.add(request);
    if (!isPaused) {
      request.begin();//开启图片请求
    } else {
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      pendingRequests.add(request);//如果是暂停状态,就把请求存起来。
    }
  }

到这里就启动了图片请求了,我们继续跟进 request.begin()

  public void begin() {
    synchronized (requestLock) {
      //......
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      //如果有尺寸,开始加载
        onSizeReady(overrideWidth, overrideHeight);
      } else {
      //如果无尺寸就先去获取
        target.getSize(this);
      }
      //......
    }
  }

然后继续瞧瞧 onSizeReady

  public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    synchronized (requestLock) {
    //......
      loadStatus =
          engine.load(
              glideContext,
              model,
              requestOptions.getSignature(),
              this.width,
              this.height,
              requestOptions.getResourceClass(),
              transcodeClass,
              priority,
              requestOptions.getDiskCacheStrategy(),
              requestOptions.getTransformations(),
              requestOptions.isTransformationRequired(),
              requestOptions.isScaleOnlyOrNoTransform(),
              requestOptions.getOptions(),
              requestOptions.isMemoryCacheable(),
              requestOptions.getUseUnlimitedSourceGeneratorsPool(),
              requestOptions.getUseAnimationPool(),
              requestOptions.getOnlyRetrieveFromCache(),
              this,
              callbackExecutor);

      //......
    }
  }

跟进 engine.load

  public <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb,
      Executor callbackExecutor) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource<?> memoryResource;
    synchronized (this) {
      //从内存加载
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
      if (memoryResource == null) { //如果内存里没有
        return waitForExistingOrStartNewJob(
            glideContext,
            model,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            options,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache,
            cb,
            callbackExecutor,
            key,
            startTime);
      }
    }
    cb.onResourceReady(
        memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
    return null;
  }
  private <R> LoadStatus waitForExistingOrStartNewJob(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb,
      Executor callbackExecutor,
      EngineKey key,
      long startTime) {

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb, callbackExecutor);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }

DecodeJob 是一个 Runnable,它通过一系列的调用,会来到 HttpUrlFetcher 的 loadData 方法。

  public void loadData(
      @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //获取输入流,此处使用的是 HttpURLConnection
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //回调出去
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

至此,网络请求结束,最后把图片设置上去就行了,在 SingleRequest 的 onResourceReady 方法,它会把结果回调给 Target 载体。

 target.onResourceReady(result, animation);

继续跟进它,最终会执行 setResource,把图片设置上去。

  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }

原理总结

with 根据传入的 context 获取图片请求管理器 RequestManager,当传入的 context 是 Application 时,图片请求的生命周期会跟随应用,当传入的是 Activity 时,会创建一个无界面的空 Fragment 添加到 Activity,用来感知 Activity 的生命周期。load 会得到了一个图片请求构建器 RequestBuilder,用来创建图片请求。into 开启加载,先会根据 ImageView 的 ScaleType 来配置参数,创建图片请求,图片载体绑定图片请求,然后开启图片请求,先从内存中加载,如果内存里没有,会创建一个 Runnable,通过一系列的调用,使用 HttpURLConnection 获取网络输入流,把结果回调出去,最后把回调结果设置上去就行了。

问题

一、Glide 的缓存机制是咋样的?

Glide 有三级缓存,读取一张图片时,顺序是: 弱引用缓存 - LruCache - 磁盘缓存

Glide 加载某张图片时,先去弱引用缓存中寻找图片,如果有则直接取出来使用,如果没有,则去 LruCache 中寻找,如果 LruCache 中有,则取出使用,并将它放入弱引用缓存中,如果没有,则从磁盘缓存或网络中加载图片。

  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> active = loadFromActiveResources(key); //从弱引用获取图片
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }

    EngineResource<?> cached = loadFromCache(key); //从 LruCache 获取缓存图片
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
  }

二、为什么需要弱引用缓存和 LruCache 两个缓存?

弱引用缓存用于存储当前正在使用的图片资源,设计的本质是通过弱引用跟踪被强引用持有的活跃资源,同时作为 Glide 内部的活跃标记,避免它们被回收。如果只用 Lrucache,当图片正在显示时,如果 Lrucache 空间不足,可能会被移除,导致图片重新加载,影响性能。

LruCache 用于存储最近使用过但当前未被引用的图片资源,如果只用弱引用缓存,当图片不再被引用时,会立即被垃圾回收器回收,无法快速重用,导致重复加载,影响性能。

三、什么是 LruCache?

LruCache是一种基于淘汰策略的缓存机制,核心思想是:当缓存容量达到上限时,优先移除 “最近最少被使用” 的缓存项,以此保留更可能被再次访问的数据,提高缓存利用率。内部通过哈希表 + 双向链表结构(如 Java 中的 LinkedHashMap)维护缓存项,既保证快速查询,又能记录访问顺序。每次调用 get(key) 或 put(key, value) 时,会将对应的缓存项标记为最近使用移到链表头部。当新增数据导致缓存超出最大容量时,自动触发淘汰逻辑,移除链表尾部也就是最久未使用的项,直到缓存容量符合限制。

四、url 不变但是图片变了,实际加载的是旧图还是新图?

Glide 加载图片时,url 不变但图片变了的这种情况,还是会用以前的图片。因为 Glide 加载图片会将图片缓存到本地,如果 url 不变则直接读取缓存不会再从网络上加载。

如果想要加载新的图片,可以使用如下解决方案:

  1. 清除缓存
  2. 让后台每次都更改图片的名字
  3. 图片地址选用 ”url?key="+随机数这种格式

五、不同尺寸的 Imageview 用的是同一缓存图吗?

不是,Glide 会为每个不同尺寸的 Imageview 缓存一张图片,也就是说不管这张图片有没有加载过,只要 Imageview 的尺寸不一样,Glide 就会重新加载一次,这时候,它会在加载 Imageview 之前从网络上重新下载,然后再缓存。举个例子,如果一个页面的 Imageview 是 100 * 100,另一个页面的 Imageview 是 800 * 800,它俩展示同一张图片的话,Glide 会下载两次图片,并且缓存两张图片,因为 Glide 缓存 Key 的生成条件之一就是控件的长宽。

六、图片加载中关闭页面,此页面会造成内存泄漏吗?

在图片加载中关闭页面,此页面也不会造成内存泄漏,因为 Glide 在加载资源的时候,如果是在 Activity 或 Fragment 这类有生命周期的组件上进行的话,会创建一个无界面的 Fragment 加入到 FragmentManager 之中,感知生命周期,当 Activity 或 Fragment 进入不可见或销毁的时候,Glide 会停止加载资源。但是,如果是在非生命周期的组件上进行时,一般会采用 Application 的生命周期贯穿整个应用,此时只有在应用程序关闭的时候才会停止加载。

七、源码运用了哪些设计模式

  1. 建造者模式:Glide 的核心 API 链式调用(如 Glide.with(context).load(url).placeholder(R.drawable.placeholder).into(imageView))就是基于建造者模式实现的。RequestBuilder 类是建造者模式的核心,它通过一系列 setter 方法(如 placeholder()、error()、transform())配置图片加载参数,最终通过 into() 方法构建并执行请求。
  2. 单例模式:Glide 类通过 Glide.get(Context) 方法获取全局唯一实例。
  3. 装饰者模式:Transformation 接口的实现类(如 CenterCrop、FitCenter、RoundedCorners)通过装饰器模式动态为图片添加变换效果。
  4. 观察者模式:RequestListener 接口允许用户监听图片加载的成功/失败状态,Target 接口则作为观察者接收加载结果(如 onResourceReady()、onLoadFailed())。当图片加载完成或失败时,Glide 会通知所有注册的观察者。
  5. 策略模式:缓存策略,线程池策略等可替换逻辑。