[Android] Glide核心逻辑解析

Glide核心逻辑解析


分三个模块进行,分别是Registry(注册器), Engine(请求),Cache(缓存)。

Registry模块

Registry的职责是注册Glide的所有组件,是Glide扩展的基石,目前在Registry中注册的组件有ModelLoaderFactory,ResourceDecoder, DataRewinder, Encoder。

我们看下个组件职责:

ModelLoaderFactory与ModelLoader

用于构造ModelLoader,ModelLoader会指定两种类型。第一个类型是Model,即要加载的数据类型,第二个类型是请求到的数据类型,比如常见的Http请求,那么它的数据类型是InputStream,下面看下定义。

public interface ModelLoader<Model, Data> {
      @Nullable
      LoadData<Data> buildLoadData(@NonNull Model model, int width, int height,
      @NonNull Options options);
      
      boolean handles(@NonNull Model model);
}

我们看下一个常见的实现,处理Http请求,并获取InputStream。

public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
    // ...
}

这个是Glide发起Http请求的ModelLoader,类似的请求还有很多,可以看到,这个ModelLoader需要处理的Model是GlideUrl,而我们一般传入的数据是String,这个时候Glide会通过一个MultiModelLoader进行过滤,最终将Model由String转成GlideUrl,使用这个ModelLoader进行处理。

如果要是好替换Glide的网络请求实现,就是通过替换这个ModelLoader实现的,具体可以看下Glide的其他集成库(intergration中的okhttp)。

@GlideModule
public final class OkHttpLibraryGlideModule extends LibraryGlideModule {
  @Override
  public void registerComponents(@NonNull Context context, @NonNull Glide glide,
      @NonNull Registry registry) {
    registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
  }
}

ResourceDecoder

ResourceDecoder的作用是将ModelLoader加载出来的数据,进行解码,解码成Bitmap,或者BitmapDrawable之类的。接口定义如下:

public interface ResourceDecoder<T, Z> {
    boolean handles(@NonNull T source, @NonNull Options options) throws IOException;
    
    Resource<Z> decode(@NonNull T source, int width, int height, @NonNull Options options)
      throws IOException;
}      
      

Glide中常用的Decoder有两个,其他都是将这两个Decoder进行包装,它们分别是ByteBufferBitmapDecoder和StreamBitmapDecoder。
我们看下StreamBitmapDecoder的实现:
将InputStream转成Bitmap。

public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {

}

解码过程中也有一些细节问题需要注意:

  • 如果要使用ARGB_565进行解码图片,最好使用GlideModule中进行配置默认的RequestOptions。但是就是配置了使用ARGB_565进行加载,Glide根据文件头判断这个图片是否含有alpha通道,如果该图片是png的,那么仍然会使用ARGB_8888进行解码,所以为了获取最小的内存占用,需要和后端尽量返回jpeg的图片。
  • Android O以后的设备会默认使用HARDWRAE这个格式进行解码,这种格式

DataRewinder

Rewinder担任的是ModelLoader到ResourceDecoder的桥梁的角色,DecodeJob将ModelLoader获得的数据,构造出DataRewinder,然后使用Rewinder将数据传给ResourceDecoder进行解码。
下面看下DataRewinder接口定义。

public interface DataRewinder<T> {
}

Encoder

Encoder的作用是将数据转换成文件,用来配合Glide硬盘缓存。

public interface Encoder<T> {
  
  boolean encode(@NonNull T data, @NonNull File file, @NonNull Options options);
}

最常用的实现是StreamEncoder,使用StreamEncoder将InputSteam转换成文件,完成文件缓存。

StreamEncoder中使用ArrayPool将字节缓冲区进行缓存,避免了每次都创建字节数组(缓冲区)。

public class StreamEncoder implements Encoder<InputStream> {
  private static final String TAG = "StreamEncoder";
  private final ArrayPool byteArrayPool;

  public StreamEncoder(ArrayPool byteArrayPool) {
    this.byteArrayPool = byteArrayPool;
  }

  @Override
  public boolean encode(@NonNull InputStream data, @NonNull File file, @NonNull Options options) {
    // 将图片编码成图片
    // 使用InputStream进行编码图片,不用每次使用new byte初始化缓冲区
    // 缓冲区默认大小64KB
    final byte[] buffer = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    boolean success = false;
    OutputStream os = null;
    try {
      os = new FileOutputStream(file);
      int read;
      while ((read = data.read(buffer)) != -1) {
        os.write(buffer, 0, read);
      }
      os.close();
      success = true;
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to encode data onto the OutputStream", e);
      }
    } finally {
      if (os != null) {
        try {
          os.close();
        } catch (IOException e) {
          // Do nothing.
        }
      }
      byteArrayPool.put(buffer);
    }
    return success;
  }
}

RequetBuilder#into()

Glide的加载流程起源于RequestBuilder的into()方法,下面逐层对into()方法进行分析。

指定加载的Target,开始执行请求

首先清除这个Target上的原有请求(如果请求相同,并且可以使用内存缓存除外),然后使用RequestManager记录此请求引用并启动。
下面分析加载的起点into()。

Glide.with(context).load(url).into(imageview);
  private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @NonNull RequestOptions options) {
    Util.assertMainThread();
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    options = options.autoClone();
    // 根据之前的Option,创建Request
    Request request = buildRequest(target, targetListener, options);

    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启动请求,并进行记录
    requestManager.track(target, request);

    return target;
  }

buildRequest的过程

buildRequest 中会创键多个请求,比如如果设置了请求错误的Request,或者缩略图Request会一起创键。

Glide.with(this).load(URL)
                .apply(new RequestOptions()
                        .diskCacheStrategy(DiskCacheStrategy.NONE)
                        .skipMemoryCache(true))
                // 如果图片错误,请求这个资源
                .error(Glide.with(this).load(errorUrl))
                .into(img2);
// 递归的创键请求对象
 private Request buildRequestRecursive(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      RequestOptions requestOptions) {

    // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
    // 创建当网络请求失败时的Request
    ErrorRequestCoordinator errorRequestCoordinator = null;
    if (errorBuilder != null) {
      errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
      parentCoordinator = errorRequestCoordinator;
    }

    // 创建缩略图Request
    Request mainRequest =
        buildThumbnailRequestRecursive(
            target,
            targetListener,
            parentCoordinator,
            transitionOptions,
            priority,
            overrideWidth,
            overrideHeight,
            requestOptions);

    if (errorRequestCoordinator == null) {
      return mainRequest;
    }

    int errorOverrideWidth = errorBuilder.requestOptions.getOverrideWidth();
    int errorOverrideHeight = errorBuilder.requestOptions.getOverrideHeight();
    if (Util.isValidDimensions(overrideWidth, overrideHeight)
        && !errorBuilder.requestOptions.isValidOverride()) {
      errorOverrideWidth = requestOptions.getOverrideWidth();
      errorOverrideHeight = requestOptions.getOverrideHeight();
    }

    // 错误Request和主Request组合起来
    Request errorRequest = errorBuilder.buildRequestRecursive(
        target,
        targetListener,
        errorRequestCoordinator,
        errorBuilder.transitionOptions,
        errorBuilder.requestOptions.getPriority(),
        errorOverrideWidth,
        errorOverrideHeight,
        errorBuilder.requestOptions);
    errorRequestCoordinator.setRequests(mainRequest, errorRequest);
    return errorRequestCoordinator;
  }

此时如果当前的状态不是Pause的话,那么直接调用Request#begin()

 /**
   * Starts tracking the given request.
   */
  public void runRequest(@NonNull Request request) {
    requests.add(request);
    if (!isPaused) {
      request.begin();
    } else {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      pendingRequests.add(request);
    }
  }

Request的实现是SingleRequest类,我们看下SingleRequest的begin()如何执行


  @Override
  public 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;
    }

    // 如果Request已经运行,再次调用begin()会产生异常
    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 {
      // 使用Callback进行获取
      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));
    }
  }

SizeDeterminer获取控件尺寸

这里的逻辑是如果Model不为null,那么就去将状态改成WATIINT_FOR_SIZE,然后使用ViewTarget的getSize()方法去获取尺寸。

这里获取尺寸的方法逻辑如下,如果该View的LayoutParams宽高都不是MATCH_PARENT或者WRAP_CONTENT(必须已经发生LayoutRequested),而是具体的数值,那么直接使用这个尺寸,否则,会使用ViewTreeObserver中的onPreDraw()进行监听尺寸。

void getSize(@NonNull SizeReadyCallback cb) {
      int currentWidth = getTargetWidth();
      int currentHeight = getTargetHeight();
      if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
        return;
      }

      // We want to notify callbacks in the order they were added and we only expect one or two
      // callbacks to be added a time, so a List is a reasonable choice.
      if (!cbs.contains(cb)) {
        cbs.add(cb);
      }
      // 添加onPredraw监听
      if (layoutListener == null) {
        ViewTreeObserver observer = view.getViewTreeObserver();
        layoutListener = new SizeDeterminerLayoutListener(this);
        observer.addOnPreDrawListener(layoutListener);
      }
    }

从onPreDraw中获取到尺寸后, 会开始Glide真正的图片加载流程。
Glide一系列加载流程起源于Engine类,Engine类是个单例,存在于GlideContext中,我们看下Engine的初始化。

// GlideBuild #build()
    //....
   if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }

    //....

这里有个技巧,如果一个ImageView的大小是WRAP_CONTENT,并且没有发生layoutRquested,即没有通过addView()之类的方法添加,那么会使用屏幕的宽或高(最大的)进行采样,而不是控件的大小。解决办法是强制使用waitForLayout()

Glide.with(this).load(URL).into(img1).waitForLayout();

或者使用override()指定宽高。

开始加载图片

 /**
   * A callback method that should never be invoked directly.
   */
  @Override
  public void onSizeReady(int width, int height) {
    //...
    // 获取到尺寸以后,将尺寸传给Engine,进行加载资源
    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);

    //...
  }

Engine加载流程(核心)

Engine的核心是load方法,这个方法也是Glide的核心,下面我们重点分析下这个方法。

  1. 从当前的的弱引用中进行查找
  2. 从内存缓存中进行查找
  3. 从已经开始的网络请求中进行查找
  4. 创建一个新的网络请求

参考 http://frodoking.github.io/2015/10/10/android-glide


 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) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    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 (Log.isLoggable(TAG, Log.VERBOSE)) {
        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 (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

      // 网络请求是否存在
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }


    // 创建一个网络请求对象
    //http://www.lightskystreet.com/2015/10/12/glide_source_analysis/
    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);
    // 开始获取资源并解码
    engineJob.start(decodeJob);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }


在方法前面会检测是否能够使用内存缓存,Glide的内存缓存分为两种,一种是ActiveResources使用WeakReference存储,一种是MemoryCache使用LRU策略进行存储。

如果能直接取出来缓存的话,是不需要进行解码的,直接回调onResourceReady(),如果缓存没命中,这时Glide会检测是否有正在进行的请求,如果有,直接添加个回调然后返回。

如果以上都没有,那么会直接启动新的加载流程,代码如下:

public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    // 从磁盘缓存中取数据
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

EngineJob的start()方法,会开始一个新的解码流程。
会先在磁盘线程池加载磁盘缓存,如果没有,那么使用网络线程池加载。

DecodeJob的run()如下:

private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      // 根据状态,获取下一个处理者
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        // 如果当前状态需要网络请求,那么将网络请求调度到别的线程池执行
        reschedule();
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }

    // Otherwise a generator started a new load and we expect to be called back in
    // onDataFetcherReady.
  }

核心逻辑是getNextStage()getNextGenerator(),这两个方法描述的是当前加载过程的状态转换。

getNextGenerator()方法会根据当前状态创建对应的Generator。

// 根据状态切换,生成DataGenerator
  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        // 带尺寸的缓存
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        // 不带尺寸的缓存
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        // 网络请求
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }

我们看下这个Stage的获取,这个可以理解为Glide的核心了。

/**
   * DecodeJob的状态切换,先从带尺寸缓存->缓存->网络->完成状态
   */
  private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }

状态切换: 带尺寸缓存->缓存->网络->完成状态
每个状态均使用DiskCacheStragey进行判断,如果DiskCacheStragey不支持直接返回下一个状态。

先看下DataFetcherGenerator的定义。

interface DataFetcherGenerator {

// 启动DataFetcher进行加载图片,如果启动,返回true,如果没有启动,返回false
  boolean startNext();
  
  void cancel();
  
}

从上面生成DataFetcherGenerator的方法可以看出,DataFetcherGenerator的作用就是发起加载流程,结束之后使用回调返回结果,回调定义如下:

  interface FetcherReadyCallback {

    /**
     * Requests that we call startNext() again on a Glide owned thread.
     */
    void reschedule();

    /**
     * Notifies the callback that the load is complete.
     *
     * @param sourceKey The id of the loaded data.
     * @param data The loaded data, or null if the load failed.
     * @param fetcher The data fetcher we attempted to load from.
     * @param dataSource The data source we were loading from.
     * @param attemptedKey The key we were loading data from (may be an alternate).
     */
    void onDataFetcherReady(Key sourceKey, @Nullable Object data, DataFetcher<?> fetcher,
        DataSource dataSource, Key attemptedKey);

    /**
     * Notifies the callback when the load fails.
     *
     * @param attemptedKey The key we were using to load (may be an alternate).
     * @param e The exception that caused the load to fail.
     * @param fetcher The fetcher we were loading from.
     * @param dataSource The data source we were loading from.
     */
    void onDataFetcherFailed(Key attemptedKey, Exception e, DataFetcher<?> fetcher,
        DataSource dataSource);
  }

下面看下SourceGenerator的实现,其他的实现大多类似,都是获取LoadData,然后使用LoadData的DataFetcher进行加载图片。


class SourceGenerator implements DataFetcherGenerator,
    DataFetcher.DataCallback<Object>,
    DataFetcherGenerator.FetcherReadyCallback {
    
  @Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      // reschedule调用过来的,有CacheData,先写入文件缓存,在从缓存中读出来,最后返回
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

    
}

SourceGenerator的逻辑大概是这样,先通过Registgry获取DataFetcher,然后使用DataFetcher(这里是HttpUrlFetcher)加载,
如果加载成功会调用onDataReady(),我们看下SourceGenerator的 onDataReady调用。

@Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    // 如果该数据能够缓存,那么重新调度到Source线程池,进行缓存
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

rechedule()调用之后,会继续调用startNext(),会调用到cacheData()

 private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> 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();
    }

    // 重新生成一个Generator,这个DataCacheGenerator从文件中读出,然后调用onResourceReady()回调
    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }
 @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    // This data fetcher will be loading from a File and provide the wrong data source, so override
    // with the data source of the original fetcher
    cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
  }

回调到DecodeJob的onDataFetcherReady()

@Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      // 收到数据以后,如果不是Source线程,继续reschedule,然后更新状态为DECODE_DATA
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        // 开始解码
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }

看下这个decodeResourceWithList()

private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Retrieved data", startFetchTime,
          "data: " + currentData
              + ", cache key: " + currentSourceKey
              + ", fetcher: " + currentFetcher);
    }
    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();
    }
  }

中间会回调到DecodeJob中的onResourceDecoded(),我们看下这个方法的实现,这个方法的作用是调用RequestOptions中的Transform处理图片,然后将ResourceCache的Key和Encode准备好(放在deferEncoderManager中),稍后进行写入缓存。


  // 资源解码后的回调
  @Synthetic
  @NonNull
  <Z> Resource<Z> onResourceDecoded(DataSource dataSource,
      @NonNull Resource<Z> decoded) {
    @SuppressWarnings("unchecked")
    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
    Transformation<Z> appliedTransformation = null;
    Resource<Z> transformed = decoded;
    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
      // 如果不是使用的本地Transformation缓存,那么将资源Transform
      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
      transformed = appliedTransformation.transform(glideContext, decoded, width, height);
    }
    // TODO: Make this the responsibility of the Transformation.
    // 如果资源被重新创建,那么回收之前的
    if (!decoded.equals(transformed)) {
      decoded.recycle();
    }

    // 是否进行Encode?
    final EncodeStrategy encodeStrategy;
    final ResourceEncoder<Z> encoder;
    // 之前在SourceGenerator中已经生成过DATA_CACHE了,这里会判断是否需要生成RESOURCE_CACHE(带尺寸的缓存)
    if (decodeHelper.isResourceEncoderAvailable(transformed)) {
      encoder = decodeHelper.getResultEncoder(transformed);
      encodeStrategy = encoder.getEncodeStrategy(options);
    } else {
      encoder = null;
      encodeStrategy = EncodeStrategy.NONE;
    }

    Resource<Z> result = transformed;
    boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
    // 是否使用Resource缓存
    if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
        encodeStrategy)) {
      if (encoder == null) {
        throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
      }
      final Key key;
      // 根据Encode策略,创建不同的缓存Key
      switch (encodeStrategy) {
        case SOURCE:
          key = new DataCacheKey(currentSourceKey, signature);
          break;
        case TRANSFORMED:
          key =
              new ResourceCacheKey(
                  decodeHelper.getArrayPool(),
                  currentSourceKey,
                  signature,
                  width,
                  height,
                  appliedTransformation,
                  resourceSubClass,
                  options);
          break;
        default:
          throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
      }

      LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
      deferredEncodeManager.init(key, encoder, lockedResult);
      result = lockedResult;
    }
    return result;
  }

接下来的逻辑是从Registry中寻找能够解码的Decoder,然后从Rewinder中获取数据,逐个尝试使用Decoder进行解码。
这里的Decoder实际上是StreamDecoder,实际解码类是Downsample。
一会在分析decoder实现,现在看下整体流程。

@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<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);
        }
        // Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but
        // instead log and continue. See #2406 for an example.
      } catch (IOException | RuntimeException | OutOfMemoryError e) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "Failed to decode data for " + decoder, e);
        }
        exceptions.add(e);
      }

      if (result != null) {
        break;
      }
    }

    if (result == null) {
      throw new GlideException(failureMessage, new ArrayList<>(exceptions));
    }
    return result;
  }

解码完成后,调用notifyEncodeAndRelease()通知主线程处理图片,如果还需要处理encode的话,进行处理。

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
    if (resource instanceof Initializable) {
      ((Initializable) resource).initialize();
    }

    Resource<R> result = resource;
    LockedResource<R> lockedResource = null;
    if (deferredEncodeManager.hasResourceToEncode()) {
      lockedResource = LockedResource.obtain(resource);
      result = lockedResource;
    }

    // 通知主线程回调,加载图片
    notifyComplete(result, dataSource);

    // 更新状态为编码
    stage = Stage.ENCODE;
    // 处理DeferredEncode容器中需要Encode的资源
    try {
      if (deferredEncodeManager.hasResourceToEncode()) {
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    // Call onEncodeComplete outside the finally block so that it's not called if the encode process
    // throws.
    onEncodeComplete();
  }

主线程收到回调,会回调下面的方法。

// 主线程执行回调
  @Synthetic
  void handleResultOnMainThread() {
    stateVerifier.throwIfRecycled();
    if (isCancelled) {
      resource.recycle();
      release(false /*isRemovedFromQueue*/);
      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");
    }
    // 将Resource进行包装,方便对使用此资源进行计数s
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;

    // Hold on to resource for duration of request so we don't recycle it in the middle of
    // notifying if it synchronously released by one of the callbacks.
    engineResource.acquire();
    listener.onEngineJobComplete(this, key, engineResource);

    // 逐个回调各Callback
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = cbs.size(); i < size; i++) {
      ResourceCallback cb = cbs.get(i);
      if (!isInIgnoredCallbacks(cb)) {
        engineResource.acquire();
        cb.onResourceReady(engineResource, dataSource);
      }
    }
    // Our request is complete, so we can release the resource.
    engineResource.release();

    // 清理资源,并且放入Cache中
    release(false /*isRemovedFromQueue*/);
  }

cb.onResourceReady()完成后,Glide加载图片的工作就大部分完成了。剩下的就是通知Traget的回调了。

Glide 缓存

Glide缓存分成三个部分,分别是ArrayPool,BitmapPool,LruResourceCache。

LruResourceCache

Glide 中最直接的内存缓存,我们看下Key的实现。

class EngineKey implements Key {
  private final Object model;
  private final int width;
  private final int height;
  private final Class<?> resourceClass;
  private final Class<?> transcodeClass;
  private final Key signature;
  private final Map<Class<?>, Transformation<?>> transformations;
  private final Options options;
  private int hashCode;
  //......
  
}

这是Key的所有字段,可以看出,出了与Model有关,还有Transform之类的因素,这也是Glide的高效之处,直接可以将处理过的图片进行缓存。
LruResourceCache在Engine#load()中进行获取,即如果图片存在LruResourceCache中,会直接返回,不会创建DecodeJob进行加载。

LruResourceCache是Glide自实现的Lru容器,存放的数据是资源的包装类Resource<?>。

/**
 * An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s.
 */
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
  private ResourceRemovedListener listener;

  /**
   * Constructor for LruResourceCache.
   *
   * @param size The maximum size in bytes the in memory cache can use.
   */
  public LruResourceCache(long size) {
    super(size);
  }

  @Override
  public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) {
    this.listener = listener;
  }

  @Override
  protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
    if (listener != null && item != null) {
      listener.onResourceRemoved(item);
    }
  }

  @Override
  protected int getSize(@Nullable Resource<?> item) {
    if (item == null) {
      return super.getSize(null);
    } else {
      return item.getSize();
    }
  }

  @SuppressLint("InlinedApi")
  @Override
  public void trimMemory(int level) {
    if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
      // Entering list of cached background apps
      // Evict our entire bitmap cache
      clearMemory();
    } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
        || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
      // The app's UI is no longer visible, or app is in the foreground but system is running
      // critically low on memory
      // Evict oldest half of our bitmap cache
      trimToSize(getMaxSize() / 2);
    }
  }
}

BitmapPool

BitmapPool是Glide内存缓存的重要成员。这个BitmapPool的作用是提供一个空的Bitmap供Glide解码使用(Config.inBitmap属性)。
同时,也可以作为提供Bitmap的容器,如Transformation中的使用。

Bitmap的获取通过一个LruPoolStrage接口实现,用于区分Android版本实现。

interface LruPoolStrategy {
  void put(Bitmap bitmap);

  @Nullable
  Bitmap get(int width, int height, Bitmap.Config config);

  @Nullable
  Bitmap removeLast();

  String logBitmap(Bitmap bitmap);

  String logBitmap(int width, int height, Bitmap.Config config);

  int getSize(Bitmap bitmap);
}

Strategy的创建策略如下

 // 创建Lru缓存策略
  private static LruPoolStrategy getDefaultStrategy() {
    final LruPoolStrategy strategy;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      strategy = new SizeConfigStrategy();
    } else {
      strategy = new AttributeStrategy();
    }
    return strategy;
  }

如果api大于19,那么使用SizeConfigStrategy,这些版本只要满足被回收的bitmap内存占用大于要解码的bitmap即可。
SizeConfigStrategy查找合适的Bitmap逻辑如下

// 查找较大尺寸的Bitmap通过TreeMap进行实现的
private Key findBestKey(int size, Bitmap.Config config) {
    Key result = keyPool.get(size, config);
    for (Bitmap.Config possibleConfig : getInConfigs(config)) {
      NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
      Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
      if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
        if (possibleSize != size
            || (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
          keyPool.offer(result);
          result = keyPool.get(possibleSize, possibleConfig);
        }
        break;
      }
    }
    return result;
  }

如果api小于等于19,那么只有在bitmap尺寸(width,height)和Config都相同的时候才能够被复用,所以该BitmapPool的实现较为简单,只是一个KeyPool和GroupLinkedMap。


  @Override
  public Bitmap get(int width, int height, Bitmap.Config config) {
    final Key key = keyPool.get(width, height, config);

    return groupedMap.get(key);
  }

BitmapPool再Downsample中有频繁的使用,Downsample类是Decoder的核心。
解码前专门设置inBitmap属性。

private static void setInBitmap(
      BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
    @Nullable Bitmap.Config expectedConfig = null;
    // Avoid short circuiting, it appears to break on some devices.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      if (options.inPreferredConfig == Config.HARDWARE) {
        return;
      }
      // On API 26 outConfig may be null for some images even if the image is valid, can be decoded
      // and outWidth/outHeight/outColorSpace are populated (see b/71513049).
      expectedConfig = options.outConfig;
    }

    if (expectedConfig == null) {
      // We're going to guess that BitmapFactory will return us the config we're requesting. This
      // isn't always the case, even though our guesses tend to be conservative and prefer configs
      // of larger sizes so that the Bitmap will fit our image anyway. If we're wrong here and the
      // config we choose is too small, our initial decode will fail, but we will retry with no
      // inBitmap which will succeed so if we're wrong here, we're less efficient but still correct.
      expectedConfig = options.inPreferredConfig;
    }
    // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
    options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
  }

ArrayPool

ArrayPool也是Glide的内存缓存,它的实现类是LruArrayPool,它的作用是在Glide解码图片和解析文件时提供缓冲区(byte[])的复用逻辑,这个Volley的代码里也有,意思就是我们不需要在每次读取文件的时候手动new byte[]了。

java中默认的缓冲区是4kb,如果频繁的申请这4kb内存,势必会造成内存抖动,从而影响流畅性。

我们先看下ArrayPool的使用,在Downsample类中。

  public Resource<Bitmap> 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 decodeFormat = options.get(DECODE_FORMAT);

    // 解码策略
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    // 是否使用Bitmap硬件缓存
    boolean isHardwareConfigAllowed =
      options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

    try {
      // 将InputStream解码成Bitmap
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      // Option对象进行复用
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);
    }
  }

其中byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);就是从缓存中获取缓冲区。

ArrayPool的实现与BitmapPool类似,只不过变成了获取byte[]或者int[],这里不多分析了。

总结

通过阅读Glide源码,收获还是很多的,下面记录下Glide的一些使用技巧,帮助我们更好地使用Glide。

  • Glide推荐使用ARGB_565进行解码,需要在DefaultRequestOptions中进行配置,但这不是绝对的,如果你的图片是png或者其他含有alpha的图片,那么仍然会使用ARGB_8888进行解码,所以如果想省点内存的话,最好限制下图片格式。

  • Android O以后,会优先使用HARDWARE格式进行解码,但这些图不能对像素进行操作,需要小心。

  • 加载图片的大小最好指明尺寸,如果使用WRAP_CONTENT的ImageView加载图片,那么有可能图片会按照屏幕的最大尺寸进行采样,从而造成内存浪费。解决办法就是使用具体尺寸或者MATCH_PARENT,或者使用override()指明图片大小。

  • 如果项目代码中要初始化Bitmap,不妨试试从BitmapPool中进行获取Glide.get(context).getBitmapPool().get(...),如果有缓存的Bitmap,那么就不用创建新的了。

  • 使用Glide加载的图片,如果没有设置skipMemory(),那么不要调用Bitmap的recycler()(该图片已在内存缓存中),否则其他模块使用会造成Crash。

参考
https://damondaliy.com/?p=51
https://juejin.im/entry/5aa11f826fb9a028d663bfe0
http://www.voidcn.com/article/p-thodukjp-brq.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值