Glide--解析

    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,具体细节大家可以自行翻阅源码

 

### 回答1: "failed to resolve: jp.wasabeef:glide-transformations:4.0.0" 是一个编译错误,意味着程序无法解决这个特定的依赖项。 这个错误通常出现在使用了依赖管理工具如Gradle或Maven时。它表明在项目构建过程中,无法正确找到并下载所需的依赖项。 有几个常见的原因会导致这个错误出现。首先,可能是因为你所使用的库的版本与你在构建文件中指定的版本不一致。在这种情况下,你需要确认库的最新版本,并更新你的构建文件。 其次,可能是因为库的地址错误或库不存在。你可以在Gradle或Maven的存储库中搜索以确定库是否存在,并检查库的名称和地址是否与你在构建文件中使用的版本一致。 最后,可能是由于网络连接错误,导致构建工具无法从存储库中下载依赖项。在这种情况下,你可以尝试使用代理或更改你的网络配置来解决问题。 总之,当你遇到"failed to resolve"的错误时,你需要检查构建文件中的依赖项的版本、地址以及你的网络连接,以确保能够成功解决依赖项。 ### 回答2: 在使用 Glide 图片加载库时出现了 "failed to resolve: jp.wasabeef:glide-transformations:4.0.0" 的错误。这个错误是由于 Gradle 无法解析指定的 Glide 图片转换库版本号4.0.0。 要解决这个问题,可以尝试以下几种方法: 1. 检查版本号:先确保正确的使用了最新版本的 Glide 和转换库。可以打开项目的 build.gradle 文件,查看 Glide 和转换库的版本号,确保二者兼容。 2. 检查依赖项:检查项目的 build.gradle 文件中是否正确添加了依赖项。需要确保在 dependencies 块中添加了正确的 Glide 和转换库依赖项,例如: ``` implementation 'com.github.bumptech.glide:glide:4.12.0' implementation 'jp.wasabeef:glide-transformations:4.3.0' ``` 3. 更新仓库:如果以上两个步骤都没问题,可能是 Gradle 无法从默认的 Maven 仓库中解析到转换库。可以尝试添加 `maven { url 'https://jitpack.io' }` 到项目的 build.gradle 文件中的 repositories 块中,确保 Gradle 可以解析 jitpack 仓库中的依赖。例如: ``` allprojects { repositories { // 其他仓库 maven { url 'https://jitpack.io' } } } ``` 4. 清理缓存:有时 Gradle 缓存可能会引起问题,可以尝试清理 Gradle 缓存并重新构建项目。可以通过在命令行运行 `./gradlew clean` 或者在 Android Studio 中点击 "Build" -> "Clean Project" 来清理缓存。 以上是解决 "failed to resolve: jp.wasabeef:glide-transformations:4.0.0" 错误的一些常见方法。根据具体情况选取适合的解决方案,希望能帮到你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值