Android 图片加载框架Glide4.0源码完全解析(二)
写在之前
上一篇博文写的是Android 图片加载框架Glide4.0源码完全解析(一),主要分析了Glide4.0源码中的with方法和load方法,原本打算是一起发布的,但是由于into方法复杂性远不是前两个方法所能比拟的,又不愿意马马虎虎的随便应付的写作,还是保持一贯的一步步深入的讲解,所以就提前发布了一篇,以减少篇幅。
正文
这篇是讲Glide源码中into方法的实现原理,可以说with和load方法只是做了前期的初始化配置工作,而真正意义上的图片加载就是在into方法中实现的,所以该方法的复杂程度是可以想象的,还是依照我之前的写作习惯,一步步的分析,不留下任何的盲点给大家带来困惑,那么下面就开始吧。
Glide 源码分析
into()
前面两个方法把所需的基础配置基本已做好,那么接下来就是真正的要去加载资源了,那么我们来看看吧:
首先进去into方法中:
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
if (requestOptions.isLocked()) {
requestOptions = requestOptions.clone();
}
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions.optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions.optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions.optionalFitCenter();
break;
case FIT_XY:
requestOptions.optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(context.buildImageViewTarget(view, transcodeClass));
}
主要是为requestOptions 做一些配置,这个配置时根据View的属性而来的。
然后调用GlideContext的buildImageViewTarget方法,并把view和transcodeClass传递进去来构造一个viewTarget的对象,跟进去看看:
直接的调用imageViewTargetFactory的buildTarget方法,然后看下buildTarget的源码:
还记得我多次提醒transcodeClass是什么吗?没错就是Drawable.class,就是在这个分支中使用到,那么在buildTarget中就是创建一个DrawableImageViewTarget对象,并它view传递进去,DrawableImageViewTarget继承于ImageViewTarget,又把view传递到了ViewTarget中,最终把view存放到了ViewTarget中的view变量中,并且还创建了一个SizeDeterminer对象:
ok,这就是我们的ImageView的最终去向,知道了view的去向,我们在返回到RequestBuilder中,创建个DrawableImageViewTarget对象后,又把它重定向到into方法中:
这个方法是非常重要的,必须要理解清楚,其实真正的理解好了也就很简单的了:
首先,从target中调用getRequest方法获取Request请求,这个getRequest方法是在ViewTarget父类中:
它首先从View中获取tag标识,但是我们从未为view设置标识,它也就不存在什么标识,所以getTag方法会返回一个null值,由此request也就是一个null空值。
在从taget中获取不到request,那么就需要为它去构造request对象,所以它调用了buildRequest:
private Request buildRequest(Target<TranscodeType> target) {
return buildRequestRecursive(target, null, transitionOptions, requestOptions.getPriority(),
requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight());
}
buildRequest方法又调用了buildRequestRecursive方法:
private Request buildRequestRecursive(Target<TranscodeType> target,
@Nullable ThumbnailRequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority, int overrideWidth, int overrideHeight) {
...
return obtainRequest(target, requestOptions, parentCoordinator, transitionOptions, priority,
overrideWidth, overrideHeight);
}
buildRequestRecursive做了一堆判断,最终会调用obtainRequest方法:
在这里它直接的调用SingleRequest.obtain方法,并返回一个Request对象,由此我们看到最终是调用到SingleRequest来真正的构造Request对象:
public static <R> SingleRequest<R> obtain(
GlideContext glideContext,
Object model,
Class<R> transcodeClass,
RequestOptions requestOptions,
int overrideWidth,
int overrideHeight,
Priority priority,
Target<R> target,
RequestListener<R> requestListener,
RequestCoordinator requestCoordinator,
Engine engine,
TransitionFactory<? super R> animationFactory) {
@SuppressWarnings("unchecked") SingleRequest<R> request =
(SingleRequest<R>) POOL.acquire();
if (request == null) {
request = new SingleRequest<>();
}
request.init(
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
requestListener,
requestCoordinator,
engine,
animationFactory);
return request;
}
由obtain方法看到,创建了一个SingleRequest对象,并调用它的init方法来进行初始化操作。
ok,到此Request创建完毕,来看看它拥有哪些成员:
让我们再回到into方法中,创建了Request对象后,把它设置给我们的target,最终则是调用setTag方法为view设置tag:
private void setTag(@Nullable Object tag) {
if (tagId == null) {
isTagUsedAtLeastOnce = true;
view.setTag(tag);
} else {
view.setTag(tagId, tag);
}
}
设置完毕之后将会调用requestManager中的track方法:
void track(Target<?> target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
track方法中分别把target和request添加到targetTracker和requestTracker追踪器中。
然后来看看runRequest方法:
它将会调用Request对象的begin方法来企图开启我们的请求,我们知道request其实就是我们的SingleRequest对象,那么到它的begin方法中看看到底做了哪些事情:
@Override
public void begin() {
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
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 (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
首先,它判断model是否为空,如果请求url为空的话,那就不用请求了,直接调用onLoadFailed加载失败。
然后,调用onSizeReady方法,真正的资源加载就是从这个方法中开始的。
最后,根据Status对象状态和是否有占位图来设置加载过程中的占位图。
那么重点就是在onSizeReady方法中了,我们来详细的看看它的源码:
@Override
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
loadStatus = engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getOnlyRetrieveFromCache(),
this);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
onSizeReady方法中首先把State状态更改为RUNNING,然后获取到ImageView的宽高属性值,这个属性值就是要加载的图片的宽高,顺便提一句,Glide框架会根据请求加载图片的ImageView的宽高来进行加载相对应的宽高图片,每次根据view的大小加载的图片是不一定一样的。最后调用engine中的load方法,我们再来看看load方法的源码:
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,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
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;
}
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;
}
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<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
useUnlimitedSourceExecutorPool);
DecodeJob<R> decodeJob = decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
哇吼,所有重要的东西都在这里了,解析下:
①:判断是否在主线程运行,说明到目前为止还是在主线程执行的,并没有真正的开启子线程。
②:通过keyFactory工厂来构建一个EngineKey对象,key关联着model,也就是url,它很根据model,view的宽高等等属性来构建一个EngineKey对象,这个对象可以用来指定缓存地址,可以用来从缓存中查找资源等。
③:根据创建的key对象分别调用loadFromCache和loadFromActiveResources方法来从内存中查找是否有缓存资源,如果有,则回调cb.onResourceReady来直接设置图片了。
④:分别使用engineJobFactory和decodeJobFactory构建EngineJob和DecodeJob对象,这两个对象是真正的加载资源的两个重要类,EngineJob对象负责开启线程去加载资源,并且加载得资源后转换到主线程并进行回调;DecodeJob是真正的执行者,它就是去网络加载资源的地方,EngineJob开启线程,真正执行的是DecodeJob,DecodeJob之后完毕之后叫道EngineJob去分发回调。这就是这两个类的关系。
⑤:EngineJob和DecodeJob的构建是基本一致的,我们看看比较复杂的DecodeJob的构建:在build方法中,首先通过pool来创建一个DecodeJob对象,然后调用DecodeJob对象的init方法进行初始化,在初始化中值得注意的是调用了decodeHelper对象的init方法。decodeHelper方法是DecodeJob的重要辅助类,后面我们会详细的接触它。
⑥:上面也提到回调,这里先cb添加到engineJob.addCallback();中,然后调用EngineJob的start方法来开启线程。
我们再来看看start方法中的源码:
start方法中将会调用GlideExecutor的execute方法:
execute中正式的开启了线程池进行加载资源。由此我们也正式的由主线程转到了子线程中。
上面我们也分析了,真正执行线程的是在DecodeJob类中,那么我们去看看的run方法是怎么执行的:
run方法中调用runWrapped方法,主要就是在它里面执行的,来看看它的源码:
我们知道,在构造DecodeJob时调用init方法是runReason被赋值为INITIALIZE值,由此它将会进入到INITIALIZE分支中,调用getNextStage方法:
在getNextStage方法中经错几次的经转会返回Stage.SOURCE值,然后在调用getNextGenerator方法来获取当前的currentGenerator对象,我们在来看看获取的这个currentGenerator到底是什么?
getNextGenerator方法中根据stage值来创建对象,由此我们可以知道currentGenerator是一个SourceGenerator对象,那么我们继续往下走,来看看runGenerators方法:
runGenerators最重要的就是执行了currentGenerator的startNext方法,这里将会真正的去加载网络资源:
我们从上面知道currentGenerator就是SourceGenerator对象,那么我们去看看SourceGenerator中startNext的实现:
到这里就太关键了,因为很多人都死在这一步上,根本找不到是怎么去加载资源的,是怎么执行到我们熟悉的HttpURLConnection的,那么到这里你千万不能分神,稍微分神就会转迷糊了,哈哈,那么接下来就开始分析吧:
由于我们在创建SourceGenerator对象时,只是传递了DecodeHelper和回调cb对象,其他的一切初始化操作都是不存在的,所以在startNext中,前面的两个判断是不成立的,主要是看while循环里面的内容:
while循环中首先调用hasNextModelLoader进行判断,我们来看下hasNextModelLoader的内容,这里必须集中精力理解清楚,不然肯定不知所云:
private boolean hasNextModelLoader() {
return loadDataListIndex < helper.getLoadData().size();
}
在hasNextModelLoader中它要去helper加载数据,调用getLoadData方法,我们跟进去看看:
在getLoadData方法中是调用GlideContext中的getRegistry方法来回去Registry对象,它是在Glide的构造方法中创建的,而且注册添加了很多解析器还记得吗?不记得的赶快去看看Glide的构造方法。这里我们插入一点非常重要的分析,理解清楚它对接下来的网络执行非常的重要:回到Glidet的构造方法来看看Registry中几个特殊的解析类:
registry.register(xx)
.append(xx)
...
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
...
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
...
;
还有印象这段很长的代码吧?
我们重点来看看上面的两个append方法,其他的都是一样或是类似的道理的。跟进去registry的append方法中:
继续进入modelLoaderRegistry.append方法中:
再跟进multiModelLoaderFactory.append方法中:
重点来了,最后进去add方法:
这里可以看到什么?你理解了吗?
创建了一个Entry对象,把我们的modelClass,dataClass和factory对象关联起来,然后存放到entries的list集合中,就这么简单,但是对这个Entry对象的理解关系到我们后面对整个网络加载的流程十分的巨大,ok,到这里,我们插入的讲解已经完了,主要想告诉你的就是这个entries集合包含了那些对象和创建Entry对象所关联的类和工厂。
那么现在回到DecodeHelper中的getLoadData方法中,它从GlideContext获取到Registry对象,Registry对象有哪些内容在上面的插入讲解中也已举特例分析了,然后调用getModelLoaders方法,并传进model对象,那么来看看它是怎么实现的:
它会从modelLoaderRegistry中获取,在来看:
它会通过model从getModelLoadersForClass方法中获取到modelLoaders的集合,来看看:
首先从cache缓存中获取,如果为空,将会从multiModelLoaderFactory工厂中获取,在继续跟进multiModelLoaderFactory的build方法看看:
从entries集合中分别的遍历出entry对象,然后调用entry.handles来进行匹配是否符合,来看handles方法:
this.modelClass是什么还记得吗?没错就是在Glide创建Registry对象是append的XX.class,例如:
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
而modelClass呢,这里就是我们传递用来加载网络图片的一个url地址,那么调用isAssignableFrom方法进行匹配,我们知道entries包含很多的解析器,所以在这一步将会排除掉不匹配的解析器,然后调用调用build方法来创建加载器:
entry.factory知道是什么吧?没错就是append方法中new的一个工厂类。
还是以下面的特例来说:
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
在这里这个factory就是HttpUriLoader.Factory,我们来看看它是怎么实现的:
这里它将会multiFactory.build方法来构造一个ModelLoader对象,看清楚它传递的参数:GlideUrl.class, InputStream.class,然后跟进去查看:
历史总是惊人的相似,再次从entries中获取Entry对象,然后调用entry.handles方法根据GlideUrl.class, InputStream.class这两个参数进行匹配过滤。
然后我们找到了以下的append内容相匹配的Entry对象
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
找到HttpGlideUrlLoader.Factory之后,然后调用build方法去构建,在build方法中同样的方式调用entry.factory.build(this),来看看HttpGlideUrlLoader.Factory()的build的源码:
直接的创建一个HttpGlideUrlLoader对象并返回。
到此我们获取了真正的图片加载对象,然后我们回到HttpUriLoader的Factory中:在multiFactory.build(GlideUrl.class, InputStream.class)获取到HttpGlideUrlLoader对象后,并传递到创建的HttpUriLoader对象中去,我们来看看:
把HttpGlideUrlLoader对象赋值给HttpUriLoader的成员变量this.urlLoader中。
ok,到此我们真正的加载器已经获取到了,当然并不是只有一个,可能有多个,因为在Registry注册了多个可以解析Uri.class的解析器。
好,我们在回到ModelLoaderRegistry类中的getModelLoaders方法中,从getModelLoadersForClass方法中我们获取到了可以解析我们请求modle的所有解析器,通过for循环遍历出所有的解析器,存放到filteredLoaders集合中并返回,一直返回到DecodeHelper类中的getLoadData方法中。
然后遍历modelLoaders集合,分别获取ModelLoader对象,并调用buildLoadData方法,我们知道modelLoaders集合中一定会包含一个ModelLoader是HttpUriLoader,那来看看它的buildLoadData方法:
它会调用urlLoader.buildLoadData方法,这个urlLoader就是HttpGlideUrlLoader对象,再来看看:
这里最重要的就是创建个一个HttpUrlFetcher对象:
然后把HttpUrlFetcher对象存放到新建的LoadData对象中:
最后把LoadData对象返回,我们往上返回到SourceGenerator的startNext方法中:
在获取到LoadData对象后,调用loadData.fetcher.loadData(helper.getPriority(), this);这个方法,从上面分析loadData.fetcher就是HttpUrlFetcher对象,那我们来看看它里面的loadData是怎么加载数据的:
它会调用loadDataWithRedirects方法来返回一个InputStream输入流,来看看loadDataWithRedirects的源码:
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.
}
}
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);
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.
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100 == 3) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == -1) {
throw new HttpException(statusCode);
} else {
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
}
}
这里的代码我们就非常的熟悉了,稍微的简单介绍下:
①:connectionFactory对象是在创建HttpUrlFetcher对象时在构造方法中初始化的,它就是DEFAULT_CONNECTION_FACTORY,也就是DefaultHttpUrlConnectionFactory工厂类,当调用它的build方法时:
通过url打开连接,返回一个HttpURLConnection对象,用于网络请求,这些代码平常我们都有接触,不多说。
②:建立连接,过去返回状态码,判断,然后通过getStreamForSuccessfulRequest方法返回一个InputStream输入流。
在获取到InputStream输入流之后,最会将会调用callback.onDataReady(result);回调方法,并把输入流传递过去。
那这个callback是什么呢?
还记得loadData.fetcher.loadData(helper.getPriority(), this);这段代码吧?
没错他就是SourceGenerator类实现的DataCallback回调类。
那么在进到SourceGenerator找到onDataReady方法吧:
在这里它又调用cb回调类的onDataFetcherReady方法,并传递了相关参数:loadData.fetcher是HttpUrlFetcher,loadData.fetcher.getDataSource()则是DataSource.REMOTE:
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
那么这个cb又是什么呢,它是FetcherReadyCallback回调类,在DecodeJob中实现,那么回到DecodeJob的onDataFetcherReady方法中:
在onDataFetcherReady方法中保存了相关的参数变量,判断是否是当前线程,然后调用decodeFromRetrievedData方法来解码数据:
首先创建一个Resource类型的变量,通过decodeFromData方法把输入流解码并返回给resource,由此可也看出,解码主要是在decodeFromData方法中:
在这里已经体现loadData.fetcher这个fetcher的用意,主要是去关闭输入流和HttpUrlConnection的。
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
在这之前它又调用decodeFromFetcher方法来进行解码返回一个Resource:
在这里获取到data.getClass,这个Class就是InputStrem.class,那么在调用decodeHelper.getLoadPath方法后,我们来看看做了哪些操作:
getLoadPath方法啥也没做,直接调用Registry中的getLoadPath,并传递了拥有的变量,其中resourceClass是Object.class,transcodeClass则是Drawable.class,这是在前面已构建初始化好的,直接拿来用。
我们在来跟进去看看
这里首先从loadPathCache缓存中获取LoadPath对象,如果没有则调用getDecodePaths方法进行获取:
getDecodePaths方法中还是蛮重要的,对真个解码过程的理解有很大的帮助,所以我们来认真的分析下:
①:首先从decoderRegistry.getResourceClasses方法中获取已经注册的registeredResourceClasses:
还记得我们创建Registry是注册了一大堆的东西吗?
对,通过handles方法对InputStrem.class和Object.class进行匹配的就是我们用红色框画出来的部分。由此我们可以得出registeredResourceClasses集合中分别对应的是Bitmap.class,BitmapDrawable.class和GifDrawable.class的三种Class对象。
②:遍历registeredResourceClasses集合,通过transcoderRegistry.getTranscodeClasses方法获取到已注册的registeredTranscodeClasses集合:
这是对registeredResourceClasses集合的再次匹配,我们知道transcodeClasses实际上是Drawable.class,在registeredResourceClasses集合中只有BitmapDrawable.class和GifDrawable.class是继承Drawable的,由此我们得出registeredTranscodeClasses包含BitmapDrawable.class和GifDrawable.class两种Class对象。
那么由此可知decoders存放了两种解码器:
对应的BitmapDrawable的BitmapDrawableDecoder解码器。
对应的GifDrawable.class的StreamGifDecoder解码器。
这个非常重要,在后面有使用到。必须理解清楚。
③:通过上面获取的registeredResourceClass和registeredTranscodeClass获取到transcoder转化器,主要是从transcoderRegistry集合中通过get方法获取,而transcoderRegistry又包含哪些转化器呢?
而又有get方法我们可以知道:
当时普通图片是transcoder将会是BitmapDrawableTranscoder,如是过动态图片gif的话transcoder将会是GifDrawableBytesTranscoder。
然后把它们存放到创建的DecodePath对象中:
最后是把封装好的DecodePath对象存储到decodePaths集合中并返回。
然后在回到Registry中的getLoadPath方法中,在获取到decodePaths集合中,把它和dataClass, resourceClass, transcodeClass又封装到LoadPath对象中,并缓存到loadPathCache对象中。
再次返回DecodeJob类中的decodeFromFetcher方法中,在获取到LoadPath后,调用runLoadPath方法,来看下它的源码:
它调用glideContext.getRegistry()获取Registry对象,然后调用Registry的getRewinder获取装置器,跟进去看看:
在getRewinder方法中直接的调用dataRewinderRegistry对象的build方法,那么这个dataRewinderRegistry又是什么呢?
它是在Registry构造方法中创建的对象:
this.dataRewinderRegistry = new DataRewinderRegistry();
而且在Glide中进行了注册:
获取到factory之后调用dataRewinderRegistry.register方法注册到Map集合rewinders中去:
再来看看factory.getDataClass()都获取到哪些key:
①:在ByteBufferRewinder.Factory中调用getDataClass()获取的key是ByteBuffer.class
②:在InputStreamRewinder.Factory中调用getDataClass()获取的key是InputStream.class
上面两个key值在接下来会使用到。
那么我们在返回到getRewinder方法中,调用dataRewinderRegistry对象的build方法到底做了什么:
在build的方法中总共做了四件事,分别用不同的线框标注出来了:
①:从rewinders集合中获取到和data.getClass()相匹配的Factory对象,从上面的分析中,我们知道rewinders注册的只有两个key,分别是ByteBuffer.class和InputStream.class,而我们又知道data.getClass()是一个InputStream.class,由此可以匹配成功。这个result就是InputStreamRewinder.Factory对象。
②:假如result没有匹配成功的话,也就是没有通过key匹配成功,那么就进行遍历rewinders集合,通过values值进行匹配,把匹配成功的Factory对象再赋值给result。
③:假如通过键key和值values都没有匹配成功,那么也不要紧,直接使用默认的DEFAULT_FACTORY
④:最后调用result的build方法。
这里由于我们通过键key直接已匹配成功,所以我们知道result就是InputStreamRewinder.Factory对象,那么来看看调用它的build方法做了什么事情:
这里直接的返回了一个InputStreamRewinder对象,在InputStreamRewinder的构造方法中又创建了RecyclableBufferedInputStream对象,并它InputStream流传递进去:
RecyclableBufferedInputStream继承FilterInputStream流,那么我们的InputStream流最终保存的位置就是在FilterInputStream类中。
ok,到这里我们知道了InputStream数据流的去向,而在InputStreamRewinder中又有对InputStream流的引用,那么在回到InputStreamRewinder之后我们来看看接下来又做了什么呢?
再次定位到DecodeJob类中的runLoadPath方法,在获取到InputStreamRewinder后,它会调用path.load方法并返回Resource资源类,这个path是在之前获取到的LoadPath对象,这里注意下,传递了一个new DecodeCallback 对象的回调类,后面会用到。最后调用InputStreamRewinder.cleanup()进行资源释放。那么我们来看看LoadPath的load方法是怎么实现的:
它直接调用loadWithExceptionList方法进行移交:
在loadWithExceptionList方法中则会调用decode方法进行解码:
decode方法中调用decodeResource方法,然后在调用decodeResourceWithList方法真正的开始解码:
①:这里首先遍历decoders集合,分别的获取到ResourceDecoder解码器,还记得我们的decoders都包含哪些解码器吗?没错主要包含两种:BitmapDrawable.class和GifDrawable.class。不记得的往上面翻下,上面已详细的讲解过了。
②:然后通过rewinder.rewindAndGet()获取我们的InputStream数据流:
③:通过decoder.handles(data, options)方法来过滤掉不相匹配的解码器,再来看看前面的这张图:
Bitmap.class已被我们过滤掉,剩下的就只有BitmapDrawable.class和GifDrawable.class,当我们调用handles方法进行匹配时,StreamGifDecoder解码器是怎么处理的:
它主要是针对options属性为gif的图片来解码的,其实这不用我说大家也是知道的,而我们加载的只是普通的静态图片,因此它是不符合我们的匹配规则的,在来看BitmapDrawableDecoder中,它传入的是StreamBitmapDecoder的解码器:
而downsampler.handles(source);中只要source是InputStream就返回true,因此匹配成功。
那么由上分析,我们真正的decoder就是BitmapDrawableDecoder。
回到decodeResourceWithList方法中,获取到真正的decoder解码器,将会调用decode方法正式解码:
它又会交给StreamBitmapDecoder的decode方法,然后在转移到Downsampler类中的decode方法中:
这里就不再一个一个的看方法了,主要是在Downsampler中decodeFromWrappedStreams方法中把InputStream数据流根据width, height, options给转化成Bitmap图片,然后把Bitmap存放到BitmapResource对象中去且直接的返回本身。
ok,获取到BitmapResource对象后,再次返回最初开始解码的DecodePath类中的decode方法中:
从上面分析我们知道,我们解析的是普通的图片,所以这个transcoder就是BitmapDrawableTranscoder转换器类。(前面有详细的分析,忘记的可以向上翻看)
接着我们去BitmapDrawableTranscoder中的transcode方法看看:
在transcode方法中通过toTranscode.get()获取bitmap图片,这个bitmap虽然经过多层的包装,它其实就是一个BitmapResource对象,这在上面我们有清楚的分析,只不过在返回的回调方法中有多次的封装而已。
获取到Bitmap图片后,调用LazyBitmapDrawableResource的obtain方法再次进行一次的封装:
把Bitmap封装到LazyBitmapDrawableResource对象中进行返回。
ok,到这里其实已完成了完完全全的解码和封装到。
一路把LazyBitmapDrawableResource对象返回到DecodeJob类中的decodeFromRetrievedData中,并赋值给resource变量:
当resource不为空时,将会调用notifyEncodeAndRelease方法并传递参数:
方法里面就不多解析了,它会调用notifyComplete方法:
这里调用了callback.onResourceReady(resource, dataSource);方法,那这个callback是什么呢?它其实是一个ResourceCallback,在SingleRequest中发起的,并且SingleRequest还实现了ResourceCallback接口内的方法:
public void onResourceReady(Resource<?> resource, DataSource dataSource) {
stateVerifier.throwIfRecycled();
loadStatus = null;
if (resource == null) {
GlideException exception = new GlideException("Expected to receive a Resource<R> with an "
+ "object of " + transcodeClass + " inside, but instead got null.");
onLoadFailed(exception);
return;
}
Object received = resource.get();
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
releaseResource(resource);
GlideException exception = new GlideException("Expected to receive an object of "
+ transcodeClass + " but instead" + " got "
+ (received != null ? received.getClass() : "") + "{" + received + "} inside" + " "
+ "Resource{" + resource + "}."
+ (received != null ? "" : " " + "To indicate failure return a null Resource "
+ "object, rather than a Resource object containing null data."));
onLoadFailed(exception);
return;
}
if (!canSetResource()) {
releaseResource(resource);
// We can't put the status to complete before asking canSetResource().
status = Status.COMPLETE;
return;
}
onResourceReady((Resource<R>) resource, (R) received, dataSource);
}
重点讲解下:在onResourceReady中使用resource.get(),我们知道这个resource就是LazyBitmapDrawableResource对象,来看看这个get获取到了什么:
在get中获取到了BitmapDrawable对象直接复制给了received变量,然后调用重载方法onResourceReady方法:
在onResourceReady方法中调用了target.onResourceReady(result, animation);还记得target是什么吗?
它在load方法中已讲解过,就是DrawableImageViewTarget对象,调用它的onResourceReady会转移到父类ImageViewTarget中:
然后在调用setResourceInternal方法:
setResource是抽象方法,由它的子类实现,我们在回到DrawableImageViewTarget中:
到这里直接调用view进行设置图片了,这个view就是我们Imageview了,所以到这里就设置好了加载的图片了。
ok,到这里就完完全全的解析完了Glide的执行原理了。
说实在的Glide的源码非常的复杂,往往深入进去就无法出来了,相信这篇博客很能好的给大家一个参考,能让大家对整个Glide有个全面的理解。
好了,终于写完了,真的很不容易,很辛苦,希望大家也能从中学到知识。
各位如果还有哪里不明白的,或是我这里讲的还不够透彻,亦或是讲错了的地方请留言指正,让我们共同进步,谢谢
同时,请大家扫一扫关注我的微信公众号,虽然写的不是很勤,但是每一篇都有质量保证,让您学习到真正的知识。
Android 图片加载框架Glide4.0源码完全解析(二)的更多相关文章
- Android 图片加载框架Glide4.0源码完全解析(一)
写在之前 上一篇博文写的是Picasso基本使用和源码完全解析,Picasso的源码阅读起来还是很顺畅的,然后就想到Glide框架,网上大家也都推荐使用这个框架用来加载图片,正好我目前的写作目标也是分 ...
- Android 图片加载框架 Glide4.x
概述 Glide是一个图片加载框架,使得我们可以轻松的加载和展示图片 Glide4.x新增apply()来进行设置,apply可以调用多次,但是如果两次apply存在冲突的设置,会以最后一次为准 新增 ...
- Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),G ...
- Android图片加载框架最全解析(八),带你全面了解Glide 4的用法
本篇将是我们这个Glide系列的最后一篇文章. 其实在写这个系列第一篇文章的时候,Glide就推出4.0.0的RC版了.那个时候因为我一直研究的都是Glide 3.7.0版本,再加上RC版本还不太稳定 ...
- Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能
我们的Glide系列文章终于要进入收尾篇了.从我开始写这个系列的第一篇文章时,我就知道这会是一个很长的系列,只是没有想到竟然会写这么久. 在前面的六篇文章中,我们对Glide的方方面面都进行了学习,包 ...
- Android图片加载框架最全解析(六),探究Glide的自定义模块功能
不知不觉中,我们的Glide系列教程已经到了第六篇了,距离第一篇Glide的基本用法发布已经过去了半年的时间.在这半年中,我们通过用法讲解和源码分析配合学习的方式,将Glide的方方面面都研究了个遍, ...
- Android图片加载框架最全解析(五),Glide强大的图片变换功能
大家好,又到了学习Glide的时间了.前段时间由于项目开发紧张,再加上后来又生病了,所以停更了一个月,不过现在终于又可以恢复正常更新了.今天是这个系列的第五篇文章,在前面四篇文章的当中,我们已经学习了 ...
- Android图片加载框架最全解析(四),玩转Glide的回调与监听
大家好,今天我们继续学习Glide. 在上一篇文章当中,我带着大家一起深入探究了Glide的缓存机制,我们不光掌握了Glide缓存的使用方法,还通过源码分析对缓存的工作原理进行了了解.虽说上篇文章和本 ...
- Android图片加载框架最全解析(三),深入探究Glide的缓存机制
在本系列的上一篇文章中,我带着大家一起阅读了一遍Glide的源码,初步了解了这个强大的图片加载框架的基本执行流程. 不过,上一篇文章只能说是比较粗略地阅读了Glide整个执行流程方面的源码,搞明白了G ...
随机推荐
- 【2017-04--28】Winform中ListView控件
ListView 1.先设置列,设置视图属性选择Details. 添加列,修改列名. 2.编辑项(添加行数据) 添加一个ListViewItem对象,该对象的Text对应着是第一列的数据, 在该对象的 ...
- 通过 U 盘启动重装 macOS 系统
重装系统是工作和生活中经常需要做的事情,作为一名开发人员,学会该技能你才是一名合格的程序猿!以后再也不会遇到"程旭元你会装系统吗?"的尴尬了!本文主要介绍怎样通过U盘启动重新安装 ...
- 七牛整合 ueditor (拦住那头牛,七牛又如何)
最近遇到个项目,要求所有图片都必须整合到七牛上,看了把你谈文档踩在前辈们的基础上终于把他完成了,恰巧本屌丝最近刚好有时间,本着天下屌丝是一家的原则,和小朋友们一同学习 闲话少说入正题. 第一 :下载编 ...
- Microsoft Windows 2003 SP2 - 'ERRATICGOPHER' SMB Remote Code Execution
EDB-ID: 41929 Author: vportal Published: 2017-04-25 CVE: N/A Type: Remote Platform: Windows Aliases: ...
- java中多种写文件方式的效率对比实验
一.实验背景 最近在考虑一个问题:“如果快速地向文件中写入数据”,java提供了多种文件写入的方式,效率上各有异同,基本上可以分为如下三大类:字节流输出.字符流输出.内存文件映射输出.前两种又可以分为 ...
- 【JAVAWEB学习笔记】06_jQuery基础
接05的学习笔记. 四.使用JQ完成省市二级联动 1.需求分析 使用jquery完成省市二级联动 2.技术分析 2.1数组的遍历操作 方式一: $(function(){ // 全选/ 全不选 $(& ...
- SpringBoot系列(一)RestTemplate
作为springBoot的开篇系列,RestTemplate只能表示我只是个意外 what RestTemplate是spring提供的用于访问rest服务的客户端(其实类似Apache的HttpCl ...
- mysql数据库小常识
什么是数据库? 计算机处理和存储的一切信息都是数据. 计算机系统中一种用于存储数据的程序. 一种:计算机系统中有很多种能够存取数据的程序. 他们各有特长和长处,有自己的适用范围. 存取:能够保存数据避 ...
- php微信支付问题之 cURL error 60: SSL certificate: unable to get local issuer certificate
cacert.pem(点击下载) 解决办法:比如我本地安装的是wamp,将cacert.pem文件放在这个文件夹下面D:\wamp\bin\php\php5.5.12\ext 如果安装的phpStud ...
- 一不小心,陷入TCP的性能问题
一.现象 在一次访问请求nginx中,通常只需要几毫秒的RT,但当请求数据达到某一个数值时,rt明显提高,甚至超过了300毫秒. 二.问题的原因 大家都知道,TCP为了提高带宽利用率和吞吐量,做了各种 ...