Glide 缓存机制分析一,磁盘缓存(3.7.0为例 4)

上篇文章讲了图片网络的请求步骤,这一章说说缓存机制。Glide的缓存分为三大步:内存缓存、磁盘缓存以及服务器存储(或 drawable、Asset等),服务器也就是上一篇说的网络请求,这里就不说了;内存缓存是 软引用+LruCache缓存,磁盘缓存分为 原始图片缓存+转换后的图片缓存;内存缓存是通过 skipMemoryCache() 来控制是否开启,默认是开启状态,磁盘缓存则是通过 diskCacheStrategy() 方法,默认是 GenericRequestBuilder 中的 DiskCacheStrategy.RESULT,即不保存原始图片,只缓存转换后的图片。

我们先分析磁盘缓存,然后是内存缓存。 图片缓存是网络获取后才存入本地,记得上一篇中的 EngineRunnable run()是执行异步获取图片的逻辑,假如没有缓存,执行 DecodeJob 中的 decodeFromSource() 方法

    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();
            final A data = fetcher.loadData(priority);
            ...
            decoded = decodeFromSourceData(data);
        } finally {
            fetcher.cleanup();
        }
        return decoded;
    }

这里的 A data 是封装了网络返回IO流的对象,decodeFromSourceData() 方法则是解码,把流转换为图片,看看它的代码

    private Resource<T> decodeFromSourceData(A data) throws IOException {
        final Resource<T> decoded;
        if (diskCacheStrategy.cacheSource()) {
            decoded = cacheAndDecodeSourceData(data);
        } else {
            long startTime = LogTime.getLogTime();
            decoded = loadProvider.getSourceDecoder().decode(data, width, height);
            ...
        }
        return decoded;
    }

    private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
        ...
        SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
        diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
        ...
        Resource<T> result = loadFromCache(resultKey.getOriginalKey());
        ...
        return result;
    }

先不说细节,单看这两个方法,decodeFromSourceData() 中显示判断磁盘的缓存方式,如果不允许保存原始图片,执行else的代码,意思是根据 width 和 height对图片进行相应的缩放,这两个参数是view的大小或是我们通过 Glide加载图片时的 override() 方法设置的数据;如果允许保留原始图片,则执行 cacheAndDecodeSourceData() 方法,这个方法名字起得很好,见名知意。SourceWriter 是个包装类,diskCacheProvider.getDiskCache() 就是 DiskLruCacheWrapper,它里面包裹了 DiskLruCache类,put(resultKey.getOriginalKey(), writer) 就把数据给缓存到磁盘中了,注意使用的key是 esultKey.getOriginalKey(),它里面只有两个属性,确切说只有一个 id,另外一个 signature 在 GenericRequestBuilder 中创建,是个固定值;数据保存到DiskLruCache 后,loadFromCache() 意思是从缓存中把它重新读出来,这么做个人感觉是为了判断是否缓存成功,

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

如果缓存的数据失败了,则会把它从 DiskLruCache 中删除。主流程上逻辑很简单,那么接下来就细致分析下,看上面的这三个方法,会发现使用的 loadProvider 的几个方法 loadProvider.getSourceDecoder().decode()、loadProvider.getSourceEncoder()。encode()、loadProvider.getCacheDecoder().decode(),loadProvider 上一篇分析过,这里就不多说了,简化点,这里几个方法对应的还是 Glide 构造方法中初始化的 dataLoadProviderRegistry 中的值,主流程还可以画一条线   ImageVideoGifDrawableLoadProvider --》 ImageVideoDataLoadProvider --》 StreamBitmapDataLoadProvider     在这条线里,对象还会被封装,创建新的类型,但最终调用的是 StreamBitmapDataLoadProvider 的 getXXX() 方法,我们以 getSourceDecoder() 为例子分析一下 loadProvider 是 ChildLoadProvider 类型对象,调用方法后,它里面中转给 FixedLoadProvider,接着调用的就是 ImageVideoGifDrawableLoadProvider 了,看下代码

    private final ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> sourceDecoder;
    public ImageVideoGifDrawableLoadProvider(DataLoadProvider<ImageVideoWrapper, Bitmap> bitmapProvider,
            DataLoadProvider<InputStream, GifDrawable> gifProvider, BitmapPool bitmapPool) {

        final GifBitmapWrapperResourceDecoder decoder = new GifBitmapWrapperResourceDecoder(
                bitmapProvider.getSourceDecoder(),
                gifProvider.getSourceDecoder(),
                bitmapPool
        );
        sourceDecoder = decoder;
        ...
    }    
    @Override
    public ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> getSourceDecoder() {
        return sourceDecoder;
    }

loadProvider.getSourceDecoder() 返回的就是 sourceDecoder 对象,它是在构造方法中创建的 GifBitmapWrapperResourceDecoder,封装了构成方法的参数,它的 decode(data, width, height) 方法被调用后,则执行 GifBitmapWrapperResourceDecoder 中代码

    public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException {
       ...
        GifBitmapWrapper wrapper = null;
        try {
            wrapper = decode(source, width, height, tempBytes);
        } finally {
            pool.releaseBytes(tempBytes);
        }
        return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
    }
    private GifBitmapWrapper decode(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException {
        final GifBitmapWrapper result;
        if (source.getStream() != null) {
            result = decodeStream(source, width, height, bytes);
        } ...
        return result;
    }
    private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes)
            throws IOException {
        InputStream bis = streamFactory.build(source.getStream(), bytes);
        ...
        if (result == null) {
            ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor());
            result = decodeBitmapWrapper(forBitmapDecoder, width, height);
        }
        return result;
    }
    private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
        GifBitmapWrapper result = null;
        Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height);
        if (bitmapResource != null) {
            result = new GifBitmapWrapper(bitmapResource, null);
        }

        return result;
    }

最终执行到 decodeBitmapWrapper() 方法,这个方法中,调用了 bitmapDecoder 的 decode(toDecode, width, height) 方法,bitmapDecoder 是通过构造方法传递进来的对象,它是什么呢?重新看 ImageVideoGifDrawableLoadProvider ,原来是 bitmapProvider.getSourceDecoder() 返回的对象,顺着上面的线,或者 Glide 的构造方法中 dataLoadProviderRegistry 添加数据的顺序,发现 bitmapProvider 是 ImageVideoDataLoadProvider,那么对应的 getSourceDecoder()

    private final ImageVideoBitmapDecoder sourceDecoder;
    public ImageVideoDataLoadProvider(DataLoadProvider<InputStream, Bitmap> streamBitmapProvider,
            DataLoadProvider<ParcelFileDescriptor, Bitmap> fileDescriptorBitmapProvider) {
        ...
        sourceDecoder = new ImageVideoBitmapDecoder(streamBitmapProvider.getSourceDecoder(),
                fileDescriptorBitmapProvider.getSourceDecoder());
    }
    @Override
    public ResourceDecoder<ImageVideoWrapper, Bitmap> getSourceDecoder() {
        return sourceDecoder;
    }

注意了,返回的是 sourceDecoder,它对应的是 ImageVideoBitmapDecoder 类型,它封装了 streamBitmapProvider.getSourceDecoder(),到此为止找到了正主,可以看它的 decode(toDecode, width, height) 方法了

     @Override
    public Resource<Bitmap> decode(ImageVideoWrapper source, int width, int height) throws IOException {
        Resource<Bitmap> result = null;
        InputStream is = source.getStream();
        if (is != null) {
            try {
                result = streamDecoder.decode(is, width, height);
            } catch (IOException e) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Failed to load image from stream, trying FileDescriptor", e);
                }
            }
        }

        if (result == null) {
            ParcelFileDescriptor fileDescriptor = source.getFileDescriptor();
            if (fileDescriptor != null) {
                result = fileDescriptorDecoder.decode(fileDescriptor, width, height);
            }
        }
        return result;
    }

看来,又中转了,它方法中调用了 streamDecoder.decode(is, width, height), streamDecoder 则是 ImageVideoBitmapDecoder 中的 streamBitmapProvider.getSourceDecoder(), streamBitmapProvider 是 ImageVideoBitmapDecoder构造方法传进来的,可以找到它是 StreamBitmapDataLoadProvider,那么它 

    @Override
    public ResourceDecoder<InputStream, Bitmap> getSourceDecoder() {
        return decoder;
    }
    public StreamBitmapDataLoadProvider(BitmapPool bitmapPool, DecodeFormat decodeFormat) {
        decoder = new StreamBitmapDecoder(bitmapPool, decodeFormat);
        ...
    }

找到正主了, StreamBitmapDecoder 的 decode() 方法,

    @Override
    public Resource<Bitmap> decode(InputStream source, int width, int height) {
        Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
        return BitmapResource.obtain(bitmap, bitmapPool);
    }

最终由 Downsampler 来进行数据的转换,有兴趣的可以看看这个类,图片的缩放就是在这个里面执行的。简单的一行代码,居然嵌套了这么多层,这里面比较绕,但只要流程明白了,就很简单。其他的几个方法可以类似的看,或者直接看 StreamBitmapDataLoadProvider 的代码,这里是一条线的尾部。

继续回到 DecodeJob 类中的 decodeFromSource() 方法,获取到图片后,会调用 transformEncodeAndTranscode() 方法

    private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
        long startTime = LogTime.getLogTime();
        Resource<T> transformed = transform(decoded);
        ...

        writeTransformedToCache(transformed);

        startTime = LogTime.getLogTime();
        Resource<Z> result = transcode(transformed);
        ...
        return result;
    }

transform(decoded) 的方法比较简单,它执行的是我们在 Glide 加载图片设置的 bitmapTransform() 方法,设置类似 模糊图片、圆角图片 等我们需要的功能,默认这是个空方法; 然后是 writeTransformedToCache(transformed) 方法

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


这里就是存储转换后的图片,它也有个判断,diskCacheStrategy.cacheResult() 这里是个开关,再往下又是缓存数据,这里分析可以参考上面的逻辑;缓存转换后的图片,key 是 resultKey,它里面信息比较多,包括 id、width、height 等等。 缓存完图片后,执行了 transcode() 方法


又是类型转换,transcoder 其实就是 loadProvider.getTranscoder(),源头是 Glide 构造方法 transcoderRegistry 中的对象,

        transcoderRegistry.register(GifBitmapWrapper.class, GlideDrawable.class,
                new GifBitmapWrapperDrawableTranscoder(new GlideBitmapDrawableTranscoder(context.getResources(), bitmapPool)));

这里面的代码比较简单,也就是转换封装对象,然后返回。在 EngineJob 中切回主线程,handleResultOnMainThread() 中 listener.onEngineJobComplete(key, engineResource) 方法,执行的是 Engine 中的

    @Override
    public void onEngineJobComplete(Key key, EngineResource<?> resource) {
        Util.assertMainThread();
        if (resource != null) {
            resource.setResourceListener(key, this);
            if (resource.isCacheable()) {
                activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
            }
        }
        jobs.remove(key);
    }

在这里把转换后的数据,存在内存中一份,这里用的是软引用,关于内存缓存,下篇再讲,这里先提一下。 本篇是以 EngineRunnable 中 run() 方法开始的,它里面先调用 decode()

    private Resource<?> decode() throws Exception {
        if (isDecodingFromCache()) {
            return decodeFromCache();
        } else {
            return decodeFromSource();
        }
    }

如果第一次图片缓存成功了,杀死进程,重新进来,仍是先执行 decodeFromCache() 方法,

    private Resource<?> decodeFromCache() throws Exception {
        Resource<?> result = null;
        try {
            result = decodeJob.decodeResultFromCache();
        } catch (Exception e) {
        }

        if (result == null) {
            result = decodeJob.decodeSourceFromCache();
        }
        return result;
    }

看它的代码,知道先从缓存中获取转换之后的图片,如果为空,则找原始图片,原始图片找到后,仍然是转换下图片,缓存起来。此时有图片,则直接通过 EngineJob 切回主线程,后续逻辑和上面一样。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值