Android图片加载框架Glide解析

现在Android上的图片加载框架有很多,比如:Universal Image Loader,Picasso,Fresco,Glide等等,这些框架有各自的优缺点。本文主要介绍的是Glide,它的作者是bumptech,作为一个高效的图片加载缓存框架,Glide被广泛的运用在google的开源项目中。

本文基于Glide最新的版本4.9.0来进行相关的介绍。

一、使用示例
二、整体概述
三、源码分析
四、缓存介绍
五、相关参考

一、使用示例

1.添加依赖

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

2.使用

Glide.with(this)
        .load(URL_IMAGE)
        .placeholder(R.drawable.ic_loading)
        .error(R.drawable.ic_load_error)
        .into(imageView);

上面这段代码即可完成图片加载,其中with()方法可以传入的参数是Context、Activity、Fragment或者FragmentActivity,load()方法可以直接传入图片链接的url或者资源id等等,into()方法传入ImageView实例。

placeholder是设置占位图,图片加载过程中会显示这张图片。
error方法是设置图片加载失败的时候显示的图片。

二、整体概述

图片加载框架一般由如下几部分构成:

(1) RequestManager:请求生成和管理模块

(2) Engine:引擎部分,负责创建任务(获取数据),并调度执行

(3) GetDataInterface:数据获取接口,负责从各个数据源获取数据。
比如 MemoryCache 从内存缓存获取数据、DiskCache 从本地缓存获取数据,下载器从网络获取数据等。

(4) Displayer:资源(图片)显示器,用于显示或操作资源。
比如 ImageView,这几个图片缓存都不仅仅支持 ImageView,同时支持其他 View 以及虚拟的 Displayer 概念。

(5) Processor 资源(图片)处理器
负责处理资源,比如旋转、压缩、截取等。

Android图片加载框架Glide解析_第1张图片

上面是Glide的总体设计图。整个库分为RequestManager(请求管理器),Engine(数据获取引擎)、Fetcher(数据获取器)、MemoryCache(内存缓存)、DiskLRUCache、Transformation(图片处理)、Encoder(本地缓存存储)、Registry(图片类型及解析器配置)、Target(目标) 等模块。

简单的讲就是Glide收到加载及显示资源的任务,创建 Request 并将它交给RequestManager,Request 启动Engine去数据源获取资源(通过 Fetcher),获取到后Transformation处理后交给Target。Glide依赖于DiskLRUCache、GifDecoder等开源库去完成本地缓存和 Gif 图片解码工作。

Glide优点

  • 图片缓存->媒体缓存

Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。

  • 与 Activity/Fragment 生命周期一致,支持 trimMemory

Glide 对每个 context 都保持一个RequestManager,通过FragmentTransaction保持与 Activity/Fragment生命周期一致,并且有对应的trimMemory接口实现可供调用。

  • 支持 okhttp、Volley

Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。

  • 内存友好

1)Glide 的内存缓存有个 active 的设计从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。

2)内存缓存更小图片Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小

3)与 Activity/Fragment 生命周期一致,支持 trimMemory

4)图片默认使用默认 RGB_565 而不是 ARGB_888虽然清晰度差些,但图片更小,也可配置到 ARGB_888。

三、源码分析

从上面的示例可以看到Glide加载图片的用法是非常简单的,下面主要是通过阅读源码来看一下Glide是怎么完成这个图片加载过程的。

1.with()

Glide的方法with()主要是为了获取到一个RequestManager,它的参数可以是Activity、Fragment或者别的Context参数。

Android图片加载框架Glide解析_第2张图片

通过查看with()方法的源码,我们可以看到它最后是调用RequestManagerRetriever的get()方法来获取到RequestManager的。

  @NonNull
  public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

  @SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
  @Deprecated
  @NonNull
  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    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);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

每一个返回的RequestManager 都会关联一个RequestManagerFragment对象,这个Fragment对象可以监听到加载图片的Activity的生命周期,这样就可以很方便的管理图片加载的过程,当Activity销毁的时候就停止加载。

2.load()

方法load()比较简单,它最终返回的是RequestBuilder。

Android图片加载框架Glide解析_第3张图片

我们这里传入的是图片的url,从图中可以看到,load方法可以传入很多不同类型的参数,直接的bitmap、文件、字节数组等等,还是很强大的。

3.placeholder()
4.error()

这两个方法是用来设置占位图和加载失败时展示的图片的,点进去我们可以看到这两个都是类BaseRequestOptions中的方法,因为BaseRequestOptions是Glide 4之后新加的一个类,主要是用于配置图片加载的设置。因为RequestBuilder是继承这个类的,所以我们可以直接调用这两个方法。

5.into()

最后的into()方法是Glide图片加载的核心部分,它主要是完成图片的加载并显示到ImageView上。

直接跟进去,最终它会调用到下面这个方法:

  private > Y into(
      @NonNull Y target,
      @Nullable RequestListener 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)) {
      request.recycle();
      // 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;
  }

方法into()里会生成用于请求资源的SingleRequest,在由requestManager调用方法track()、以及RequestTracker调用方法runRequest后,SingleRequest会执行begin()方法:

  public synchronized void begin() {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      // Only log at more verbose log levels if the user has set a fallback drawable, because
      // fallback Drawables indicate the user expects null models occasionally.
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }

    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }

    // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
    // that starts an identical request into the same Target or View), we can simply use the
    // resource and size we retrieved the last time around and skip obtaining a new size, starting a
    // new load etc. This does mean that users who want to restart a load because they expect that
    // the view size has changed will need to explicitly clear the View or Target before starting
    // the new load.
    if (status == Status.COMPLETE) {
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }

    // Restarts for requests that are neither complete nor running can be treated as new requests
    // and can run again from the beginning.

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }

    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      target.onLoadStarted(getPlaceholderDrawable());
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
  }

方法begin()里面会对图片的尺寸进行判断,只有在图片的宽高都有效的情况下才会去加载。图片尺寸准备好后会走方法onSizeReady()。

  @Override
  public synchronized void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    if (IS_VERBOSE_LOGGABLE) {
      logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
      return;
    }
    status = Status.RUNNING;

    float sizeMultiplier = requestOptions.getSizeMultiplier();
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    if (IS_VERBOSE_LOGGABLE) {
      logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    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);

    // This is a hack that's only useful for testing right now where loads complete synchronously
    // even though under any executor running on any thread but the main thread, the load would
    // have completed asynchronously.
    if (status != Status.RUNNING) {
      loadStatus = null;
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
  }

方法onSizeReady里会调用类Engine的方法load,如果缓存可用的话,那么load方法会直接获取缓存并返回。如果不可用,会走到类DecodeJob的方法runWrapped()里。方法runWrapped()主要是用于启动DataFetcherGenerator,DataFetcherGenerator有三个实现类,网络获取数据一般是SourceGenerator类,SourceGenerator中有DataFetcher,DataFetcher由很多子类:

Android图片加载框架Glide解析_第4张图片

从这些实现类的名字可以很容易的区别各自的作用,从网络获取数据是HttpUrlFetcher,查看它内部的方法loadDataWithRedirects可以看到内部是用HttpURLConnection来加载的。

我们是在SourceGenerator开始数据加载的,加载完成会回调方法onDataReady(),之后会走到DecodeJob的方法onDataFetcherReady()中,类DecodeJob做了很多事情,它的职责主要是解码从数据源获取到的数据。

接下去,层层调用,走到StreamBitmapDecoder的方法decode中,然后由Downsampler完成从InputStream到Bitmap的转换。

  @SuppressWarnings({"resource", "deprecation"})
  public Resource decode(InputStream is, int requestedWidth, int requestedHeight,
      Options options, DecodeCallbacks callbacks) throws IOException {
    Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
        + " mark()");

    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    bitmapFactoryOptions.inTempStorage = bytesForOptions;

    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    boolean isHardwareConfigAllowed =
      options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

    try {
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);
    }
  }

DecodeJob完成解码后会调用方法notifyComplete(),然后EngineJob接收到通知后再通知结果出去:

  void notifyCallbacksOfResult() {
    ResourceCallbacksAndExecutors copy;
    Key localKey;
    EngineResource localResource;
    synchronized (this) {
      stateVerifier.throwIfRecycled();
      if (isCancelled) {
        // TODO: Seems like we might as well put this in the memory cache instead of just recycling
        // it since we've gotten this far...
        resource.recycle();
        release();
        return;
      } else if (cbs.isEmpty()) {
        throw new IllegalStateException("Received a resource without any callbacks to notify");
      } else if (hasResource) {
        throw new IllegalStateException("Already have resource");
      }
      engineResource = engineResourceFactory.build(resource, isCacheable);
      // Hold on to resource for duration of our callbacks below so we don't recycle it in the
      // middle of notifying if it synchronously released by one of the callbacks. Acquire it under
      // a lock here so that any newly added callback that executes before the next locked section
      // below can't recycle the resource before we call the callbacks.
      hasResource = true;
      copy = cbs.copy();
      incrementPendingCallbacks(copy.size() + 1);

      localKey = key;
      localResource = engineResource;
    }

    listener.onEngineJobComplete(this, localKey, localResource);

    for (final ResourceCallbackAndExecutor entry : copy) {
      //通知结果
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
    decrementPendingCallbacks();
  }

SingleRequest接收到结果后显示出来:

  private synchronized void onResourceReady(Resource resource, R result, DataSource dataSource) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();
    status = Status.COMPLETE;
    this.resource = resource;

    if (glideContext.getLogLevel() <= Log.DEBUG) {
      Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
          + dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
          + LogTime.getElapsedMillis(startTime) + " ms");
    }

    isCallingCallbacks = true;
    try {
      boolean anyListenerHandledUpdatingTarget = false;
      if (requestListeners != null) {
        for (RequestListener listener : requestListeners) {
          anyListenerHandledUpdatingTarget |=
              listener.onResourceReady(result, model, target, dataSource, isFirstResource);
        }
      }
      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

      if (!anyListenerHandledUpdatingTarget) {
        Transition animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }

Target接收到结果,完成图片设置:

  @Override
  protected void setResource(Bitmap resource) {
    view.setImageBitmap(resource);
  }

这样,一个大致的整体流程就走完了。

四、缓存介绍

一般来说,图片缓存主要有内存缓存和磁盘缓存两种,二者使用的都是LRU算法,即最近最少使用算法。Glide中除了这两个缓存之外还有一种用弱引用实现的ActiveResources缓存。

具体在使用的过程中,图片资源获取成功后会写入磁盘缓存,ActiveResources缓存保存的是正在使用中的图片资源,当ActiveResources被移除时,资源会被放进内存缓存。获取缓存时,优先从ActiveResources中获取,没有的话从内存缓存中获取,最后从磁盘中读取。

下面是相关的代码:

1.缓存读取

  public synchronized  LoadStatus load(...) {

    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

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

    EngineResource active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    EngineResource cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    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 engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob decodeJob = decodeJobFactory.build(...);

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

2.写入缓存

磁盘缓存(SourceGenerator)

  private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      helper.getDiskCache().put(originalKey, writer);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

内存缓存()

EngineJob获取到结果后调用方法notifyCallbacksOfResult,然后调用Engine的方法onEngineJobComplete,这里会把图片资源放在ActiveResources中。

  @SuppressWarnings("unchecked")
  @Override
  public synchronized void onEngineJobComplete(
      EngineJob engineJob, Key key, EngineResource resource) {
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null) {
      resource.setResourceListener(key, this);

      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }

当ActiveResources被移除时,类Engine收到通知调用方法onResourceReleased()把资源放到内存缓存中。

  @Override
  public synchronized void onResourceReleased(Key cacheKey, EngineResource resource) {
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

五、相关参考

Glide最全解析

你可能感兴趣的:(Android图片加载框架Glide解析)