2024年最全Glide图片框架使用详细介绍(五)之Glide-源码详解,Android中高级岗面试为何越来越难

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

这里有三点需要注意的:

1.Util.assertMainThread();这里会检查是否主线程,不是的话会抛出异常,所以into方法必须在主线程中调用.

2.当你没有调用transform方法,并且你的ImageView设置了ScaleType,那么他会根据你的设置,对图片做处理(具体处理可以查看DrawableRequestBuilder的applyCenterCrop或者applyFitCenter方法,我们自己自定义BitmapTransformation也可以参考这里的处理).

3.view在这里被封装成一个Target.

//GenericRequestBuilder.java

public <Y extends Target> Y into(Y target) {

Util.assertMainThread();

if (target == null) {

throw new IllegalArgumentException(“You must pass in a non null Target”);

}

if (!isModelSet) {

throw new IllegalArgumentException(“You must first set a model (try #load())”);

}

Request previous = target.getRequest();

if (previous != null) {

previous.clear();

requestTracker.removeRequest(previous);

previous.recycle();

}

Request request = buildRequest(target);

target.setRequest(request);

lifecycle.addListener(target);

requestTracker.runRequest(request);

return target;

}

这里可以看到控件封装成的Target能够获取自身绑定的请求,当发现之前的请求还在的时候,会把旧的请求清除掉,绑定新的请求,这也就是为什么控件复用时不会出现图片错位的问题(这点跟我在Picasso源码中看到的处理方式很相像).

接着在into里面会调用buildRequest方法来创建请求

//GenericRequestBuilder.java

private Request buildRequest(Target target) {

if (priority == null) {

priority = Priority.NORMAL;

}

return buildRequestRecursive(target, null);

}

//GenericRequestBuilder.java

private Request buildRequestRecursive(Target target, ThumbnailRequestCoordinator parentCoordinator) {

if (thumbnailRequestBuilder != null) {

Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);

Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);

coordinator.setRequests(fullRequest, thumbRequest);

return coordinator;

} else if (thumbSizeMultiplier != null) {

ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);

Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);

Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);

coordinator.setRequests(fullRequest, thumbnailRequest);

return coordinator;

} else {

// Base case: no thumbnail.

return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);

}

}

1.这里就是请求的生成,buildRequestRecursive里面if有三个分支,这里是根据你设置thumbnail的情况来判断的,第一个是设置缩略图为新的请求的情况,第二个是设置缩略图为float的情况,第三个就是没有设置缩略图的情况.

前两个设置了缩略图的是有两个请求的,fullRequest和thumbnailRequest,没有设置缩略图则肯定只有一个请求了.

2.请求都是通过obtainRequest方法生成的(这个简单了解一下就行)

//GenericRequestBuilder.java

private Request obtainRequest(Target target, float sizeMultiplier, Priority priority,

RequestCoordinator requestCoordinator) {

return GenericRequest.obtain(…);

}

REQUEST_POOL是一个队列,当队列中有,那么就从队列中取,没有的话就新建一个GenericRequest

//GenericRequest.java

public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(…) {

GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();

if (request == null) {

request = new GenericRequest<A, T, Z, R>();

}

request.init(…);

return request;

}

回到into方法:当创建了请求后runRequest会调用Request的begin方法,即调用GenericRequest的begin方法

//GenericRequestBuilder.java

public <Y extends Target> Y into(Y target) {

Request request = buildRequest(target);

requestTracker.runRequest(request);

}

//GenericRequest.java

public void begin() {

if (Util.isValidDimensions(overrideWidth, overrideHeight)) {

onSizeReady(overrideWidth, overrideHeight);

} else {

target.getSize(this);

}

}

最终会调用Engine的load方法

//GenericRequest.java

public void onSizeReady(int width, int height) {

loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,

priority, isMemoryCacheable, diskCacheStrategy, this);

}

我们先看load方法的前面一段:

1.首先会尝试从cache里面取,这里cache就是Glide的构造函数里面的MemoryCache(是一个LruResourceCache),如果取到了,就从cache里面删掉,然后加入activeResources中

2.如果cache里面没取到,就会从activeResources中取,activeResources是一个以弱引用为值的map,他是用于存储使用中的资源.之所以在内存缓存的基础上又多了这层缓存,是为了当内存不足而清除cache中的资源中,不会影响使用中的资源.

//Engine.java

public <T, Z, R> LoadStatus load(…) {

EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);

if (cached != null) {

cb.onResourceReady(cached);

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);

if (Log.isLoggable(TAG, Log.VERBOSE)) {

logWithTimeAndKey(“Loaded resource from active resources”, startTime, key);

}

return null;

}

}

load方法接着会通过EngineJobFactory创建一个EngineJob,里面主要管理里两个线程池,diskCacheService和sourceService,他们就是Glide构造函数中Engine里面创建的那两个线程池.

//Engine.java

public <T, Z, R> LoadStatus load(…) {

EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);

}

//Engine.java

static class EngineJobFactory {

private final ExecutorService diskCacheService;

private final ExecutorService sourceService;

private final EngineJobListener listener;

public EngineJobFactory(ExecutorService diskCacheService, ExecutorService sourceService,

EngineJobListener listener) {

this.diskCacheService = diskCacheService;

this.sourceService = sourceService;

this.listener = listener;

}

public EngineJob build(Key key, boolean isMemoryCacheable) {

return new EngineJob(key, diskCacheService, sourceService, isMemoryCacheable, listener);

}

}

接着说load方法,前面创建了EngineJob,接着调用EngineJob的start方法,并将EngineRunnable放到diskCacheService(处理磁盘缓存的线程池里面运行),接着线程池就会调用EngineRunnable的run方法.

//Engine.java

public <T, Z, R> LoadStatus load(…) {

EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);

jobs.put(key, engineJob);

engineJob.addCallback(cb);

engineJob.start(runnable);

}

//EngineJob.java

public void start(EngineRunnable engineRunnable) {

this.engineRunnable = engineRunnable;

future = diskCacheService.submit(engineRunnable);

}

//EngineRunnable.java

public void run() {

try {

resource = decode();

} catch (Exception e) {

if (Log.isLoggable(TAG, Log.VERBOSE)) {

Log.v(TAG, “Exception decoding”, e);

}

exception = e;

}

if (resource == null) {

onLoadFailed(exception);

} else {

onLoadComplete(resource);

}

}

run里面调用的是decode()方法,里面会尝试先从磁盘缓存中读取,如果不行就从源资源中读取

//EngineRunnable.java

private Resource<?> decode() throws Exception {

if (isDecodingFromCache()) {

//第一次会走这

return decodeFromCache();//从磁盘缓存中读取

} else {

return decodeFromSource();//从源资源中读取

}

}

我们先来看从磁盘中读取的策略

//EngineRunnable.java

private Resource<?> decodeFromCache() throws Exception {

Resource<?> result = null;

try {

result = decodeJob.decodeResultFromCache();

} catch (Exception e) {

if (Log.isLoggable(TAG, Log.DEBUG)) {

Log.d(TAG, "Exception decoding result from cache: " + e);

}

}

if (result == null) {

result = decodeJob.decodeSourceFromCache();

}

return result;

}

我们可以看到这里先尝试读取处理后的图片(Result),然后再尝试读取原图,但是这里面具体逻辑会根据你设置的磁盘缓存策略来决定是否真的会读取处理图和原图

那么我们再回到EngineRunnable的run()方法中

public void run() {

try {

resource = decode();

} catch (Exception e) {

if (Log.isLoggable(TAG, Log.VERBOSE)) {

Log.v(TAG, “Exception decoding”, e);

}

exception = e;

}

if (resource == null) {

onLoadFailed(exception);

} else {

onLoadComplete(resource);

}

}

第一次走decode的时候会先尝试从磁盘中获取,如果获取的为null,那么在onLoadFailed方法里面又会把这个run再次放入线程池中,但是这次是放入sourceService(处理源资源的线程池)

//EngineRunnable.java

private void onLoadFailed(Exception e) {

if (isDecodingFromCache()) {

stage = Stage.SOURCE;

manager.submitForSource(this);

} else {

manager.onException(e);

}

}

//EngineJob.java

@Override

public void submitForSource(EngineRunnable runnable) {

future = sourceService.submit(runnable);

}

接着sourceService里面又会调用调用EngineRunnable的run方法,这次decode里面会走从源资源读取的那条分支

//EngineRunnable.java

private Resource<?> decode() throws Exception {

if (isDecodingFromCache()) {

//第一次会走这

return decodeFromCache();//从磁盘缓存中读取

} else {

//第二次会走这

return decodeFromSource();//从源资源读取

}

}

//DecodeJob.java

public Resource decodeFromSource() throws Exception {

Resource decoded = decodeSource();//获取数据,并解码

return transformEncodeAndTranscode(decoded);//处理图片

}

里面主要做了两件事,一个是获取图片,一个是处理图片

1.我们先来看获取图片的decodeSource方法

//DecodeJob.java

private Resource decodeSource() throws Exception {

//拉取数据

final A data = fetcher.loadData(priority);

//解码,并保存源资源到磁盘

decoded = decodeFromSourceData(data);

return decoded;

}

//DecodeJob.java

private Resource decodeFromSourceData(A data) throws IOException {

final Resource decoded;

if (diskCacheStrategy.cacheSource()) {

//解码并保存源资源(图片)到磁盘缓存中

decoded = cacheAndDecodeSourceData(data);

} else {

long startTime = LogTime.getLogTime();

decoded = loadProvider.getSourceDecoder().decode(data, width, height);

if (Log.isLoggable(TAG, Log.VERBOSE)) {

logWithTimeAndKey(“Decoded from source”, startTime);

}

}

return decoded;

}

这里调用了DataFetcher的loadData方法来获取数据,DataFetcher有很多实现类,一般来说我们都是从网络中读取数据,我们这边就以HttpUrlFetcher为例

//HttpUrlFetcher.java

@Override

public InputStream loadData(Priority priority) throws Exception {

return loadDataWithRedirects(glideUrl.toURL(), 0 /redirects/, null /lastUrl/, glideUrl.getHeaders());

}

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)

throws IOException {

if (redirects >= MAXIMUM_REDIRECTS) {

throw new IOException("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 IOException(“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(2500);

urlConnection.setReadTimeout(2500);

urlConnection.setUseCaches(false);

urlConnection.setDoInput(true);

// 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 IOException(“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 IOException(“Unable to retrieve response code from HttpUrlConnection.”);

}

throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());

}

}

2.看完了获取图片的方法,我们再来看看处理图片的方法transformEncodeAndTranscode

//DecodeJob.java

最后

给大家送上我成功跳槽复习中所整理的资料,由于文章篇幅有限,所以只是把题目列出来了

image

image

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

");

}

URL redirectUrl = new URL(url, redirectUrlString);

return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);

} else {

if (statusCode == -1) {

throw new IOException(“Unable to retrieve response code from HttpUrlConnection.”);

}

throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());

}

}

2.看完了获取图片的方法,我们再来看看处理图片的方法transformEncodeAndTranscode

//DecodeJob.java

最后

给大家送上我成功跳槽复习中所整理的资料,由于文章篇幅有限,所以只是把题目列出来了

[外链图片转存中…(img-qdCaZobo-1715887935593)]

[外链图片转存中…(img-zU8KUPqj-1715887935594)]

[外链图片转存中…(img-cgQfP9cI-1715887935594)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值