随手记->使用Glide遇到的第一个问题

来源:互联网 发布:oracle 自动优化 工具 编辑:程序博客网 时间:2024/05/23 21:56

从2016年开始,我们小组在项目中就开始使用了Glide,目前使用的版本是3.7.0 。但是最近自测发现了一个不起眼的问题,因为这个问题又发现了别的问题,好多坑啊!!

第一个问题,发现已经用Glide加载好了的图片,再次显示该图时,Glide竟然又从网络下载这张图片。。。

观察现象:加载完了所有图片墙里面的缩略图后,不断尝试从底部滑动到底部,然后从底部滑动到顶部,就会发现某张或者某些图片加载比较慢,这是一个可复现的问题,应该好办!

问题跟踪:首先检查下自己的代码,是否有重复设置默认图的代码存在?检查发现自己的代码里面最后都是调用Glide来加载的,那就对Glide进行日志调试。如何打开Glide的日志调试呢?自己网上搜索一下就有很多,这里简单介绍下:

比如我想打开Glide加载网络数据和本地缓存数据的相关日志,这部分代码在DecodeJob中,那就使用命令打开这部分的日志

adb shell setprop log.tag.DecodeJob VERBOSE
一般来说,比较重要的日志包括Engine、EngineRunnable、DecodeJob;打开这些日志就够我定位这个问题了。

观察了Glide的日志输出,发现Glide在加载某张已经加载过的图片的时候,在Engine中又开启了一个新的任务,输出了日志:Started new load。那就是说Glide并没有命中缓存,接下来我就仔细观察了DecodeJob的日志输出,发现了一些端倪:

第一次缓存图片时候的Log(从网络加载图片)

03-14 14:21:22.584 26188-26540/? I/CusHttpUrlFetcher: star to load image03-14 14:21:22.592 26188-26540/? D/CusHttpUrlFetcher: image url: http://xxxxxxx/03-14 14:21:23.673 26188-26540/? V/DecodeJob: Fetched data in 1081.081359, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[221x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:21:23.653 26188-26540/? V/DecodeJob: Decoded from source in 20.341332996989517, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[221x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:21:23.653 26188-26540/? V/DecodeJob: Transformed resource from source in 0.036178, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[221x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:21:23.670 26188-26540/? V/DecodeJob: Wrote transformed from source to cache in 14.342681, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[221x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:21:23.670 26188-26540/? V/DecodeJob: Transcoded transformed from source in 0.049123, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[221x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}

第二次没命中缓存,又去加载网络图片的log

03-14 14:58:25.558 26188-26188/? V/Engine: Started new load in 0.21012499999999998ms, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:58:25.579 26188-26264/? V/DecodeJob: Decoded transformed from cache in 0.5814579999999999, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:58:25.579 26188-26264/? V/DecodeJob: Transcoded transformed from cache in 0.044416, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:58:25.584 26188-26540/? I/CusHttpUrlFetcher: star to load image03-14 14:58:25.592 26188-26540/? D/CusHttpUrlFetcher: image url: http://xxxxxxx/03-14 14:58:26.630 26188-26540/? V/DecodeJob: Fetched data in 1049.450583, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:58:26.653 26188-26540/? V/DecodeJob: Decoded from source in 22.581332999999997, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:58:26.653 26188-26540/? V/DecodeJob: Transformed resource from source in 0.047958, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:58:26.670 26188-26540/? V/DecodeJob: Wrote transformed from source to cache in 16.672458, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}03-14 14:58:26.670 26188-26540/? V/DecodeJob: Transcoded transformed from source in 0.053833, key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}

看出什么来了吗?第二次中的日志,从第一行到第三行,Glide去加载了磁盘缓存的数据,但是后面的日志却是去访问网络获取图片了,这可不是我想要的。。。

跟踪一下日志所在的方法,下面贴出源码(位于DecodeJob中):

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

Glide来到了这里,为什么?因为默认使用缓存磁盘的方式是DiskCacheStrategy.RESULT,这样做就是为了只缓存最后显示的图片,而不是缓存从服务器下载下来的图片,在显示图片的尺寸小于服务器返回的图片尺寸的情况下,这样做可以节省磁盘空间。

好了,回到刚刚说的没有命中缓存的问题,可能还需要再贴出一些源码才行(位于DecodeJob中):

private Resource<T> loadFromCache(Key key) throws IOException {        File cacheFile = diskCacheProvider.getDiskCache().get(key);        if (cacheFile == null) {            return null;        }        Resource<T> result = null;        try {            result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);        } finally {            if (result == null) {                diskCacheProvider.getDiskCache().delete(key);            }        }        return result;}

private Resource<Z> transcode(Resource<T> transformed) {        if (transformed == null) {            return null;        }        return transcoder.transcode(transformed);}
结合这两个方法可以发现,用于命中缓存的方法 decodeResultFromCache 实际上是调用了 loadFromCache 和 transcode 方法来加载磁盘缓存的图片。关键点在于获取缓存的key
File cacheFile = diskCacheProvider.getDiskCache().get(key);if (cacheFile == null) {    return null;}

如果同一张图片的这个key不一样,那就无法命中缓存文件啊,Damned!真的是Key不一样么?再来看看上面贴出来的两个log吧!

第一次加载图片使用的key

key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[221x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}
第二次加载图片使用的key

key: EngineKey{http://xxxxxxx/+com.bumptech.glide.signature.EmptySignature@2e6bc8b8+[222x148]+''+'ImageVideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap'+''+'BitmapEncoder.com.bumptech.glide.load.resource.bitmap'+''+''}

从这两个key可以看出,控件的尺寸变了呀,Damned!第一次的时候是221x148,第二次是222x148,差一个像素,城会玩。。。


问题原因:加载缓存使用的key不一致,使得Glide无法命中缓存然后去加载网络图片。

解决方案:显式的使用 diskCacheStrategy(DiskCacheStrategy.SOURCE)!
为什么这样做就可以避免key不一致导致无法命中缓存的问题呢?来,我们在看看源码:

在EngineRunnable中是这样读取缓存图片的

    private Resource<?> decodeFromCache() throws Exception {        Resource<?> result = null;        try {            result = decodeJob.decodeResultFromCache();        } catch (Exception e) {            if (Log.isLoggable(TAG, Log.DEBUG)) {                Log.d(TAG, "Exception decoding result from cache: " + e);            }        }        if (result == null) {            result = decodeJob.decodeSourceFromCache();        }        return result;    }

这就是说,调用DecodeJob的decodeResultFromCache方法没有命中缓存,我们还可以来decodeSourceFromCache嘛!我们再来看看decodeSourceFromCache方法的源码,它位于DecodeJob中:

public Resource<Z> decodeSourceFromCache() throws Exception {        if (!diskCacheStrategy.cacheSource()) {            return null;        }        long startTime = LogTime.getLogTime();        Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());        if (Log.isLoggable(TAG, Log.VERBOSE)) {            logWithTimeAndKey("Decoded source from cache", startTime);        }        return transformEncodeAndTranscode(decoded);    }

从代码中可以看出,你必须显示地调用

diskCacheStrategy(DiskCacheStrategy.SOURCE)

才会走到这个方法哦。到这里可能有人会问了,这里要命中缓存还是得用key的啊!没错,确实得用key,但是Glide这次用的是originalKey而不是直接用resultKey,这两者有什么区别吗?区别大了去了。。。

我们先看看resultKey和originalKey是怎么来的吧,这个得从Engine类源码中的load方法说起:

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();//这里就生成key了,也就是上面提到的resultKey        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),                transcoder, loadProvider.getSourceEncoder());        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;        }        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);        if (active != null) {//正在使用的资源,其实是从上面的loadFromCache中获取的资源            cb.onResourceReady(active);            if (Log.isLoggable(TAG, Log.VERBOSE)) {                logWithTimeAndKey("Loaded resource from active resources", startTime, key);            }            return null;        }        EngineJob current = jobs.get(key);//判断是否有相同的任务在执行,妈蛋,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);    }
显式的使用 diskCacheStrategy(DiskCacheStrategy.SOURCE)是缓兵之计,根源问题还是在于key,key不一样无法命中缓存。。。

回到我们的关注点来,我们说的是key,从上面代码看出,加载缓存使用的resultKey其实就是EngineKey,那originalKey哪里来?去EngineKey的源码看看便知:

    public EngineKey(String id, Key signature, int width, int height, ResourceDecoder cacheDecoder,            ResourceDecoder decoder, Transformation transformation, ResourceEncoder encoder,            ResourceTranscoder transcoder, Encoder sourceEncoder) {        this.id = id;        this.signature = signature;        this.width = width;        this.height = height;        this.cacheDecoder = cacheDecoder;        this.decoder = decoder;        this.transformation = transformation;        this.encoder = encoder;        this.transcoder = transcoder;        this.sourceEncoder = sourceEncoder;    }    public Key getOriginalKey() {        if (originalKey == null) {            originalKey = new OriginalKey(id, signature);        }        return originalKey;    }

OriginalKey很简单,传入了id和另一个signature,id从Engine的load方法可以看出,这是fetcher的id,一般就是图片的URL啦。那signature呢?哪里冒出来的?其实这样signature是可以我们自己传入的,怎么传?看代码:

Glide.with(mContext).load(url).signature(new StringSignature("you string signature")).into(imageView);

使用signature方法就可以传入啦,这个StringSignature是继承与Key的,你也可以自定义Signature,看这个signature方法的注释可知,传入的signature可作为缓存图片的key的一部分,如果以时间作为自定义signature的一部分,还可以控制缓存图片啥时候过期哦!噢耶,我们再也不用担心app不会定期更新图片啦,因为过期时间可用signature定制了。

又有点扯远了,扯回来先。假如我们不使用这个signature方法会怎样呢?Glide已经帮你想好了,你不用signature方法,那就给你个默认的呗,看源码:

public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable {        //此处省略某些代码。。。。    private Key signature = EmptySignature.obtain();    //后面省略某些代码。。。。。}

public final class EmptySignature implements Key {    private static final EmptySignature EMPTY_KEY = new EmptySignature();    public static EmptySignature obtain() {        return EMPTY_KEY;    }    private EmptySignature() {        // Empty.    }    //此处省略一些代码。。。}

看,你不传signature那就使用默认的signature咯,因为是静态的,所以是同一个signature。

到这里终于搞明白了吧,为什么使用diskCacheStrategy(DiskCacheStrategy.SOURCE)能命中缓存,因为这样使用了originalKey来命中缓存,而originalKey只需要保证fetcher的id和signature一致即可,不需要判断width和height。


根本问题还是控件尺寸发生了变化,我用的是RecyclerView平铺显示图片,类似GridView的效果,在某个格子获取到焦点的时候执行放大动画,失去焦点则执行缩小动画,可能是在这个动画切换的过程中导致格子的尺寸发生了变化,这算不算是Android的bug??


打了好多字,好累,先写这么多了,虽然没把Glide啃完,但是也把它的主流程啃了好几遍,这个坑就留作纪念吧!






0 0
原创粉丝点击