前言
前一章介绍了Glide 的创建与Request 的创建,本篇我们探究Request 的执行过程。
一、下载图片
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
options = options.autoClone();
Request request = buildRequest(target, targetListener, options);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
request.recycle();
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
//
requestManager.track(target, request);
return target;
}
request的请求是由requestManager.track(target, request); 方法发起
targetTracker.track(target); 试讲target 加入到一个列表里面。targetTracker 用于保存当前活跃的所有Target。
public void runRequest(Request request) {
//将请求加入的队列当中
requests.add(request);
if (!isPaused) {
//开始执行请求
request.begin();
} else {
pendingRequests.add(request);
}
}
runRequest 首先将请求加入到队列里面,然后调用请求的begin 方法,这里以SImpleRequest为例分析。
public void begin() {
//代码有删减
startTime = LogTime.getLogTime();
//model 可以看成是图片额url
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
//抛出异常
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
//如果当前状态正处于加载图片的状态,则抛出异常
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
if (status == Status.COMPLETE) {
//如果当前状态处于已经加载完成,则回调资源已经加载完毕
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
//标识当前状态为等待获取View的大小信息,
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
//如果View大小已经测量完成,则调用onSizeReady方法进行图片加载请求
onSizeReady(overrideWidth, overrideHeight);
} else {
//如果view大小未就绪,则先去获取view的大小。
//这个很重要,实际是注册一个回调接口,在View测量完大小之后会
//调用onSizeReady。
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
//通知图片开始加载
target.onLoadStarted(getPlaceholderDrawable());
}
}
这个方法中,先是对model进行判断,这个model此时就是我们传的那个url,如果为空,则直接load失败,然后是一些状态的检查和一些回调方法等,接下来判断size,如果是有效的,则触发去真正的请求图片,否则设置一个回调,等待view布局有size之后,再来触发请求,真正的请求其实就在onSizeReady中被得到执行。
begin的步骤可以归纳如下:
a、如果当前状态是正在加载图片,则抛出异常,结束
b、如果当前状态是已经加载完成,则回调加载图片已就绪方法
c、如果当前View大小已经就绪,则调用onSizeReady方法实现图片加载
d、如果当前View大小为就绪,则等待View大小就绪再回调onSizeReady实现图片加载
e、调用onLoadStarted通知图片开始加载
onSizeReady 内部是通过线程池现在图片,在进入onSizeReady 之前我们先看target.onLoadStarted(getPlaceholderDrawable());
ImageViewTarget
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
setDrawable(placeholder);
}
public void setDrawable(Drawable drawable) {
view.setImageDrawable(drawable);
}
onLoadStarted 是在开启线程下载图片之前先显示占位图片。
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
if (IS_VERBOSE_LOGGABLE) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
//如果当前状态不处于等待获取View大小,则返回结束
return;
}
//更改当前状态为正在运行图片加载中
status = Status.RUNNING;
//获取图片大小的乘积系数,也就是放大或是缩小的倍数。
float sizeMultiplier = requestOptions.getSizeMultiplier();
//如果图片大小为-1,则返回图片大小为负数,否则=大小*sizeMultiplier
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
//通过图片加载引擎加载图片
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);
if (status != Status.RUNNING) {
loadStatus = null;
}
}
这个方法中,首先检查状态是不是在等待size,如果不是,则表明size已经有了,下面则是更新状态到Status.RUNNING,进而去调用Engine的load 方法。
Engine 是Glide很重要的一个类,其作用是执行请求以及管理请求下来的资源
public <R> LoadStatus load(
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) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
//构建唯一的key,
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//根据key从活跃的资源列表中获取图片资源对象
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
//如果活跃的图片资源对象存在,则回调资源加载已就绪方法。
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
//根据key从缓存中加载图片资源对象
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
//如果缓存图片资源对象存在,则回调资源加载已就绪方法
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
//根据key从图片加载任务中获取任务对象
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
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<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
//构建一个新的图片解析任务对象
DecodeJob<R> 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);
//调用图片加载任务的start方法开始加载图片
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
对于图片加载引擎Engine的load实现可以归纳如下:
a、构建图片加载对应的key
b、如果活跃资源列表中存在要加载的图片资源,则回调资源准备就绪
c、如果缓存列表中存在要加载的图片资源,则回调资源准备就绪
d、如果存在要加载图片对于的图片加载任务,则添加回调给图片加载任务,等待任务加载图片完成调度
e、构建图片加载任务和图片解析任务对象
f、通过图片加载任务加载任务
这里涉及到了Glide 的缓存策略,本处值简单介绍,具体的实现有机会在单开一篇文章。
Glide有三级缓存策略,一个是活跃内存缓存,一个是内存缓存,一个是磁盘缓存。这边文章将对活跃内存缓存策略进行剖析
活跃缓存使用的是弱引用缓存,内存缓存是强引用缓存,磁盘缓存就是磁盘缓存。
如果没有在缓存中找到想要下载的图片,此时会新建一个engineJob,然后通过engineJob的start 方法下载图片。
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
//具体使用哪一个线程池由DecodeJob自己决定。
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
DecodeJob对象被放到线程池Executor执行。
我们看看DecodeJob的run方法
@Override
public void run() {
//...
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
//已取消,则通知加载失败
notifyFailed();
return;
}
//run的包裹实现
runWrapped();
} catch (Throwable t) {
//代码有删减
} finally {
if (localFetcher != null) {
//清除数据、释放相关资源
localFetcher.cleanup();
}
GlideTrace.endSection();
}
}
private void runWrapped() {
switch (runReason) {
//主要分析这个
case INITIALIZE:
//当前状态是初始化状态
//此处返回的是RESOURCE_CACHE
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);
}
}
通常缓存文件包含内存缓存与磁盘缓存,内存缓存在上面介绍了主要是软引用缓存与强引用缓存,而Glide 的磁盘缓存有分为两种缓存一种是原图片的缓存,一种是变换之后的缓存文件,例如缓存裁剪,缩放之后的图片。因此加载磁盘文件也分为两种,先加载变换之后的文件,如果有那么直接返回该文件,如果不存在那么就加载原图片,缩放,裁剪之后返回,如果也没有缓存原图片,那么就只能加载网络图片,RESOURCE_CACHE,DATA_CACHE,SOURCE 对应的就是这三种情形。
第一次进入这个方法的时候runReason 为INITIALIZE,此时会读取缓存的图片,如果没有找到缓存的图片,runGenerators 里面的reschedule 会再次进入到runWrapped 方法(后面有详细介绍),再次进入会执行SWITCH_TO_SOURCE_SERVICE 分支,此时会从网络挤在图片。当从网络下载图片之后可能再次进入到runWrapped 方法runReason就是DECODE_DATA,此时主要是缓存图片以及执行回调显示图片。
在分析runWrapped的时候有山重水复疑无路的感觉,但是实际距离最终目的地尚有西天还有十万八千里呢!
通过变量runWrapped选择性的进入分支
stage记录目前执行阶段,currentGenerator记录目前Generator
runGenerators方法推进stage和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);
}
}
通常缓存文件包含内存缓存与磁盘缓存,内存缓存在上面介绍了主要是软引用缓存与强引用缓存,而Glide 的磁盘缓存有分为两种缓存一种是原图片的缓存,一种是变换之后的缓存文件,例如缓存裁剪,缩放之后的图片。因此加载磁盘文件也分为两种,先加载变换之后的文件,如果有那么直接返回该文件,如果不存在那么就加载原图片,缩放,裁剪之后返回,如果也没有缓存原图片,那么就只能加载网络图片,RESOURCE_CACHE,DATA_CACHE,SOURCE 对应的就是这三种情形。
我们这里主要分析SourceGenerator,也就是加载网络图片。
private void runGenerators() {
//获取当前线程
currentThread = Thread.currentThread();
//记录当前获取的时间
startFetchTime = LogTime.getLogTime();
//开始获取状态
boolean isStarted = false;
//开启循环while一直在推进stage和currentGenerator向下一阶段变化
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
//获取新的状态对应的图片加载执行器
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
//如果当前新的状态为从网络读取,则重新规划图片加载的请求
reschedule();
return;
}
}
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
//状态已完成、已取消图片加载、执行器执行失败,通知失败
notifyFailed();
}
}
while 循环与getNextGenerator 组合在一起实现的就是先读取转换后的缓存,没有的话就读取缓存的源文件,还是没有就从网络加载。我们进入网络加载也就是reschedule
@Override
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
callback 这是实际就是EngineJob
EngineJob
@Override
public void reschedule(DecodeJob<?> job) {
// Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
// 开启一个新的线程
getActiveSourceExecutor().execute(job);
}
@Override
public void run() {
DataFetcher<?> localFetcher = currentFetcher;
try {
runWrapped();
} catch (Throwable t) {
//xxx
} finally {
//xxx
}
}
此处再一次执行runWrapped,但是此时的runReason 已经不是INITIALIZE而是SWITCH_TO_SOURCE_SERVICE。
由此也可以知道,整个获取图片的流程中DecodeJob的run会执行多次。根据每次的runReason 不同执行不同的分支。
第二次调用 runWrapper的时候runReason 是SWITCH_TO_SOURCE_SERVICE
此时的currentGenerator 是SourceGenerator,此处会调用startNext 方法。
@Override
public boolean startNext() {
if (dataToCache != null) {
//dataToCache是网络图片流,网络图片流不为空
Object data = dataToCache;
dataToCache = null;
//读取图片缓存流,并缓存图片到磁盘中
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
//磁盘缓存执行器不为空,则执行磁盘缓存执行器
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//遍历网络图片加载器
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
//使用网络图片加载器加载图片
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
上面的代码主要做了两个逻辑:
1、先通过getLoadData获取ModelLoder对象的集合,然后获取一个LoadData对象
2、通过loadData对象的fetcher对象的loadData方法来获取图片数据。
这里的网络图片加载器,如果不指定默认使用的是HttpUrlFetcher,如果配置了其他的网络图片加载器,比如OkHttpStreamFetcher,则使用配置的网络加载器,我们这里看HttpUrlFetcher的loadData是怎么加载图片的:
LoadData 对象内部有一个DataFetcher,该类就是Glide实际下载图片的类,我们直接进入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处理
callback.onDataReady(result);
} catch (IOException e) {
callback.onLoadFailed(e);
}
}
}
loaddata方法是真正获取图片数据的地方:
1、调用loadDataWithRedirects方法,发起网络请求,讲URL指定的图片数据转换成InputStream输入流,该方法内部就是通过URLConnection对象来完成的。
调用loadDataWithRedirects 获取数据
//发起http请求获取图片输入流
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
//构建http请求连接
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
//添加请求头
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
//设置连接、读超时时间
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
//禁用缓存
urlConnection.setUseCaches(false);
//使用post请求方式
urlConnection.setDoInput(true);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
//连接http
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
//获取图片输入流
stream = urlConnection.getInputStream();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (isHttpOk(statusCode)) {
//http请求成功
return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
//重定向重新发起请求
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
// Closing the stream specifically is required to avoid leaking ResponseBodys in addition
// to disconnecting the url connection below. See #2352.
cleanup();
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == INVALID_STATUS_CODE) {
//网络请求失败,抛出异常
throw new HttpException(statusCode);
} else {
//网络请求失败,抛出异常
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
}
}
这里通过URLConnection 打开url。然后将对应的数据流返回
总结
至此,Glide 下载图片的过程就梳理完了,但是下载图片之后的路程还没有梳理,这些留到下一篇再做介绍。