本文通过在字节面试遇到的问题总结而出,如有不对地方,请及时批评指正。篇幅较长,请耐心阅读。
简介
Glide是一个优秀的图片加载框架,支持多种数据源,功能强大,性能高。如图所示:
使用步骤
1.在build.gradle中添加glide引用
implementation 'com.github.bumptech.glide:glide:4.12.0'
2.使用glide加载图片
Glide.with(this).load(BASE_PIC_URL).into(img_user)
源码缓存分析
LruCache算法
Glide四级缓存中使用了LruCache最近最少使用算法。其内部使用了LinkedHashMap存储数据,并默认支持排序访问。其工作原理如下:
假设当前LruCache的容量为4,依次向LruCache添加A,B,C,D试,正好LruCache容量已满,当添加第五个数据 E 时,LruCache会默认移除最先添加的 A,如果再添加 F,会移除 B。按照这个原理,LruCache会移除最近最少使用的数据。
四级缓存
Glide最为一个优秀的图片加载框架使用了四级缓存来加载图片。分别为ActiveResources 活动缓存,MemoryCache 内存缓冲,DiskCache 磁盘缓存,Http 网络缓存。如下图所示。
Glide的四级缓存实现比较复杂,分析源码时只关注主线流程。文章末尾用一张图来总结整个流程。篇幅较长,请耐心阅读
1.从Glide的into开始进行图片加载。
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
.......省略部分代码......
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
//请求配置参数
requestOptions,
//线程池
Executors.mainThreadExecutor());
}
2.构建一个请求任务,如果和上一个请求任务相同,则直接执行上一个请求任务。
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
.....省略部分代码....
//构建一个请求
Request request = buildRequest(target, targetListener, options, callbackExecutor);
//获取上一个请求
Request previous = target.getRequest();
//如果两个请求相同且已经完成请求
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
if (!Preconditions.checkNotNull(previous).isRunning()) {
//重新开始执行上一个请求
previous.begin();
}
return target;
}
//清除目标资源
requestManager.clear(target);
//资源绑定请求任务
target.setRequest(request);
//执行请求
requestManager.track(target, request);
return target;
}
3.根据资源宽,高加载资源。
@Override
public void begin() {
synchronized (requestLock) {
......省略部分代码......
// 判断宽,高是否有效
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
//重新获取宽高
target.getSize(this);
}
............
}
}
4.准备加载资源
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
......省略部分代码......
loadStatus =
//执行数据请求
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
.............,
this,
callbackExecutor);
...............
}
}
5.检查当前正在使用的资源集,如果存在则返回活动资源,并将任何新的不使用的资源移到内存缓存中。
检查内存缓存并提供缓存资源(如果存在)。
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
......................,
ResourceCallback cb,
Executor callbackExecutor) {
...........................
//构建资源的Key
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this) {
//首先从缓存中获取资源
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
//如果为空,则发起新的任务请求
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
....................,
cb,
callbackExecutor,
key,
startTime);
}
}
//将获取的资源回调回去
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, false);
return null;
}
6.首先从活动缓存loadFromActiveResources中获取资源。如果有直接返回,如果没有再从内存中查找资源loadFromCache,如果找到则返回,没有返回null。
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
//设置不使用缓存
if (!isMemoryCacheable) {
return null;
}
//1 .首先从活动缓存activeResources中load资源
EngineResource<?> active = loadFromActiveResources(key);
//如果资源不为空直接返回
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
//2 .如果活动缓存中没有,则从内存中获取资源
EngineResource<?> cached = loadFromCache(key);
//如果资源不为空直接返回
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
7.如果从内存中获取到资源,将资源返回同时保存到活动缓存中,并移除内存中的资源。
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
//保存到活动缓存中
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
//从内存中移除
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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
8.如果活动缓存和内存缓存中都没有,则从磁盘缓存或网络中获取。返回第5步
private <R> LoadStatus waitForExistingOrStartNewJob(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor,
EngineKey key,
long startTime) {
//从缓存中获取执行任务
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
//存在直接返回
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
//构建任务
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
//构建解码任务
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
..........
engineJob);
//添加任务队列
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
//开始执行解码任务
engineJob.start(decodeJob);
............
return new LoadStatus(cb, engineJob);
}
9.执行新的任务,engineJob.start 将decodeJob放进线程池中执行。所以decodeJob中肯定有一个run方法。
public void run() {
.........................
DataFetcher<?> localFetcher = currentFetcher;
try {
//取消
if (isCancelled) {
notifyFailed();
return;
}
//执行任务
runWrapped();
} catch (CallbackException e) {
//异常处理
....................
}
}
10.从初始状态INITIALIZE开始执行,获取当前资源执行器并执行
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);
}
}
11.根据配置的资源状态获取执行器
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);
}
}
12.根据判断当前任务执行情况,而获取下一个执行器执行。
private void runGenerators() {
................
while (!isCancelled
&& currentGenerator != null
/判断是否是属于开始执行的任务
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
.................
}
13.当执行到磁盘缓存执行器DataCacheGenerator,从磁盘缓存获取资源helper.getDiskCache().get(originalKey)。
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
//获取资源id
Key sourceId = cacheKeys.get(sourceIdIndex);
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
//创建资源key
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
//从磁盘缓存diskCacheProvider中获取缓存文件
cacheFile = helper.getDiskCache().get(originalKey);
............
}
14.最后执行到源数据执行器SourceGenerator。
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
//将得到的数据保存到磁盘缓存中
cacheData(data);
}
.................
while (!started && hasNextModelLoader()) {
//获取数据加载器
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
//开始加载
startNextLoad(loadData);
}
}
return started;
}
15.当没有配置任何缓存时,最后一步就是网络请求了。loadData.fetcher.loadData就只能是从网络获取数据。
private void startNextLoad(final LoadData<?> toStart) {
loadData.fetcher.loadData(
helper.getPriority(),
new DataCallback<Object>() {
@Override
public void onDataReady(@Nullable Object data) {
if (isCurrentRequest(toStart)) {
onDataReadyInternal(toStart, data);
}
}
@Override
public void onLoadFailed(@NonNull Exception e) {
if (isCurrentRequest(toStart)) {
onLoadFailedInternal(toStart, e);
}
}
});
}
16.最终执行到HttpUrlFetcher.loadData进行网络加载数据
@Override
public void loadData(
@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//通过网络请求返回数据流
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
} catch (IOException e) {
..............
}
}
17.获取流成功,将数据流返回,第15步
public void onDataReady(@Nullable Object data) {
if (isCurrentRequest(toStart)) {
onDataReadyInternal(toStart, data);
}
}
void onDataReadyInternal(LoadData<?> loadData, Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
//将得到的数据赋值给dataToCache
dataToCache = data;
cb.reschedule();
} else {
cb.onDataFetcherReady(
loadData.sourceKey,
data,
loadData.fetcher,
loadData.fetcher.getDataSource(),
originalKey);
}
}
分析到这里,整个Glide图片缓存加载就结束,总结如下:
- 活动缓存:优先从活动缓存中查找资源,如果找到直接返回资源。当活动缓存中的资源释放时,将资源放入内存缓存。使用Map缓存资源
- 内存缓存:当活动缓存中找不到可用资源,从内存缓存中查找,同时将内存缓存中的资源放入活动缓存,并从内存缓存中移除资源。使用LruCache算法进行资源保存。
- 磁盘缓存:当内存缓存中也找不到可用资源,则从磁盘缓存中查找。同时将资源放入活动缓存。
- 网络缓存:当磁盘缓存中也找不到时,就从网络加载,并将请求返回的资源存入磁盘缓存中。
以上就是字节面试后总结的几个要点,还不会的同学赶紧学起来吧,感谢您的阅读,创造不易,如果您觉得本篇文章对您有帮助,请点击关注小编,您的支持就是小编创作的最大动力!
如果您想了解其他框架源码,欢迎评论区留言!