Glide load源码、缓存机制分析,一举拿下腾讯美团滴滴offer

}

return cached;

}

这个方法和之前的 loadFromActiveResources差别不大,先看看 getEngineResourceFromCache(key)

// Engine.java

private final MemoryCache cache;

private EngineResource<?> getEngineResourceFromCache(Key key) {

Resource<?> cached = cache.remove(key); // 1

final EngineResource<?> result;

if (cached == null) {

result = null;

} else if (cached instanceof EngineResource) {

result = (EngineResource<?>) cached;

} else {

result = new EngineResource<>(cached, true /isMemoryCacheable/, true /isRecyclable/);

}

return result;

}

这个方法就是从 MemoryCache中拿出一个EngineResource并返回。那我们要去看看 MemoryCache做了啥,但是MemoryCache是一个接口类,没有实现,在向前找的时候发现它是在创建 Glide initalizeGlide() 的过程中创建的,这里直接看创建的代码:

// GlideBuilder.java

@NonNull

Glide build(@NonNull Context context) {

if (memoryCache == null) {

memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());

}

}

// LruResourceCache.java

public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {

}

LruResourceCache实现了MemoryCache,并继承了LruCache。

这里简单的描述一下LruCache,它是最近最少使用策略,实现原理是 accessOrder为true的 LinkedHashMap。所以在getEngineResourceFromCache的注释1中,它就是调用了 LinkedHashMap.remove(key)来看Map中是否有key对应的 EngineResources。

回到 loadFromCache中:

// Engine.java #loadFromCache

EngineResource<?> cached = getEngineResourceFromCache(key);

if (cached != null) {

cached.acquire();

activeResources.activate(key, cached);

}

如果remove到的资源是不为null的,则调用 acquire()方法,并且调用 activeResources.activate(key, cached),我们看看这个方法做了什么:

// ActiveResources.java

void activate(Key key, EngineResource<?> resource) {

ResourceWeakReference toPut =

new ResourceWeakReference(

key,

resource,

getReferenceQueue(),

isActiveResourceRetentionAllowed);

ResourceWeakReference removed = activeEngineResources.put(key, toPut);

if (removed != null) {

removed.reset();

}

}

它把 key和EngineResource作为元素 put到了 ActiveResources的 HashMap中。便于以后 loadFromActiveResources去拿。

然后最后return为null。 loadFromCache()就结束了。可以看出来 LruCache也是在运行时产生的,所以它也是内存缓存

这里出现了一个问题,这里出现了两个内存缓存,一个是HashMap缓存,一个是LruResourcesCache的缓存,在前者拿资源拿不到的情况下,去拿后者,如果后者拿到了,会把该资源放到前者中缓存。乍一看是没事找事,为什么不把所有的资源都放在一个cache下存储呢?这需要往后面的代码看。

接下来继续load()函数中:

private final Jobs jobs;

// Engine.java #load

EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);

if (current != null) {

current.addCallback(cb);

if (VERBOSE_IS_LOGGABLE) {

logWithTimeAndKey(“Added to existing load”, startTime, key);

}

return new LoadStatus(cb, current);

}

通过 Jobs去拿一个 EngineJob,如果 EngineJob不为null,则调用其 addCallback(),这个方法最终也会调用 onResourceReady(),并返回一个 LoadStatus 。先来看看 jobs的get方法:

// Jobs.java

private final Map<Key, EngineJob<?>> jobs = new HashMap<>();

private final Map<Key, EngineJob<?>> onlyCacheJobs = new HashMap<>();

EngineJob<?> get(Key key, boolean onlyRetrieveFromCache) {

return getJobMap(onlyRetrieveFromCache).get(key);

}

private Map<Key, EngineJob<?>> getJobMap(boolean onlyRetrieveFromCache) {

return onlyRetrieveFromCache ? onlyCacheJobs : jobs;

}

onlyRetrieveFromCache这个字段中文意思为:是否只从Cache中搜索。默认情况下是 false。它就会去 jobs这个HashMap中拿EngineJob。这里也是一个缓存,但他并不是之间缓存图片资源。

EngineJob它不是一个图片资源,那它是什么呢?这里还不是很清晰,先往load下面走,

// Engine.java #load

EngineJob engineJob =

engineJobFactory.build(

key,

isMemoryCacheable,

useUnlimitedSourceExecutorPool,

useAnimationPool,

onlyRetrieveFromCache);

DecodeJob decodeJob =

decodeJobFactory.build(

glideContext,

model,

key,

signature,

width,

height,

resourceClass,

transcodeClass,

priority,

diskCacheStrategy,

transformations,

isTransformationRequired,

isScaleOnlyOrNoTransform,

onlyRetrieveFromCache,

options,

engineJob);

jobs.put(key, engineJob);

engineJob.addCallback(cb);

engineJob.start(decodeJob);

这里是load方法的最后一步,在上述三种缓存都命中不到资源的情况下,会创建一个 EngineJob和一个 DecodeJob,将 key和engineJob一起绑定加入到 Jobs的HashMap,这样之后上面的jobs缓存就有资源可以找,然后调用 EngineJob.addCallbackEngine.start()

这里的重点是 start方法,它是没有内存缓存后,去做的事情,所以这里算是一个新的开始。 也就是说,load可以看成两个部分,这里开始就是第二个部分。

1.2 EngineJob的start方法解析


// EngineJob.java

private final GlideExecutor diskCacheExecutor;

private final GlideExecutor sourceExecutor;

private final GlideExecutor sourceUnlimitedExecutor;

private final GlideExecutor animationExecutor;

public void start(DecodeJob decodeJob) {

this.decodeJob = decodeJob;

GlideExecutor executor = decodeJob.willDecodeFromCache()

? diskCacheExecutor
getActiveSourceExecutor();

executor.execute(decodeJob);

}

EngineJob里面有线程池执行器 GlideExecutor,它就是一个ExecutorService,因为加载图片是一个耗时操作,所以放到子线程做,所以这里出现了 Executor,其次,在EngineJob中,exeutor执行器并不是只有一个:

它会先根据缓存策略,拿到对应executor,比如这里的 :

// EngineJob.java

GlideExecutor executor = decodeJob.willDecodeFromCache()

? diskCacheExecutor
getActiveSourceExecutor();

就会根据 “是否在Cache中解码” 这个条件来拿 disCacheExecutor,还是其他三个中的执行器。

在默认情况下, DiskCacheStategey是 AUTOMATIC的,这里就是true,即用的是 diskCacheExecutor。

接下来就会拿着这个线程执行器去执行 DecodeJob,就是调用它的 run()

// DecodeJob.java

@Override

public void run() {

DataFetcher<?> localFetcher = currentFetcher;

try {

if (isCancelled) {

notifyFailed();

return;

}

runWrapped();

} catch (Throwable t) {

} finally {

}

}

notifyFailed()是通知失败的方法,然后没取消,就调用 runWrapped()

// DecodeJob.java

private void runWrapped() {

switch (runReason) {

case INITIALIZE:

stage = getNextStage(Stage.INITIALIZE);

currentGenerator = getNextGenerator();

runGenerators();

break;

case SWITCH_TO_SOURCE_SERVICE:

runGenerators();

break;

case DECODE_DATA:

decodeFromRetrievedData();

break;

default:

throw new IllegalStateException("Unrecognized run reason: " + runReason);

}

}

这里根据 runReason分成了三种情况:

  • INITIALIZE

在第一次我们提交执行任务的状态

  • SWITCH_TO_SOURCE_SERVICE

当我们想要从 DiskCache 的做法切换到 Source执行的做法(这里我也不知道干嘛的)

  • DECODE_DATA

该状态标识着:当我们在子线程中拿到了一个我们从未拥有过的资源,这个时候我们需要切换回主线程,去让主线程得到这个资源。

当然了,上面的三个是执行 DecodeJob的原因,它强调的是原因,DecodeJob又维护了一套当前的状态,和上面产生了联系:

  • INITIALIZE

和上面初始时一样

  • RESOURCE_CACHE

根据一个 缓存资源 进行 解码(Decode)

  • DATA_CACHE

根据一个 缓存的源数据 进行 解码(Decode)

  • SOURCE

根据 获取到的源数据 进行 解码

  • ENCODE

在一个 资源成功加载后, 为了将它缓存起来,进行编码

  • FINISHED

要结束了

假设我们是第一次进入到这个方法,那我们的runReason是 INITIALIZE,接下来会调用 getNextState(Stage.INITIALIZE):

// DecodeJob.java

private Stage getNextStage(Stage current) {

switch (current) {

case INITIALIZE:

return diskCacheStrategy.decodeCachedResource()

? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);

case RESOURCE_CACHE:

return diskCacheStrategy.decodeCachedData()

? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);

case DATA_CACHE:

return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;

case SOURCE:

case FINISHED:

return Stage.FINISHED;

default:

throw new IllegalArgumentException("Unrecognized stage: " + current);

}

}

// 这里是默认情况下的 DiskCacheStaratey(磁盘)的缓存模式:

// DiskCacheStrategy。java

public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {

@Override

public boolean isDataCacheable(DataSource dataSource) {

return dataSource == DataSource.REMOTE;

}

@Override

public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,

EncodeStrategy encodeStrategy) {

return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)

|| dataSource == DataSource.LOCAL)

&& encodeStrategy == EncodeStrategy.TRANSFORMED;

}

@Override

public boolean decodeCachedResource() {

return true;

}

@Override

public boolean decodeCachedData() {

return true;

}

};

在 INITIALIZE状态下调用了 getNextStage(),state就变成了 Stage.RESOURCE_CACHE,接下来在 runWrapped中:

调用了 currentGenerator = getNextGenerator()

private DataFetcherGenerator getNextGenerator() {

switch (stage) {

case RESOURCE_CACHE:

return new ResourceCacheGenerator(decodeHelper, this);

case DATA_CACHE:

return new DataCacheGenerator(decodeHelper, this);

case SOURCE:

return new SourceGenerator(decodeHelper, this);

case FINISHED:

return null;

default:

throw new IllegalStateException("Unrecognized stage: " + stage);

}

}

返回了一个 ResourceCacheGenerator,它现在是做什么的还不知道。

继续往下走 ,会调用 runGenerators()

// DecodeJob.java

private void runGenerators() {

currentThread = Thread.currentThread();

startFetchTime = LogTime.getLogTime();

boolean isStarted = false;

while (!isCancelled && currentGenerator != null

&& !(isStarted = currentGenerator.startNext())) { // 1

stage = getNextStage(stage); // 2

currentGenerator = getNextGenerator(); // 3

if (stage == Stage.SOURCE) {

reschedule(); // 4

return;

}

}

if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {

notifyFailed();

}

}

这个方法会开启一个 while循环,判断条件中第三个较为关键:调用 currentGenerator.startNext()

如果这个方法的结果为false,则 stage进入到下一个状态, currentGenerator换成下一个执行器。

在当前状态为 Stage.SOURCE时,调用了 reschedule(),然后return,也就是说 SOURCE为当前decodeJob执行的最后一个状态。

如果要一直到最后 SOURCE,那么当前的DecodeJob会走这么个顺序:

(1)DecodeJob的状态:

RESOURCE_CACHE -> DATA_CACHE -> SOURCE

(2)currentGenrator的变化:

ResourceCacheGenerator -> DataCacheGenerator -> SourceGenerator

(3) startNext的执行

ResourceCacheGenerator.startNext() = false -> DataCacheGenerator .startNext() = false

这就是在 startNext()一直为false的情况下。 我们现在就去研究一下这个方法,由于 Generator是一个发生器,在这里分成了两个,所以startNext()有必要拆出来讲解。

startNext()这个方法有点长,并且借助了一个新的类 DecodeHelper的辅助,这个类主要做了组件注册,用来扩展或替换Glide的默认加载,还有负责解码、编码的Registry类。而且我们其实还并不是很清楚 ResourceCacheGenerator、DataCacheGenerator、SourceGenerator这些所关联 DataFetcher的类是做什么的,所以有必要先简单的介绍他们。

1.3 DataFetcher、ModelLoader以及DecodeHelper的介绍


1.关于DataFetcher

它是一个接口,先来看看它的定义:

// DataFetcher.java

public interface DataFetcher {

interface DataCallback {

void onDataReady(@Nullable T data); // 资源已经弄好了

void onLoadFailed(@NonNull Exception e); //加载失败回调

}

void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback); //加载方法

void cleanup(); //资源释放

void cancel(); // 取消加载

@NonNull

Class getDataClass();

@NonNull

DataSource getDataSource();

}

DataFetcher是Glide的数据加载模块,定义了数据的加载时遵循的框架。

它的使用大概就是:通过调用 loadData()来加载资源,当加载成功后,调用 onDataReady()进行回调。如果失败,调用 onLoadFailed()。非常好懂。

它有很多个实现类,比如:

  • LocalUriFetcher

使用 ContentResolver 从本地资源的Uri加载数据

  • AssetPathFetcher

使用 AssetManager从asset path中获取数据的抽象类

  • HttpUrlFetcher

具体加载的数据类型为 InputStream,从网络Url中获取数据

2.关于ModelLoader

它也是个接口,来看看代码:

public interface ModelLoader<Model, Data> {

class LoadData {

public final Key sourceKey;

public final List alternateKeys;

public final DataFetcher fetcher;

public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher fetcher) {

this(sourceKey, Collections.emptyList(), fetcher);

}

public LoadData(@NonNull Key sourceKey, @NonNull List alternateKeys,

@NonNull DataFetcher fetcher) {

this.sourceKey = Preconditions.checkNotNull(sourceKey);

this.alternateKeys = Preconditions.checkNotNull(alternateKeys);

this.fetcher = Preconditions.checkNotNull(fetcher);

}

}

@Nullable

LoadData buildLoadData(@NonNull Model model, int width, int height,

@NonNull Options options);

boolean handles(@NonNull Model model);

}

它是一个工厂类的接口,用于将任意复杂的数据模型转换为具体的数据类型。

复杂的数据模型:通过给Glide设置了的各种属性,比如动画、缩放、模糊等等配置

具体的数据类型:LoadData ---- 由一系列的Key和DataFetcher组成的数据结构

它可以构建像 HttpGlideUrlLoader,内部构建一个HttpUrlFetcher,来进行网络的下载资源

也可以构建 FileLoader,内部构建一个 FileFetcher,来进行文件资源的获取。

它和DataFetcher是一起绑定的。

3. DecodeHelper

具体讲解需要费大量时间,所以可能会对本文出现岔道,所以具体请看这篇 DecodeHelper类相关方法分析

我们在 Glide.with().load(url) 的load中输入了获取图片资源的方式,这个方式可能是String、BitMap、Uri等。而 DecodeHelper的作用就是通过分析这个方式,分析出我们想要获取图片资源,需要哪些 ModelLoader、CacheKeys等。

比如当我们输入的是一个Url的字符串,它就能分析出我们所需的 cacheKey为:[GlideUrl,ObjectKey],从而获取到对应的LoadData

1.4 DataFetcherGenerator.startNext()


在1.2节中讲到,如果说一直要执行到 SOURCE状态,要先执行 ResourceCacheGenerator.startNext()DataCacheGenerator .startNext() = false ,这里看下两个方法的实现。

1.关于ResourceCacheGenerator的实现

// ResourceCacheGenerator.java

public boolean startNext() {

List sourceIds = helper.getCacheKeys(); // 1

if (sourceIds.isEmpty()) { // 2

return false;

}

List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses(); // 3

if (resourceClasses.isEmpty()) {

if (File.class.equals(helper.getTranscodeClass())) {

return false;

}

}

while (modelLoaders == null || !hasNextModelLoader()) {

resourceClassIndex++;

if (resourceClassIndex >= resourceClasses.size()) {

sourceIdIndex++;

if (sourceIdIndex >= sourceIds.size()) {

return false;

}

resourceClassIndex = 0;

}

Key sourceId = sourceIds.get(sourceIdIndex);

Class<?> resourceClass = resourceClasses.get(resourceClassIndex);

Transformation<?> transformation = helper.getTransformation(resourceClass);

currentKey =

new ResourceCacheKey(

helper.getArrayPool(),

sourceId,

helper.getSignature(),

helper.getWidth(),

helper.getHeight(),

transformation,

resourceClass,

helper.getOptions()); // 4

cacheFile = helper.getDiskCache().get(currentKey); // 5

if (cacheFile != null) {

sourceKey = sourceId;

modelLoaders = helper.getModelLoaders(cacheFile);

modelLoaderIndex = 0;

}

}

loadData = null;

boolean started = false;

while (!started && hasNextModelLoader()) {

ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);

loadData = modelLoader.buildLoadData(cacheFile,

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

这里我特地整理了一份《Android开发核心知识点笔记》,里面就包含了自定义View相关的内容

除了这份笔记,还给大家分享 Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

分享上面这些资源,希望可以帮助到大家提升进阶,如果你觉得还算有用的话,不妨把它们推荐给你的朋友~

喜欢本文的话,给我点个小赞、评论区留言或者转发支持一下呗~

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

628)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-i4IIdO0p-1712136289629)]

最后

这里我特地整理了一份《Android开发核心知识点笔记》,里面就包含了自定义View相关的内容

[外链图片转存中…(img-oqP3u1L2-1712136289629)]

除了这份笔记,还给大家分享 Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

[外链图片转存中…(img-xQGE3RFt-1712136289630)]

分享上面这些资源,希望可以帮助到大家提升进阶,如果你觉得还算有用的话,不妨把它们推荐给你的朋友~

喜欢本文的话,给我点个小赞、评论区留言或者转发支持一下呗~

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值