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

总的来说,过程很艰辛,但结果也收获颇丰!