美文网首页安卓
图片框架 - Glide缓存机制解析

图片框架 - Glide缓存机制解析

作者: Stan_Z | 来源:发表于2020-07-21 19:16 被阅读0次

本篇文章主要来梳理Glide的缓存策略。

先给出客户端配置举例:

Glide.with(this)
     .load(Constants.dynamicWebpUrl)
     .apply(new RequestOptions()
             .skipMemoryCache(false)//运行内存缓存
             .diskCacheStrategy(DiskCacheStrategy.ALL)) //配置缓存策略
     .into(mImageView);

这个配置包含了内存缓存、磁盘缓存(原始数据和转换后数据)。

整个Glide图片获取的方式有三种:内存缓存、 磁盘缓存 、网络请求。

一、内存缓存

从Engine load方法作为入口开始:

Engine.java

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) {
  …

//通过多个参数最终生成一个唯一资源标识的缓存key对象EngineKey
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
     resourceClass, transcodeClass, options);
  //先获取loadFromActiveResources
  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
  if (active != null) {
    cb.onResourceReady(active, DataSource.MEMORY_CACHE);
  ...
  }

  //再获取loadFromCache
  EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
  if (cached != null) {
    cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
  ...
  }

  //最后获取EngineJob,然后走engineJob.start
  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  if (current != null) {
    current.addCallback(cb);
  ...
  }
  ...
  jobs.put(key, engineJob);
  engineJob.addCallback(cb);
  engineJob.start(decodeJob);
  ...
  return new LoadStatus(cb, engineJob);
}

一个个来看:

1.1 loadFromActiveResources 活跃资源内存缓存

Engine.java

private final ActiveResources activeResources;//在Engine构造方法中初始化
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
  //这就是通过RequestOptions配置的skipMemoryCache,但isMemoryCacheable与skipMemoryCache是相反的
  if (!isMemoryCacheable) {
    return null;
  }
  EngineResource<?> active = activeResources.get(key);
  if (active != null) {
    active.acquire();
  }
  return active;
}

ActiveResources.java

final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();//弱引用缓存
EngineResource<?> get(Key key) {
  ResourceWeakReference activeRef = activeEngineResources.get(key);
  if (activeRef == null) {
    return null;
  }
  EngineResource<?> active = activeRef.get();
  if (active == null) {
    cleanupActiveReference(activeRef);
  }
  return active;
}

那在什么时候缓存的?

ActiveResources.java

void activate(Key key, EngineResource<?> resource) {
  ResourceWeakReference toPut =
      new ResourceWeakReference(
          key,
         resource,
         getReferenceQueue(),
         isActiveResourceRetentionAllowed);
  ResourceWeakReference removed = activeEngineResources.put(key, toPut);//该方法对应了map唯一put的地方
  if (removed != null) {
    removed.reset();
  }
}

调用点:从loadFromCache成功获取、通过EngineJob从磁盘或者网络成功获取。

也就是说从其他途径成功获取到当前正在加载的图片资源,则会缓存到活跃内存缓存,形式是弱引用对象。如果loadFromActiveResources能成功获取,则直接返回。

1.2 loadFromCache

loadFromActiveResources获取为null,则尝试loadFromCache

Engine.java

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
  if (!isMemoryCacheable) {
    return null;
  }
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire();
   activeResources.activate(key, cached);
  }
  return cached;
}

Engine.java

private EngineResource<?> getEngineResourceFromCache(Key key) {
 //cache是 LruResourceCache extends LruCache
 Resource<?> cached = cache.remove(key);
  final EngineResource<?> result;
  if (cached == null) {
    result = null;
  } else if (cached instanceof EngineResource) {
    // Save an object allocation if we've cached an EngineResource (the typical case).
   result = (EngineResource<?>) cached;
  } else {
    result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
  }
  return result;
}

那在什么时候缓存的?

Engine.java

public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
  Util.assertMainThread();
  activeResources.deactivate(cacheKey);
  if (resource.isCacheable()) {
    cache.put(cacheKey, resource);
  } else {
    resourceRecycler.recycle(resource);
  }
}

调用点:active弱引用被回收会转到cache、EngineResource调用release的时候。

总结下结论:

取的逻辑:加载图片先从activeResources取,没有再去MemoryCache取,再没有就起EngineJob线程走磁盘或者网络获取。
存的逻辑:当前加载到ImageView的图片会先被缓存到activeResources,activeResources被remove的会被加入到MemoryCache。

二、磁盘缓存

内存获取不到会起个线程即DecodeJob来完成接下来的任务。
DecodeJob.java

public void run() {
  ...
    runWrapped();
  ...
}

private void runWrapped() {
  switch (runReason) {
    case INITIALIZE: //首次进入走INITIALIZE
     stage = getNextStage(Stage.INITIALIZE);//1
     currentGenerator = getNextGenerator();//2
     runGenerators();//3
     break;
   case SWITCH_TO_SOURCE_SERVICE:
      runGenerators();
     break;
   case DECODE_DATA:
      decodeFromRetrievedData();
     break;
   default:
      throw new IllegalStateException("Unrecognized run reason: " + runReason);
  }
}

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

注:
stage:

  • INITIALIZE //初始阶段
  • RESOURCE_CACHE //从磁盘缓存中读取转换之后的图片
  • DATA_CACHE 从磁盘缓存中读取原始图片
  • SOURCE //从网络上加载
  • ENCODE //在成功加载后对转换的资源进行编码
  • FINISHED //完成阶段

diskCacheStrategy:

  • ALL //缓存原始数据和转换后的数据
  • NONE //不缓存
  • DATA //原始数据,未经过解码或者转换
  • RESOURCE //缓存经过解码的数据
  • AUTOMATIC //根据EncodeStrategy和DataSource等条件自动选择合适的缓存方式,

这里不同的diskCacheStrategy有不同的策略:比如:

 public static final DiskCacheStrategy ALL = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE; //远程网络请求
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) { 
      //非转换资源缓存 和 非内存缓存
      return dataSource != DataSource.RESOURCE_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
    }

    @Override
    public boolean decodeCachedResource() {
      return true; //支持转换资源解码
    }

    @Override
    public boolean decodeCachedData() {
      return true; //支持原始资源解码
    }
  };

根据getNextStage去匹配到对应的stage,然后根据stage匹配对应的Generator,而generator是具体处理类。

interface DataFetcherGenerator
实现类:

  • ResourceCacheGenerator 原始数据转换后的缓存
  • DataCacheGenerator 原始数据缓存
  • SourceGenerator 新资源获取

默认整个获取顺序就是:ResourceCache > DataCache > Source,当然也可以自定义diskCacheStrategy来调整。

三、网络请求

磁盘缓存中转换资源和原始资源都获取不到,则走网络请求获取。
SourceGenerator.java

public boolean startNext() {
...
  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;
}

这里举例:OkHttpStreamFetcher

public void loadData(@NonNull Priority priority,
                    @NonNull final DataCallback<? super InputStream> callback) {
    Request request = null;
   if (url != null) {
        Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
       if (url.getHeaders() != null) {
            for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
                if (headerEntry != null) {
                    String key = headerEntry.getKey();
                   requestBuilder.addHeader(key, headerEntry.getValue());
               }
            }
            request = requestBuilder.build();
       }
    }
    this.callback = callback;
   if (client != null && request != null) {
        call = client.newCall(request);
       if (call != null) {
            call.enqueue(this);
       }
    }
}

@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "OkHttp failed to obtain result", e);
   }

    if (callback != null) {
        callback.onLoadFailed(e);
   }
}

@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
    if (response == null || response.body() == null) {
        return;
   }

    Log.d(TAG, "ImageLoader- okhttp onResponse timeMs:" + response.getNetworkTimeMs());
   responseBody = response.body();
   if (response.isSuccessful()) {
        long contentLength = responseBody.contentLength();
       stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
       if (callback != null) {
            callback.onDataReady(stream);
       }
    } else {
        if (callback != null) {
            callback.onLoadFailed(new HttpException(response.message(), response.code()));
       }
    }
}

这里网络请求成功才会回调callback.onDataReady(stream),stream是数据流,callback是SourceGenerator

SourceGenerator.java

public void onDataReady(Object data) {
  DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
  //这里满足diskCacheStrategy.isDataCacheable开始走缓存流程
  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);
  }
}

cb.reschedule()这里cb是DecodeJob

DecodeJob.java

public void reschedule() {
  runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
  callback.reschedule(this);
}

这个callback是EngineJob

DecodeJob.java

@Override
public void reschedule(DecodeJob<?> job) {
  // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
  // up.
  getActiveSourceExecutor().execute(job);
}

这里绕了一圈又回到DecodeJob的run方法,这次runReason为SWITCH_TO_SOURCE_SERVICE,DecodeJob执行reschedule()时赋值的

private void runWrapped() {
  switch (runReason) {
    case INITIALIZE:
      stage = getNextStage(Stage.INITIALIZE);
     currentGenerator = getNextGenerator();
     runGenerators();
     break;
   case SWITCH_TO_SOURCE_SERVICE:
      runGenerators();
     break;
   case DECODE_DATA:
      decodeFromRetrievedData();
     break;
   default:
      throw new IllegalStateException("Unrecognized run reason: " + runReason);
  }
}

走SWITCH_TO_SOURCE_SERVICE,对应 runGenerators();

debug一下,发现这个generator仍然是SourceGenerator

这次进来是走的cache了

private void cacheData(Object dataToCache) {
    GlideUtils.getCallers(40);
   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();
   }
    sourceCacheGenerator =
            new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}

最终由cacheData方法完成磁盘缓存。

最后总结一张缓存逻辑图:

Glide缓存逻辑

这里简单总结下:

取逻辑:

内存 > 磁盘 > 网络请求

  • 内存:
    上次刚被加载的资源(activeResources) > (最近被加载的资源)lruCache。

  • 磁盘:
    如果有主动设置DiskCacheStrategy,则按设置来。默认是取转换之后的资源(ResourceCache) > (DataCache)原始资源

  • 网络请求:
    走网络请求获取图片资源流。

存逻辑
  • 内存:
    当前被加载的图片资源存到activeResources中,下次加载资源切换,当前activeResources会remove然后被转移到lruCache。

  • 磁盘:
    网络请求成功之后,获取到资源流,然后看DiskCacheStrategy是否支持磁盘缓存,如果支持,会通过回调在SourceGenerator中通过cacheData进行资源缓存。

相关文章

网友评论

    本文标题:图片框架 - Glide缓存机制解析

    本文链接:https://www.haomeiwen.com/subject/riywkktx.html