1.引言
最近在开发产品过程中,项目经理提了一个问题:"为什么图片加载这么缓慢?",我看了看的确非常缓慢,图片加载用的经典的Glide框架,慢难道仅仅只是网络的原因?带着这份不解,我下了一个glide源码,开启了学习之路。
2.正题
通过学习想彻底弄懂这几个问题:
Glide加载流程
Glide切换Activity,是否有中断和恢复操作
Glide的内存管理
Glide的弱网管理机制
2.1 Glide总体加载流程
未命名文件.jpg
Glide框架就像是一条流水线,每个环节做什么,每个环节为下一步输出什么。都非常清楚明了。把握其中的主要流程和环节,那么搞懂全貌只是时间问题。
接下来我们以下面的代码为基础开始正式分析我们的glide源码加载流程:
Glide.with(Context context)
.load(Strint url)
.into(ImageView imageView);
2.1.1 Request产生阶段
Glide类:
Glide类和OkhttpClient一样,首先都是一个单例,目的是为整个框架做初始化以及为使用者提供唯一的使用窗口:
Glide.with 接受如下几个参数:
@see #with(android.app.Activity)
@see #with(android.app.Fragment)
@see #with(androidx.fragment.app.Fragment)
@see #with(androidx.fragment.app.FragmentActivity)
不同的参数,所代表的lifecycle也就不同。这样在生命周期处理这块就有所不同。with()方法最终会调用RequestManagerRetriever的get方法得到一个RequestManager
RequestManagerRetriever类:
RequestManagerRetriever类中是直接生产RequestManager的,生产主要是靠RequestManagerFactory。其次调用checkAndInitializeGlide()来初始化Glide
GlideBuilder类:
checkAndInitializeGlide()方法最终会调用到GlideBuilder.build. 对整个Glide框架中的GlideExecutor,加载引擎Engine,DiskCache 等做初始化。一个非常典型的建造者模式使用场景
RequestManager类:
RequestManager以Manager,肯定是包含了:
请求的创建,setRequestOptions
请求的管理队列 移除、等待
请求开始、请求暂停、取消、重新开始,设置RequestListener
请求的暂停、取消等是通过:RequestTracker类去跟踪处理
请求的创建以及各种配置是通过:RequestBuilder类完成
RequestBuilder类:
为Request设置完毕RequestOptions之后就会开始,进行build得到真正的Request对象。这一步骤是通过调用into(Target t)方法。
private > Y into(
@NonNull Y target,
@Nullable RequestListener targetListener,
BaseRequestOptions> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request request = buildRequest(target, targetListener, options, callbackExecutor);//
Request previous = target.getRequest();//判断此请求是否被加载过
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();//假如Request存在的话 就直接走begin开始走Engin流程
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);//开启网络请求
return target;
}
这一步骤应该有俩个疑问:
传入的ImageView如何转换成Target的
buildRequest方法中是如何产生一个Request的
解答1:
load(ImageView )方法最终会调用GlideContext.buildImageViewTarget 方法转换得到Target。源码如下:
public class ImageViewTargetFactory {
@NonNull
@SuppressWarnings("unchecked")
public ViewTarget buildTarget(
@NonNull ImageView view, @NonNull Class clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
}
解答2:
Request是通过GlideContext.buildRequestRecursive 产生的。Request是一个抽象类:具体的实现有这几个:
image.png
根据配置不同生成的Request就不同。一般网络请求产生的是SingleRequest; 到这里 前期工作做好了,Request也产生了。于是开始交给Engine引擎去深层次加工。
小结:Glide前期代码执行流程:
image.png
2.1.2 加载Request
上面得到Request之后,会调用RequestManager.track()开启 加载。RequestManager中对请求的管理。最终都是通过RequestTracker实现。
RequestTracker类:
作用:开始、暂停、取消请求
RunRequest方法 代码如下:
/** Starts tracking the given request. */
public void runRequest(@NonNull Request request) {
requests.add(request);//添加Request到set中
if (!isPaused) {
request.begin();//开启真正的请求
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
SingleRequest类:
由上面我们知道了,产生的request是SingleRequest.我们来看下SingleRequest是干什么的。
image.png
每个SingleRequest都有这6中状态。SingleRequest类有一个Status变量,用来标记当前SingleRequst的状态。
SingleRequest#begin方法如下:
@Override
public void begin() {
synchronized (requestLock) {
//加载完成从内存中去取
if (status == Status.COMPLETE) {
onResourceReady(
resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false); return;
}
// Restarts for requests that are neither complete nor running can be treated as new requests
// and can run again from the beginning.
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
}
onSizeReady() 调用了Engine.load方法正式开始加载。是加载的入口
loadStatus =
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
Engine类:
engine是整个Glide的灵魂所在,著名的缓存机制都是在Engine中进行的。和我们之前学习一样。Engine肯定又是一个“大家长”。其主要功能是负责开启load Request 且管理资源的缓存。这些功能都是由其内部一个又一个类构成。往往在这个类中就会声明实现各个模块的“小部件”
public class Engine
implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
private static final String TAG = "Engine";
private static final int JOB_POOL_SIZE = 150;
private static final boolean VERBOSE_IS_LOGGABLE = Log.isLoggable(TAG, Log.VERBOSE);
private final Jobs jobs;//通过HashMap记录产生的EngineJob
private final EngineKeyFactory keyFactory;
private final MemoryCache cache;//内存缓存机制第二层
private final EngineJobFactory engineJobFactory;//生产EngineJob
private final ResourceRecycler resourceRecycler;
private final LazyDiskCacheProvider diskCacheProvider;
private final DecodeJobFactory decodeJobFactory;
private final ActiveResources activeResources;//内存缓存机制第一层
Engine#load:
public LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class> resourceClass,
Class transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map, Transformation>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
//标记EngineResource的独一无二的key
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); //缓存机制1:从内存加载
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
loadFromMemory方法:就是Glide框架中的第一层缓存机制---内存缓存。
@Nullable
private EngineResource> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
EngineResource> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
EngineResource> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
我们首先看看第一层内存缓存 loadFromActiveResources:
@Nullable
private EngineResource> loadFromActiveResources(Key key) {
EngineResource> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
ActiveResources类:
final class ActiveResources {
private final boolean isActiveResourceRetentionAllowed;
private final Executor monitorClearedResourcesExecutor;
@VisibleForTesting final Map activeEngineResources = new HashMap<>();//缓存EngineResource。
private final ReferenceQueue> resourceReferenceQueue = new ReferenceQueue<>();
private ResourceListener listener;
ResourceWeakReference 对象可以理解成是对EngineResource包装。本身是弱引用。当前程序中被使用的EngineResource都会放在这个map中。这样当在其他地方再次使用,可以直接从内存中查找出来,根据EngineKey。
active.acquire();采用了引用计数机制(类似jvm内存管理中的),被引用一次就+1,当为0的时候,就会退居二线内存容器中,也就是会被添加到MemoryCache中。
我们首先看看第二层内存缓存loadFromCache:
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;
}
cache指的就是MemoryCache.其实现类是:LruResourceCache 类继承自 LruCache。内部主要是维护了一个LinkedHashMap。
关于Lru算法可以参考这个文章:Lru算法
LinkHashMap 参考这个:LinkHashMap介绍
GetEngineResourceFromCache 就是从第二层LruCache中查找EngineResource。上面是调用remove方法。意味着当命中EngineKey。LruCache会删除这个EngineKey。同时activeResources调用put方法。放进一级缓存中。同时计数+1
流程图:
image.png
内存缓存的机制就讲到这里。因为第一次加载网络图片,内存中肯定是找不到EngineResource的。 找不到怎么办呢? 会调用waitForExistingOrStartNewJob 方法进而产生一个EngineJob和DecodeJob
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, callbackExecutor);
engineJob.start(decodeJob);
EngineJob类:
EngineJob是Engine处理每个请求的最小单元,里面有load success/fail 等回调;也有开启load,取消load等操作
image.png
DecodeJob类:
DecodeJob 负责从diskCache中或者服务器上解码得到图片的数据。实现了Runnable接口。是真正进行请求的核心类;也是文件缓存的入口。
EngineJob调用Start方法治好,会执行DecodeJob run方法。run代码如下:
@SuppressWarnings("PMD.AvoidRethrowingException")
@Override
public void run() {
GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);
DataFetcher> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();//关键
} catch (CallbackException e) {
throw e;
} catch (Throwable t) {
}
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);
}
}
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);
}
}
DataFetcherGenerator是一个接口,实现类如下:
image.png
万众瞩目的文件缓存来!!!
runWrapped() 调用顺序
调用ResourceCacheGenerator.startNext方法 从文件中加载数据
找不到数据,调用DataCacheGenerator的startNext 从另外的文件中查询数据;
依然查询不到就调用SourceGenerator.startNext从网络上加载数据。
ResourceCacheGenerator.startNext方法如下:
currentKey =
new ResourceCacheKey( // NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(
cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.startNext(helper.getPriority(), this);
}
}
cacheFile = helper.getDiskCache().get(currentKey);
从文件去寻找图片资源
找到之后,调用去读取数据
loadData.fetcher.startNext(helper.getPriority(), this);
DataCacheGenerator的流程和ResourceCacheGenerator流程几乎一样。都是从文件中加载图片区别在于:
ResourceCacheGenerator文件中保存的是经过转换的图片,例如转换成圆角。那张圆角就是保存在ResourceCacheGenerator 中
DataCacheGenerator 是保存最原始的,也就是从服务器拉下来的那张图片。
当然这也与Glide 缓存策略有关:
ALL:既缓存原始图片,也缓存转换过后的图片;对于远程图片,缓存 DATA 和 RESOURCE;对于本地图片,只缓存 RESOURCE。
AUTOMATIC (默认策略):尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从 URL 下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过 (比如,变换、裁剪等) 的原始数据(DATA),因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图(RESOURCE),因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。
DATA:只缓存未被处理的文件。我的理解就是我们获得的 stream。它是不会被展示出来的,需要经过装载 decode,对图片进行压缩和转换,等等操作,得到最终的图片才能被展示。
NONE:表示不缓存任何内容。
RESOURCE:表示只缓存转换过后的图片(也就是经过decode,转化裁剪的图片)。
完整的内存缓存图:
image.png
DataFetcherGenerator 加载数据最终都是靠DataFetcher来实现的。DataFetcher接口。常用的DataFetch实现由如下三种:
image.png
HttpUrlFetcher:默认的从服务器拉取数据的实现。SourceGenerator 持有的DataFetcher
FileFetcher:从文件中读取数据。ResourceCacheGenerator持有的。
接下来看看 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) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
loadDataWithRedirects方法就不展开讲了,内部就是通过网络请求最终返回一个InputStream.
到此整个请求的数据就拿到了。Glide的缓存机制也分析了一遍。接下来就是各种回调。
2.1.3 请求回调处理
HttpUrlFetcher#loadData中的 callback.onDataReady(result);经过层层的回调一直到Engine类中。流程图如下:
image.png
以上就是整个Glide加载流程的主脉络。很多细节问题没有去深究。等以后遇到问题再去弄懂细节问题。
这篇文章整整花了4个小时时间抒写。回调章节讲的也是很简单。可以打印下堆栈快速弄懂回调逻辑。