Glide相信大家并不陌生,我项目里面用的也是Glide,但是一直在用,一直也听别人说它好,但是具体哪儿好呢?今天我就来跟大家分享一下Glide的优点。
    作为一个图片框架,最核心的就是其缓存机制,这里我们就来分析一下Glide的缓存机制。Glide采用的是二级缓存:
        1.磁盘缓存和内存缓存。磁盘缓存主要是为了防止应用重复从网上下载图片,浪费资源和流量。     

    2.内存缓存主要是为了防止应用重复的读取本地图片到内存中,从而避免出现大量图片重复创建/回收的问题,从而尽可能的避免内存抖动,减少卡顿。

       3.内存缓存分为两级:采用LRUCache的内存缓存(缓存访问过的图片) 和 采用WeakReference的内存缓存(缓存正在使用的图片)

    缓存KEY
        大家都知道Glide在第一加载图片的时候没有Picasso快,但是在图片已经缓存下来的情况下Glide比Picasso要快的多,这是为什么呢?这是因为Glide缓存的图片是裁剪缩放后的图片,即Glide缓存的图片与控件的宽高有关,就算是同一张图片,如果在界面上显示控件的大小不一样,缓存的依然是不同的两份。Glide首次下载图片后会对图片进行缩放裁剪然后缓存到本地,而Picasso加载到图片后直接进行缩放然后展示出来,所以比Glide要快,而在图片已经加载的情况下,Glide直接拿本地缓存进行展示,不需要再次对图片进行裁剪缩放,所以会比Picasso快的多。那么从哪儿可以看出Glide缓存的图片与图片控件的宽高有关系呢?当然是看生成key的地方咯,生成key的地方在Engine.java里面,我们看看源码

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();
        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) {
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

        EngineJob current = jobs.get(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);
    }

    从以上代码可以看出生成EngineKey是通过EngineKeyFactory来生成的,传递的参数有id和width、height等,id就是图片的url,width、height就是图片控件的宽高

    缓存

    开篇我们说到Glide采用的是两级内存缓存,一个LRUCache缓存访问过的图片,一个WeakReference缓存正在使用的图片。那么这里不知道大家有没有这样一个疑问,为什么要弄两个缓存,直接用一个LRUCache不就行了?哈哈哈,这里就是Glide高级的地方,如果只用一个LRUCache的话,可能会出现正在使用的Bitmap会被回收的情况。假设现在加载了20张图片,那么最后加载的图片自然会在LRUCache的最前面,也就是最近使用的,而正在使用的图片可能会排在LRUCache的后面,也就是说可能会被回收掉,所以这里采用WeakReference来缓存正在使用的图片。上面load方法里面有这样一段代码,就是load的时候会先调用loadFromCache,如果获取不到,会继续调用loadFromActiveResources

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) {
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

    loadFromCache()顾名思义就是从内存缓存里面获取,loadFromActiveResources()就是从正在使用的缓存里面获取。我们看看这两个方法的源码

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
            cached.acquire();
            // *** 将其放入WeakReference中缓存
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        return cached;
    }

    @SuppressWarnings("unchecked")
    private EngineResource<?> getEngineResourceFromCache(Key key) {
        // *** 注意,此处用的是cache.remove(key)来获取缓存的,也就是说获取到缓存后会将其移除LRUCache
        Resource<?> cached = cache.remove(key);

        final EngineResource result;
        if (cached == null) {
            result = null;
        } else if (cached instanceof EngineResource) {
            // Save an object allocation if we've cached an EngineResource (the typical case).
            result = (EngineResource) cached;
        } else {
            result = new EngineResource(cached, true /*isCacheable*/);
        }
        return result;
    }

private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
            active = activeRef.get();
            if (active != null) {
                // *** 每次调用acquire方法访问计数器acquired都会+1 每次调用release方法的时候-1
                active.acquire();
            } else {
                activeResources.remove(key);
            }
        }

        return active;
    }

    每次调用loadFromCache()的时候,使用的是MemoryCache.remove(key)可以方法获取的缓存,也就是获取到缓存后会将它从LRUCache里面移除,这样做是为了防止正在被使用的图片被LRUCache清除掉。那么问题来了,一张正再被使用的图片会从LRUCache里面清除,那如果一直滑动,图片不可见的时候岂不是缓存都没有了,因为每次访问后缓存都会从LRUCache里面清除?说到这里不知道大家还记不记得开篇我有提到Glide的内存缓存采用的是LRUCache和WeakReference两级缓存,正在被使用的图片会被WeakReference引用起来,上面代码里面我已经注释了,即loadFromCache()获取到缓存后会调用
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
那么问题依然存在,只是从LRUCache里面取出缓存并且移除了,而正在使用的图片并没有放进LRUCache里面啊。不知道大家有没有注意到上面代码里面我写了一句注释"每次调用acquire方法会使得对象引用计数器+1,每次调用release引用计数器-1",那么我们就来看看引用计数器-1的代码(大家看注释即可,无需解释)

// Engine.java的release方法
public void release(Resource resource) {
        Util.assertMainThread();
        if (resource instanceof EngineResource) {
            ((EngineResource) resource).release();
        } else {
            throw new IllegalArgumentException("Cannot release anything but an EngineResource");
        }
    }

// 上面调用的EngineResource的release方法
void release() {
        if (acquired <= 0) {
            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call release on the main thread");
        }
        if (--acquired == 0) {
            //acquired为0的时候说明图片没有正在使用
            listener.onResourceReleased(key, this);
        }
    }

// Engine.java实现了上面调用的ResourceListener接口
public void onResourceReleased(Key cacheKey, EngineResource resource) {
        Util.assertMainThread();
        activeResources.remove(cacheKey);
        if (resource.isCacheable()) {
            //也就是说暂时不使用的时候会将正在使用的图片放入LRUCache
            cache.put(cacheKey, resource);
        } else {
            resourceRecycler.recycle(resource);
        }
    }

    那么从网络加载图片完成后是如何通知控件的呢,这里采用的是Handler--Message机制,下载图片属于网络请求,必然是在子线程执行,子线程图片加载完毕需要更新UI,自然是通过Handler发送消息来切换到主线程。这里图片加载完毕后是在EngineJob.java里面,我们来看看代码

public void onResourceReady(Resource<?> resource) {
        this.resource = resource;
        MAIN_THREAD_HANDLER.obtainMessage(1, this).sendToTarget();
    }

    private void handleResultOnMainThread() {
        if(this.isCancelled) {
            this.resource.recycle();
        } else if(this.cbs.isEmpty()) {
            throw new IllegalStateException("Received a resource without any callbacks to notify");
        } else {
            this.engineResource = this.engineResourceFactory.build(this.resource, this.isCacheable);
            this.hasResource = true;
            this.engineResource.acquire();
            // 这里的listener就是Engine.class
            this.listener.onEngineJobComplete(this.key, this.engineResource);
            Iterator i$ = this.cbs.iterator();

            while(i$.hasNext()) {
                ResourceCallback cb = (ResourceCallback)i$.next();
                if(!this.isInIgnoredCallbacks(cb)) {
                    this.engineResource.acquire();
                    cb.onResourceReady(this.engineResource);
                }
            }

            this.engineResource.release();
        }
    }

// Engine.java里面的onEngineJobComplete回调
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
        Util.assertMainThread();
        if(resource != null) {
            resource.setResourceListener(key, this);
            if(resource.isCacheable()) {
                // ***在这里写入缓存
                this.activeResources.put(key, new Engine.ResourceWeakReference(key, resource, this.getReferenceQueue()));
            }
        }

        this.jobs.remove(key);
    }

    磁盘缓存
        上面我们分析了两级内存缓存,那么磁盘缓存在哪里呢,在EngineRunnable里面,

private Resource<?> decode() throws Exception {
        return this.isDecodingFromCache()?this.decodeFromCache():this.decodeFromSource();
    }

    private Resource<?> decodeFromCache() throws Exception {
        Resource result = null;

        try {
            //首先从RESULT缓存中取
            result = this.decodeJob.decodeResultFromCache();
        } catch (Exception var3) {
            if(Log.isLoggable("EngineRunnable", 3)) {
                Log.d("EngineRunnable", "Exception decoding result from cache: " + var3);
            }
        }

        if(result == null) {
            //如果RESULT缓存中没有再从SOURCE缓存中获取
            result = this.decodeJob.decodeSourceFromCache();
        }

        return result;
    }

    private Resource<?> decodeFromSource() throws Exception {
        return this.decodeJob.decodeFromSource();
    }

   以上decodeResultFromCache 对应DiskCacheStrategy.RESULT  decodeSoureFromCache对应DiskCacheStrategy.SOURCE
讲到这里相信大家对Glide的缓存逻辑已经非常清楚了,如果对压缩感兴趣可以查看DownSampler.java,具体细节大家可以自行翻阅源码