随手记->使用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啃完,但是也把它的主流程啃了好几遍,这个坑就留作纪念吧!
- 随手记->使用Glide遇到的第一个问题
- 记glide框架使用中所遇到的问题
- Glide使用中遇到的问题
- 遇到的第一个问题
- 使用Unity发布第一个程序时遇到的问题
- 使用jetbrains idea开发android遇到的第一个问题
- 随手记->Glide初始化
- 学C++遇到的第一个问题
- 在C遇到的第一个问题!
- 第一个DWR程序遇到的问题
- 写第一个VC++遇到的问题
- 第一个遇到的问题,关于KEILL
- 第一个MapReduce程序遇到的问题
- Android studio 遇到的第一个问题
- Eclipse遇到的第一个问题
- 第一个项目遇到的问题2
- Glide使用中遇到的问题及解决
- 使用glide框架加载图片遇到的一些问题
- docker 网络 不好用 docker: Error response from daemon: failed to create endpoint jovial_wing on network b
- 浅析邮件服务
- 编程珠玑 笔记
- 【微信公众号】- 微信公众号自定义菜单二维码扫描scancode_waitmsg获取不到ScanResult的值
- caffe中的StartInternalThread函数怎么被调用的
- 随手记->使用Glide遇到的第一个问题
- JavaScript中判断对象类型的几种方法
- 总结:使用IoMarkPending的原因及原理
- MVC界面点击下载进行下载JSON文件
- Mastering Markdown
- 产生随机数
- pdb — The Python Debugger(Python调试器)
- 服务器端 文件上传模版代码
- Caldroid日历控件的使用