Glide 缓存机制研究及同命名图片的替换回显

背景:

相册-图片后处理场景,需要先展示一张原图,同时后台对图片进行算法优化,完成优化之后无缝替换原图展示,同时保证后续都展示算法优化后的图片

图片加载采用 Glide 库实现

画重点:

  1. 相册场景:意味着存在图片缓存的复用

  2. 无缝替换:切换过程要很丝滑,不能有闪烁等现象

  3. 后续展示优化后的图片:说明后续缓存要复用算法优化后的,不能用原图

  4. 因为是对图片进行优化,所以原图和算法优化后的图片文件名一样

最开始的想法:

步骤:

  1. 先加载原图展示

  2. 等图片算法优化,重新调用 glide 加载一遍

结果:

  1. 重新加载之后,还是原图效果

Glide 缓存机制

基于上述原因,专门研究了下 Glide 的缓存机制,重点是缓存的读取这一块

四级缓存机制

Glide 采用四级缓存机制,主要分成两个模块,内存缓存,硬盘缓存

  • 内存缓存:防止应用重复将图片数据读到内存中
  • 硬盘缓存:防止应用重复从网络或者其他地方重复下载和读取数据
  • 缓存Key:既然有缓存,那肯定需要对应的缓存Key,通过按照一定规则生成缓存key之后,根据key从缓存集中取出对应缓存复用

这里先简单讲下四级缓存机制,然后再跟着源码过一遍

内存缓存-lru缓存:

Glide 在内存中维持的一个缓存 map,通过 lru 算法实现内存管理

private final LinkedHashMap<T, Y> cache = new LinkedHashMap<T, Y>(100, 0.75f, true);
​
T:缓存Key
Y:缓存资源

内存缓存-弱引用缓存:

Glide 在内存中维持的一个弱引用缓存 map,由于是弱引用,这就意味着内存不足的时候先销毁这个

private final Map<Key, WeakReference<EngineResource<?>>> activeResources;

硬盘缓存-转换过缓存:

Glide 在硬盘上的缓存,该缓存是经过各种变换后的缓存,不是原始图片

硬盘缓存-原始图片缓存:

Glide 在硬盘上的缓存,该缓存是原始图片缓存

Glide 缓存加载流程:

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
        DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
        Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();
​
    final String id = fetcher.getId();
    // 1. 生成 缓存key,可以看出 key 的生成用到很多参数,比如 id(图片路径)、signature、width、height,还有 transformation
    EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
            loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
            transcoder, loadProvider.getSourceEncoder());
​
    // 2. 先从内存缓存-lru缓存中拿缓存,如果拿到缓存调用 onResourceReady
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        return null;
    }
   
    // 3. 内存缓存-lru中没有缓存,则从弱引用缓存中拿,如果拿到调用 onResourceReady
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        cb.onResourceReady(active);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from active resources", startTime, key);
        }
        return null;
    }
    
    // 4. 内存缓存-弱引用中也没有缓存,从硬盘缓存中拿或加载文件地址,不管是拿硬盘缓存还是加载文件,涉及到IO操作,需要子线程处理
    EngineJob current = jobs.get(key);
    if (current != null) {
        current.addCallback(cb);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Added to existing load", startTime, key);
        }
        return new LoadStatus(cb, current);
    }
​
    EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
    DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
            transcoder, diskCacheProvider, diskCacheStrategy, priority);
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    engineJob.start(runnable);
​
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
}

内存缓存-lru缓存中拿缓存:

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }
    // 如果从 lru 中能拿到缓存,将该缓存从 lru 缓存中移除,并存入 弱引用缓存
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
        cached.acquire();
        activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
    }
    return cached;
}

从硬盘缓存中拿或加载文件地址:

private Resource<?> decode() throws Exception {
    // 这里有个策略判断,只有支持硬盘缓存策略,才能从硬盘中拿缓存
    if (isDecodingFromCache()) {
        // 从硬盘缓存中拿
        return decodeFromCache();
    } else {
        // 加载文件
        return decodeFromSource();
    }
}


​
// 从硬盘缓存中拿
private Resource<?> decodeFromCache() throws Exception {
    Resource<?> result = null;
    try {
        // 5. 从硬盘缓存-转换过缓存中拿
        result = decodeJob.decodeResultFromCache();
    } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Exception decoding result from cache: " + e);
        }
    }
​
    if (result == null) {
        // 6. 硬盘缓存-转换过缓存中拿不到,从原始图片缓存中拿
        result = decodeJob.decodeSourceFromCache();
    }
    return result;
}
​
// 从硬盘缓存-转换过缓存中拿
public Resource<Z> decodeResultFromCache() throws Exception {
    if (!diskCacheStrategy.cacheResult()) {
        return null;
    }
​
    long startTime = LogTime.getLogTime();
    Resource<T> transformed = loadFromCache(resultKey);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Decoded transformed from cache", startTime);
    }
    startTime = LogTime.getLogTime();
    Resource<Z> result = transcode(transformed);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Transcoded transformed from cache", startTime);
    }
    return result;
}
​
// 从硬盘缓存-原始图片缓存中拿
public Resource<Z> decodeSourceFromCache() throws Exception {
    if (!diskCacheStrategy.cacheSource()) {
        return null;
    }
​
    long startTime = LogTime.getLogTime();
    // 这里值得注意的是,这里用的key和其他三个缓存中用到的不一样,这里用的是 resultKey.getOriginalKey() 就是原始图片地址,不过也能理解,加载原始图片缓存,也不需要额外的key信息,只需要文件地址就行了
    Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Decoded source from cache", startTime);
    }
    return transformEncodeAndTranscode(decoded);
}

理完Glide的加载机制之后,就很好理解为什么重新加载之后还是原图,所以进行了优化

优化方案二

加载原图时:

glideOptions = new RequestOptions()
        .signature(new ObjectKey(signatureKey));

加载优化后图片时:

glideOptions = new RequestOptions();

加载原图时多调用了 signature 方法,这样加载优化后图片时,由于和加载原图时的缓存key不一样,会重新进行加载

优化方案二解决了优化图片不加载问题,但是由于重新加载图片,会导致切换过程出现很明显的闪烁,这里可以通过 placeholder 方法优化

优化方案三

// 1. 获取当前 imageView 的 drawable
Drawable drawable = imageView.getDrawable();
Bitmap placeholderBitmap = null;
// 2. drawable 转成 bitmap
if(drawable instanceof BitmapDrawable){
    placeholderBitmap = ((BitmapDrawable) drawable).getBitmap();
}
// 3. 由于 imageView 的 drawable 可能会被回收,所以需要将 bitmap 重新 clone 一份
Bitmap copiedBitmap = null;
if(placeholderBitmap != null){
    copiedBitmap = placeholderBitmap.copy(placeholderBitmap.getConfig(), true);
}
// 4. 将 bitmap 作为 占位符传入
if(copiedBitmap != null && !copiedBitmap.isRecycled()){
    glideOptions = new RequestOptions()
            .frame(0)
            .placeholder(new BitmapDrawable(context.getResources(), copiedBitmap))
            .override(size.getWidth(), size.getHeight());
}else {
    glideOptions = new RequestOptions()
            .frame(0)
            .override(size.getWidth(), size.getHeight());
}

至此,效果实现!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值