Glide在6.0系统下加载图片失败问题
来源:互联网 发布:unity3d 5.5粒子系统 编辑:程序博客网 时间:2024/06/07 10:56
最近在项目中使用到了Glide3.7,在通过GlideModel定制图片缓存到SD卡时,遇到了一个非常诡异的问题:
在Glide中使用缓存策略为Source或者All时,图片无法展示出来,并且也无法缓存到SD卡中:
只有在设置为Result时才能够正常的显示,但是同样无法缓存到SD中;
diskCacheStrategy(DiskCacheStrategy.RESULT);
代码里找了很久,都找不到问题原因,所以只能走一波源码了;
通过翻找源码,可以发现glide所以的图片请求任务都是通过EngineRunnable来执行的,在run方法中会调用decode方法来获取数据源:
@Overridepublic void run() { if (isCancelled) { return; } Exception exception = null; Resource<?> resource = null; try { //请求图片数据 resource = decode(); } catch (Exception e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Exception decoding", e); } exception = e; } if (isCancelled) { if (resource != null) { resource.recycle(); } return; } if (resource == null) { onLoadFailed(exception); } else { onLoadComplete(resource); }}
由于初始请求没有缓存,所有会执行decodeFromSource方法;
private Resource<?> decode() throws Exception { if (isDecodingFromCache()) { return decodeFromCache(); } else { //初始从网络等获取源数据 return decodeFromSource(); }}private Resource<?> decodeFromSource() throws Exception { return decodeJob.decodeFromSource();}
接下来进入DecodeJob中开始获取、解析、缓存数据:
public Resource<Z> decodeFromSource() throws Exception { Resource<T> decoded = decodeSource(); return transformEncodeAndTranscode(decoded);}private Resource<T> decodeSource() throws Exception { Resource<T> decoded = null; try { long startTime = LogTime.getLogTime(); //这一步从fetcher拿到图片源数据 final A data = fetcher.loadData(priority); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Fetched data", startTime); } if (isCancelled) { return null; } decoded = decodeFromSourceData(data); } finally { fetcher.cleanup(); } return decoded;}private Resource<T> decodeFromSourceData(A data) throws IOException { final Resource<T> decoded; if (diskCacheStrategy.cacheSource()) { //如果缓存策略是SOURCE,则往这走 decoded = cacheAndDecodeSourceData(data); } else { long startTime = LogTime.getLogTime(); decoded = loadProvider.getSourceDecoder().decode(data, width, height); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded from source", startTime); } } return decoded;}
在diskCacheStrategy是Source的情况:
private Resource<T> cacheAndDecodeSourceData(A data) throws IOException { long startTime = LogTime.getLogTime(); //数据写入本地缓存,但是失败了,原因后面再说 SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data); diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Wrote source to cache", startTime); } startTime = LogTime.getLogTime(); //从本地缓存拿出数据,在返回给调用者 Resource<T> result = loadFromCache(resultKey.getOriginalKey()); if (Log.isLoggable(TAG, Log.VERBOSE) && result != null) { logWithTimeAndKey("Decoded source from cache", startTime); } return result;}
在diskCacheStrategy是Result的情况:
private Resource<T> decodeFromSourceData(A data) throws IOException { final Resource<T> decoded; if (diskCacheStrategy.cacheSource()) { decoded = cacheAndDecodeSourceData(data); } else { //在diskCacheStrategy是Result的情况,并没有进行缓存操作,而是先对数据进行裁剪 long startTime = LogTime.getLogTime(); decoded = loadProvider.getSourceDecoder().decode(data, width, height); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded from source", startTime); } } return decoded;}
然后回到DecodeJob最初被调用的方法:decodeFromSource,此时diskCacheStrategy是Source的情况,已经进行了缓存操作,并且缓存失败,返回null;而Result的情况,则还没进行缓存,只是对源数据进行了裁剪,返回裁剪后的数据;
public Resource<Z> decodeFromSource() throws Exception { Resource<T> decoded = decodeSource(); return transformEncodeAndTranscode(decoded);}
结束调用:transformEncodeAndTranscode
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) { long startTime = LogTime.getLogTime(); Resource<T> transformed = transform(decoded); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transformed resource from source", startTime); } //在这里,diskCacheStrategy是Result的情况进行了图片缓存 //同样,缓存会失败,但并不影响本方法的返回数据 writeTransformedToCache(transformed); startTime = LogTime.getLogTime(); Resource<Z> result = transcode(transformed); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transcoded transformed from source", startTime); } return result;}//diskCacheStrategy是Result的情况进行了图片缓存//同样,缓存会失败private void writeTransformedToCache(Resource<T> transformed) { if (transformed == null || !diskCacheStrategy.cacheResult()) { return; } long startTime = LogTime.getLogTime(); SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed); diskCacheProvider.getDiskCache().put(resultKey, writer); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Wrote transformed from source to cache", startTime); }}
到此,可以知道为什么Result的情况能够显示图片,而Source或者All的情况不能:
因为Source的情况下,Glide先将图片缓存到本地,然后再从本地取出缓存,进行展示,如果缓存失败,则拿不到数据,相应的图片肯定展示不出来!
而Result的情况,Glide缓存和数据展示是分开的 ,即使缓存失败,也不会影响到数据展示。
那么,问题来了,为什么缓存会失败呢?
对比其他手机的情况,发现只有在6.0手机上出现这个问题,并且查找到SD卡中,发现缓存目录并没有创建成功。
思来想去,创建失败应该是没有权限导致,而项目中权限是在引导页动态获取的,经过无数次测试,检查……最后发现,原来是项目自己挖下的坑 /(ㄒoㄒ)/~~
由于是音乐项目,在Application启动时会调起通知栏,而通知栏的音乐封面是用Glide加载的,这样就会导致一个问题:
在Glide第一次被使用时,会去扫描清单文件:
//Glide.javapublic static Glide get(Context context) { if (glide == null) { synchronized (Glide.class) { if (glide == null) { Context applicationContext = context.getApplicationContext(); List<GlideModule> modules = new ManifestParser(applicationContext).parse(); GlideBuilder builder = new GlideBuilder(applicationContext); for (GlideModule module : modules) { module.applyOptions(applicationContext, builder); } glide = builder.createGlide(); for (GlideModule module : modules) { module.registerComponents(applicationContext, glide); } } } } return glide;}
而我自己定制的GlideModel此时就会被扫进来,我的SD缓存就配置在其中:
public class CustomGlideModule implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { ViewTarget.setTagId(R.id.glide_tag_id); // 设置别的get/set tag id,以免占用View默认的 builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); // 设置图片质量为高质量 int cacheSize = 100*1000*1000; builder.setDiskCache(new DiskLruCacheFactory(new DiskLruCacheFactory.CacheDirectoryGetter() { @Override public File getCacheDirectory() { //设置SD卡缓存目录 String downloadDirectoryPath = Environment.getExternalStorageDirectory().getPath() + File.separator + "xxx" + File.separator + "AlbumArt"; File file = new File(downloadDirectoryPath); if (!file.exists()) { boolean mkdirs = file.mkdirs(); return mkdirs ? file : null; } return file; } }, cacheSize)); } @Override public void registerComponents(Context context, Glide glide) { // 注册我们的ImageFidLoader glide.register(MusicInfo.class, InputStream.class, new CoverUrlLoader.Factory()); }}
此时,可能在引导页还没有获取到权限,而Glide已经扫描完清单文件,并且进行图片加载。
再走一波源码,可以更清楚!
由于没有SD卡权限,那么创建文件夹的操作就会失败,而在DiskLruCacheFactory的build方法中可以看到:当缓存目录为空时,return null;
//DiskLruCacheFactory.java@Overridepublic DiskCache build() { File cacheDir = cacheDirectoryGetter.getCacheDirectory(); if (cacheDir == null) { return null; } //当缓存目录为空时 if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) { return null; } return DiskLruCacheWrapper.get(cacheDir, diskCacheSize);}
看它的调用者Engine类的内部类LazyDiskCacheProvider,可以发现Glide在diskCache为空的情况下,会返回一个DiskCacheAdapter;
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider { private final DiskCache.Factory factory; private volatile DiskCache diskCache; public LazyDiskCacheProvider(DiskCache.Factory factory) { this.factory = factory; } @Override public DiskCache getDiskCache() { if (diskCache == null) { synchronized (this) { if (diskCache == null) { diskCache = factory.build(); } if (diskCache == null) { diskCache = new DiskCacheAdapter(); } } } return diskCache; }}
而DiskCacheAdapter只是一个空壳:
public class DiskCacheAdapter implements DiskCache { @Override public File get(Key key) { // no op, default for overriders return null; } @Override public void put(Key key, Writer writer) { // no op, default for overriders } @Override public void delete(Key key) { // no op, default for overriders } @Override public void clear() { // no op, default for overriders }}
至此一切真相大白! \ (≧▽≦)/
解决方案也很简单,只需把Application中调起通知栏的操作延迟到引导页流程之后即可:
//延迟通知栏的加载至应用获取到写SDCard权限之后,这样Glide才能成功创建缓存目录registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks(){ @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { super.onActivityCreated(activity, savedInstanceState); if (activity instanceof MainActivity) { //发起通知... unregisterActivityLifecycleCallbacks(this); } }});
总的来说,过程很艰辛,但结果也收获颇丰!
- Glide在6.0系统下加载图片失败问题
- Glide加载图片失败的Bug?
- Glide加载图片变色问题
- glide加载图片变形问题
- 解决Glide加载图片问题!!!
- Glide加载图片的变形问题
- 关于 Glide 加载图片圆角问题
- 关于 Glide 加载图片圆角问题
- Glide加载网络图片报404问题
- android 使用Glide加载图片闪动问题
- 在GridView或者ListView的Adapter中使用Glide加载图片,图片被拉伸问题
- 图片加载库Glide
- Android Glide 图片加载
- Glide加载图片
- 图片加载库Glide
- 图片加载库Glide
- 图片加载框架Glide
- Glide图片加载
- Drupal 创建一个空白页面
- 求分析
- 【Python基础教程笔记(二)】字典,条件,循环和其他语句
- 【BigHereo 39】---L12---C++真题之 最后大题代码
- bzoj 3043(差分)
- Glide在6.0系统下加载图片失败问题
- 【网络编程】(一)基本模型、套接字之间的连接
- Windows 下安装composer
- openshift rhel7 添加image stream
- Split Array into Consecutive Subsequences 解法
- Q146:PBRT-V3,对系统进行拓展(以添加一个新的Integrator为例)
- Linux内核版本和发行版本
- 【慕课网_性能优化之MySQL优化_学习】【07】
- Linux各常用命令缩写