磁盘缓存

磁盘缓存

  • 通过DiskLruCache实现 与LruCache算法相似,当缓存大小将超过阈值的时候,清除缓存中最老的数据
  • DiskLruCache内部也是通过LinkedhashMap 实现的
  • 同样的也是在glide 生成时创建的
  • 这里我们也从两个角度分析 取,存

经过前面文章的分析,我们知道当内存缓存和活动缓存都没有的时候我们就要开启线程从磁盘或者网络中获取 下面我们看代码

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) {
		//1构建enginejob
        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
     	//2创建DecodeJob
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
       //3创建EngineRunnable 用于从磁盘或者网络加载图片
     EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
     	//4 调用start方法执行 runnable
        engineJob.start(runnable);
        return new LoadStatus(cb, engineJob);
    }
  • EngineJob
  • 通过添加和删除加载的回调并在加载完成时通知回调来管理加载的类。
  • 内部维护了
  • diskCacheService线程池用来执行执行加载任务
diskCacheService = new FifoPriorityThreadPoolExecutor(1);
  • sourceService线程池用来执行源数据加载 比如从网络获取
final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
sourceService = new FifoPriorityThreadPoolExecutor(cores);
  • 二者都是在Glide创建的过程中初始化并赋值的
  • DecodeJob
  • 负责从缓存数据或原始源解码资源并应用转换和转码的类。
  • 负责解码工作的,从原始数据转换成对应类型的图片
  • EngineRunnable
  • 一个可运行类,负责使用DecodeJob分两个阶段对后台线程上的资源进行解码。
  • 通过观察上面三个对象的描述我们知道
  • 通过EngineJob中的线程池来执行EngineRunnable,通过DecodeJob完成解码等工作
  • 真正的取操作是在EngineRunnable中 所以我们分析EngineRunnablerun方法
  • 调用start方法执行EngineRunnable
public void start(EngineRunnable engineRunnable) {
        this.engineRunnable = engineRunnable;
        future = diskCacheService.submit(engineRunnable);
}
  • 可以看到是通过diskCacheService来完成的 下面分析Run方法
  • Run
@Override
    public void run() {
        Resource<?> resource = null;
        // 第一处
        resource = decode();//通过decode 方法执行
        if (resource == null) {
            //第二处
            onLoadFailed(exception);
        } else {
            // 第三处
            onLoadComplete(resource);
        }
    }
  • decode
private Resource<?> decode() throws Exception {
        // 判断是否使用磁盘缓存
        if (isDecodingFromCache()) {
            //1. 如果有磁盘缓存则去缓存读取 如果不设置磁盘缓存 默认是根据 图片大小和数量来决定是否缓存的 我们从缓存中取也是从这里开始的
            return decodeFromCache();
        } else {
            // 2.否则去网络请求 
            return decodeFromSource();
        }
    }
  • 可以看到 上面根据条件判断,分两种情况加载图片
  • 条件 isDecodingFromCache()是否使用磁盘缓存

取决于在使用API时是否采用DiskCacheStrategy.NONE,即不缓存任何图片,即禁用磁盘缓存,若不设置该值则默认从磁盘缓存获取

private boolean isDecodingFromCache() {
        // 判断stage 
        return stage == Stage.CACHE;
    }

  1. 若不禁用磁盘缓存则首先从磁盘缓存加载 -》若磁盘没有 在去网络获取
  • 这点下面到调用onLoadFailed就知道是如何执行到的网络请求
  1. 若禁用磁盘缓存则直接从网络加载

从磁盘缓存获取数据decodeFromCache()

  • 通过decode方法我们可以得出
  • 当使用磁盘缓存时 调用ecodeFromCache();从磁盘获取数据
private Resource<?> decodeFromCache() throws Exception {
        Resource<?> result = null;
        try {
            //1 先获取转换过后的图片资源
            result = decodeJob.decodeResultFromCache();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Exception decoding result from cache: " + e);
            }
        }

        if (result == null) {
            //2 若获取不到则获取原始数据的缓存
            result = decodeJob.decodeSourceFromCache();
        }
        return result;
    }
//1 先获取转码过后的图片资源 decodeResultFromCache
 public Resource<Z> decodeResultFromCache() throws Exception {
		//...
        Resource<T> transformed = loadFromCache(resultKey);
		//...
        Resource<Z> result = transcode(transformed);
        return result;
    }
//2 若获取不到则获取为转码的原始数据decodeSourceFromCache
public Resource<Z> decodeSourceFromCache() throws Exception {
       	//...
        Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
     	//...
        return transformEncodeAndTranscode(decoded);
    }
// 3 二者都调用了loadFromCache 获取磁盘缓存原始数据或者转码数据
 private Resource<T> loadFromCache(Key key) throws IOException {
     	//1 通过diskCacheprovider获取diskCache 通过diskCache获取key对应的资源
     	//diskCacheProvider在Engine创建时生成的,创建时过创建Glide时在GlideBuilder里创建的diskCacheFactory为参数
        File cacheFile = diskCacheProvider.getDiskCache().get(key);
        if (cacheFile == null) {
            return null;
        }

        Resource<T> result = null;
        try {
            //2 通过loadproider 获取解码器 对 缓存解码
            result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
        } finally {
            if (result == null) {
                diskCacheProvider.getDiskCache().delete(key);
            }
        }
        return result;
    }
  • 通过上面代码
  • 首先获取由原始数据转换过后的图片缓存
  • 若获取不到则获取原始数据的缓存
  • 此时缓存获取解释
  • 则代码回到run方法开始执行第二处
  • 若缓存不为空,调用onLoadComplete 通知主线程数据加载完成
  • 若缓存为空调用onLoadFailed
private void onLoadFailed(Exception e) {        // 当加载失败时 stage 改变为从网络获取 并开启网络加载        if (isDecodingFromCache()) {            stage = Stage.SOURCE;            // 开启网络加载            manager.submitForSource(this);        } else {            manager.onException(e);        }    }
@Override    public void submitForSource(EngineRunnable runnable) {       	// 通过sourceSerive线程池执行网络请求        future = sourceService.submit(runnable);    }
  • 此时已经开启了网络请求从网络里获取资源,到了这里也是开始了从网络获取资源后缓存到磁盘缓存中

从网络获取数据decodeFromSource()

由上文知道

  • 若未禁用磁盘加在当磁盘加载失败调用manager.submitForSource(this);
  • manager 就是EngineJob,调用 sourceService.submit(runnable);
  • 通过sourceService线程池执行runnable
  • 相当于又执行了一次run方法,此时的 stage = Stage.SOURCE,isDecodingFromCache返回值为false执行decodeFromSource()从网络加载
  • 若禁用磁盘缓存则isDecodingFromCache返回值为false执行decodeFromSource()从网络加载
private Resource<?> decodeFromSource() throws Exception {        return decodeJob.decodeFromSource(); }//decodeFromSource()public Resource<Z> decodeFromSource() throws Exception {    	// 1 调用decodeSource() 获取Resource        Resource<T> decoded = decodeSource();    	// 2 将Resource转码返回        return transformEncodeAndTranscode(decoded); }//decodeSource()  private Resource<T> decodeSource() throws Exception {        Resource<T> decoded = null;        try {            //1 通过fetcher.loadData 这里的priority 就是之前的request            final A data = fetcher.loadData(priority);            // 2 从源数据解码            decoded = decodeFromSourceData(data);        } finally {            fetcher.cleanup();        }        return decoded;    }
  • 通过上面的代码我们发现是通过
  • fetcher.loadData(priority);来获取的网络资源
  • fetcher 是 DataFetcher 类型的对象 ,在前面的onSizeReady中通过ModelLoader创建
  • DataFetcher 是一个接口,其有多个实现类,这些实现类均为从指定数据来源加载数据的DataFetcher
  • 由ModelLoader创建一个新实例,用来指定加载的数据来源
  • 比如我们用String ,对应实现类就是HttpUrlFetcher
  • priority 为request,这里没用上
  • 我们传递为Url , 所以loadDataHttpUrlFetcherloadData
public InputStream loadData(Priority priority) throws Exception {    return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());}// 获取InputStreamprivate InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)        throws IOException {	//通过HttpURLConnection获取inputStream    urlConnection = connectionFactory.build(url);    //...省略给urlConnection设置参数部分 超时时间等    urlConnection.connect();   	// 获取状态    final int statusCode = urlConnection.getResponseCode();    // 判断状态为200 在获取inputStream 返回    if (statusCode / 100 == 2) {        return getStreamForSuccessfulRequest(urlConnection);    } else if (statusCode / 100 == 3) {        // 判断状态为300 则为重定向 获取重定向的url        String redirectUrlString = urlConnection.getHeaderField("Location");        // 若重定向url为空则抛出异常        if (TextUtils.isEmpty(redirectUrlString)) {            throw new IOException("Received empty or null redirect url");        }        // 若重定向url不为空则重新调用loadDataWithRedirects()自身参数为重定向url        URL redirectUrl = new URL(url, redirectUrlString);        return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);    }}
  • 至此网络请求结束
网络请求总结
  • 通过数据来源对应的数据加载器DataFetcher调用LoadData
  • 这里我们以Url为例对应的DataFetcherHttpUrlFetcherLoadData内部通过loadDataWithRedirects获取数据
  • 内部通过给Glide设置的url参数来构建HttpURLConnection
  • 通过连接urlConnection对象获取states
  • 判断statescode是否 200
  • 是则获取urlConnection内的inputStream 并返回
  • 判断statescode是否300
  • 是则需要重定向,获取重定向url ,判断该url是否为空
  • 是则抛出异常
  • 否 则根据重定向url构建新的Url 并再次调用方法loadDataWithRedirects获取数据
  • 至此当statescode=200 时 数据请求结束代码执行到了decodeSource中 2处的 代码 decodeFromSourceData 将源数据解码

解码 & 存 原始数据和解码器

至此当statescode=200 时 数据请求结束代码执行到了decodeSource中 2处的 代码 decodeFromSourceData 将源数据解码

private Resource<T> decodeFromSourceData(A data) throws IOException {        final Resource<T> decoded;        // 1 如果使用磁盘缓存则缓存 原始数据 以及 解码器        if (diskCacheStrategy.cacheSource()) {            decoded = cacheAndDecodeSourceData(data);        } else {         // 2 如果不适用磁盘缓存则将原始数据根据解码器解码            decoded = loadProvider.getSourceDecoder().decode(data, width, height);        }        // 返回解码后的 Resource        return decoded;    }

位置 1 若使用磁盘缓存则缓存 原始数据 以及 解码器

如果使用磁盘缓存则将原始数据解码 缓存

private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {       	// 创建 SourceWriter 内部维护 解码器和原始数据        SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);     	// 将writer缓存到磁盘缓存        diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);        // 获取缓存 赋值给result        Resource<T> result = loadFromCache(resultKey.getOriginalKey());        // 返回        return result;    }
  • loadProvider 继承DataLoadProvider 是个接口
  • 资源加载器,资源它提供必要的编码器和解码器,以便从特定类型的数据解码特定类型的资源。
  • 在生成GenericRequestBuilder时根据创建
  • 在DrawableTypeRequest的构造函数里调用super的构造函数时调用buildProvider生成
buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class,GlideDrawable.class, null)

位置2 若禁止使用磁盘缓存 则 解码返回

loadProvider.getSourceDecoder().decode(data, width, height);
  • 调用 loadProvider.getSourceDecoder()转接调用StreamBitmapDataLoadProvider,实际调用的是StreamBitmapDecoder(因为这里我们以url为例子),调用的decode方法如下
public Resource<Bitmap> decode(InputStream source, int width, int height) {    // 解码成bitmap  具体解码就不看了 有兴趣自己分析下      Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);    // 将bitmap,和bitmap池封装成 BitmapResource返回        return BitmapResource.obtain(bitmap, bitmapPool);    }
  • 此时解码完成执行return 返回resourcedecodeSource()执行完成代码指定到了,decodeFromSource中位置2 的代码transformEncodeAndTranscode(decoded)将网络请求的数据源进行转码,同时存到磁盘缓存

解码存 总结

  • 如果使用磁盘缓存则缓存 原始数据 以及 解码器
  • 如果不使用磁盘缓存则将原始数据根据解码器解码返回

转变 & 存转换数据

transformEncodeAndTranscode(decoded)

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {        long startTime = LogTime.getLogTime();    	// 1. 将数据转变        Resource<T> transformed = transform(decoded);        if (Log.isLoggable(TAG, Log.VERBOSE)) {            logWithTimeAndKey("Transformed resource from source", startTime);        }		// 2 将转变的数据存入本地        writeTransformedToCache(transformed);        // 3 将数据转码成对应的图片类型资源为返回 比如bitmap 转换成Drawable        Resource<Z> result = transcode(transformed);        if (Log.isLoggable(TAG, Log.VERBOSE)) {            logWithTimeAndKey("Transcoded transformed from source", startTime);        }        return result;    }

位置 1 代码 数据转换 transform(decoded);

  • 执行的是我们在 Glide 加载图片设置的 bitmapTransform() 方法,设置类似 模糊图片、圆角图片 等我们需要的功能
private Resource<T> transform(Resource<T> decoded) { 	// 通过transformation.transForm转换    Resource<T> transformed = transformation.transform(decoded, width, height);    return transformed;}
  • transformation 是个接口,用于在资源上执行任意转换的接口 其实现类如下
  • BitmapTransformation
  • GifBitmapWrapperTransformation
  • GifDrawableTransformation
  • MultiTransformation<T>
  • UnitTransformation<T>
  • 以为例
public final Resource<Bitmap> transform(Resource<Bitmap> resource, int outWidth, int outHeight) {       	//获取bitmap        Bitmap toTransform = resource.get();        int targetWidth = outWidth == Target.SIZE_ORIGINAL ? toTransform.getWidth() : outWidth;        int targetHeight = outHeight == Target.SIZE_ORIGINAL ? toTransform.getHeight() : outHeight;        //根据宽高转换图片        Bitmap transformed = transform(bitmapPool, toTransform, targetWidth, targetHeight);        //创建Resource<Bitmap> result赋值返回        final Resource<Bitmap> result;        // 判断 如果转换前的数据和转换后的数据相同则返回未转换的数据        if (toTransform.equals(transformed)) {            result = resource;        } else {            // 如果不同则返回转换后的数据            result = BitmapResource.obtain(transformed, bitmapPool);        }        return result;    }

位置2 代码 存 writeTransformedToCache(transformed);

执行缓存动作

private void writeTransformedToCache(Resource<T> transformed) {    if (transformed == null || !diskCacheStrategy.cacheResult()) {        return;    }    long startTime = LogTime.getLogTime();    //构建SourceWriter将编码器和转换后的数据封装    SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);    // 将转换后返回的数据存入磁盘缓存,至于缓存的是转换后的数据还是为转换的数据是根据转换方法中,转换前后的hash地址对比确定的如果    // 如果转换前的数据与转换后的数据相同则存储未转换数据    // 如果不同则存储转换后的数据    diskCacheProvider.getDiskCache().put(resultKey, writer);}
转变&存总结
  • 将数据转码对比转码后的数据判断是否相同
  • 相同则缓存未转码数据
  • 不同则缓存转码数据

流程总结

  1. decodeSource()网络请求获取数据之后调用decodeFromSourceData
  • 解码
  • 判断是否禁用缓存,若没禁用则构建SourceWriter将解码器和原始数据封装并缓存,并从缓存取出数据返回
  • 若禁用缓存则直接解码返回解码后的数据
  1. 转变
  • 当解码完成后调用transformEncodeAndTranscode对数据进行转变,转变完成后则构建SourceWriter将编码器和转换后的数据封装 存到缓存中
  1. 调用transcode(transformed);将数据转码为对应图片资源返回
  • 解码转码过程
  • glide 不能直接加载bitmap 但是可以把bitmap转换成drawable 或者byte数组
  • 在Glide抓取到数据后,会转换成为InputStream,此时,通过类型模型转换的思想,从解码注册器中,找到可以解码InputStream的解码器,有StreamBitmapDecoder和StreamGifDecoder,我们知道最后最有StreamBitmapDecoder可以顺利解码器数据,成为一张Bitmap数据。
    我们在Glide.with(this).load(url).into(iv_img);中知道(以下代码),我们的目标是获取一个Drawable,即transcodeClass实际上是Drawable.class,因此,通过匹配寻找,获取到BitmapDrawableTranscoder转码器。