Glide图片加载流程浅析
Glide是Android开发中常用的图片框架,其最基本用法例如Glide.with(context).load(url).into(imageView)
,我们沿着此链式调用的顺序一窥Glide图片加载流程的样貌。
一图胜千言
1.Glide.with(context)
// Glide.java
public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context);
}
public static RequestManager with(@NonNull Activity activity) { return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull FragmentActivity activity) { return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull Fragment fragment) { return getRetriever(fragment.getContext()).get(fragment);
}
public static RequestManager with(@NonNull View view) { return getRetriever(view.getContext()).get(view);
}
该函数的作用是创建了RequestManager
对象并返回。其参数有五种类型,这里选取Context
类型情形进行分析。
可以看到RequestManager
对象是先通过getRetriver()方法创建得到一个RequestManagerRetriever
对象,再调用该对象的get()方法返回。
1.1RequestManager对象的创建过程
// Glide.java
private static RequestManagerRetriever getRetriever(@Nullable Context context) { // Glide.get(context)获取Glide实例 return Glide.get(context).getRequestManagerRetriever();
}
public static Glide get(@NonNull Context context) { if (glide == null) { // 加载AppGlideModule GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(context.getApplicationContext()); synchronized (Glide.class) { if (glide == null) { // 加载Mainfest配置、注册模块回调 // 这一步执行了 Glide.build()方法构造Glide实例。build方法下面会讲到 checkAndInitializeGlide(context, annotationGeneratedModule); } } } return glide; }
在getRetriever(context)
方法中,先通过Glide.get(context)
获取Glide实例,Glide采用双重校验锁的懒汉式的单例模式创建,同时读取AppGlideModule和AndroidManifest.xml的配置。
获取到Glide实例后,紧接着调用getRequestManagerRetriever
方法返回了上一步已经初始化好的RequestManagerRetriever
对象。
1.1.1RequestManagerRetriever
是如何初始化的?
RequestManagerRetriever
对象的初始化主要是在Glide对象的创建过程中完成的,也就是Glide.get(context)
方法中的checkAndInitializeGlide
()函数中,该函数内部调用了Glide.build
方法,源码如下:
// GlideBuilder.java
Glide build(@NonNull Context context) { // 分配线程池、配置缓存策略 sourceExecutor = GlideExecutor.newSourceExecutor(); diskCacheExecutor = GlideExecutor.newDiskCacheExecutor(); animationExecutor = GlideExecutor.newAnimationExecutor(); memorySizeCalculator = new MemorySizeCalculator.Builder(context).build(); // 监听网络变化 connectivityMonitorFactory = new DefaultConnectivityMonitorFactory(); int size = memorySizeCalculator.getBitmapPoolSize(); if (size > 0) { bitmapPool = new LruBitmapPool(size); } else { bitmapPool = new BitmapPoolAdapter(); } arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes()); memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); diskCacheFactory = new InternalCacheDiskCacheFactory(context); // engine是负责执行加载任务的 if (engine == null) { engine = new Engine( memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor, GlideExecutor.newUnlimitedSourceExecutor(), animationExecutor, isActiveResourceRetentionAllowed); } if (defaultRequestListeners == null) { defaultRequestListeners = Collections.emptyList(); } else { defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners); } // focus 1===RequestManagerRetriever requestManagerRetriever = new RequestManagerRetriever(requestManagerFactory); return new Glide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptionsFactory, defaultTransitionOptions, defaultRequestListeners, isLoggingRequestOriginsEnabled, isImageDecoderEnabledForBitmaps); }
注意focus 1处的代码创建了RequestManagerRetriever对象。
public interface RequestManagerFactory {@NonNullRequestManager build(@NonNull Glide glide,@NonNull Lifecycle lifecycle,@NonNull RequestManagerTreeNode requestManagerTreeNode,@NonNull Context context);}
从以上代码我们可以知道:
1.执行Glide.get()方法时就已经分配好了资源加载、缓存线程池、配置好了缓存策略,这里的engine专门负责加载、解码资源,ConnectivityMonitor注册了网络状态监听器,当网络断开时暂停请求网络资源,重连后继续请求资源;
2.RequestManagerRetriever是通过RequestManagerFactory工厂类构造的,进入到RequestManagerFactory.java类中,可以看到其build()方法获取到了相应的RequestManager对象;
1.1.2 factory.build()方法在什么时候调用的?
在之前getRetriever(context).get(context);
的调用中可以发现
在RequestManagerRetriever类中有一个get()方法,正是在这个方法中调用了factory.build()方法返回了RequestManager对象。
如果Glide.with(context)传入的context参数是
Application类
或者是在后台线程调用的该方法,则调用getApplicationManager(Context context)
private RequestManager getApplicationManager(@NonNull Context context) {// Either an application context or we're on a background thread.if (applicationManager == null) {synchronized (this) {if (applicationManager == null) {// Normally pause/resume is taken care of by the fragment we add to the fragment or// activity. However, in this case since the manager attached to the application will not// receive lifecycle events, we must force the manager to start resumed using// ApplicationLifecycle.// TODO(b/27524013): Factor out this Glide.get() call.Glide glide = Glide.get(context.getApplicationContext());applicationManager =factory.build(glide,new ApplicationLifecycle(),new EmptyRequestManagerTreeNode(),context.getApplicationContext());}}}return applicationManager;}
否则调用
supportFragmentGet
或者fragmentGet
private RequestManager fragmentGet(@NonNull Context context,@NonNull android.app.FragmentManager fm,@Nullable android.app.Fragment parentHint,boolean isParentVisible) {RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);RequestManager requestManager = current.getRequestManager();if (requestManager == null) {// TODO(b/27524013): Factor out this Glide.get() call.Glide glide = Glide.get(context);requestManager =factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);current.setRequestManager(requestManager);}return requestManager;}
从这里也可以发现,当App进入后台后会导致页面不可见,此时RequestManager绑定到了ApplicationContext,与App的生命周期一致;在其他情况下,RequestManager是和当前页面的生命周期绑定,因此在RequestManager.java类中也实现了生命周期相关的回调函数;
执行完Glide.with(context)后我们拿到了一个对应的RequestManager对象,接下来就执行下一个任务load(url);
load(url)
拿到了RequestManager,紧接着调用load方法开始执行下一步操作,同样先看看load方法的实现
// RequestManager.java
public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) { return asDrawable().load(bitmap); } public RequestBuilder<Drawable> load(@Nullable Drawable drawable) { return asDrawable().load(drawable); } public RequestBuilder<Drawable> load(@Nullable String string) { return asDrawable().load(string); } public RequestBuilder<Drawable> load(@Nullable Uri uri) { return asDrawable().load(uri); } public RequestBuilder<Drawable> load(@Nullable File file) { return asDrawable().load(file); } public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) { return asDrawable().load(resourceId); } public RequestBuilder<Drawable> load(@Nullable URL url) { return asDrawable().load(url); } public RequestBuilder<Drawable> load(@Nullable byte[] model) { return asDrawable().load(model); } public RequestBuilder<Drawable> load(@Nullable Object model) { return asDrawable().load(model); }
load()同样有多个重载函数,传入的参数可以是图片对象Bitmap、Drawable、本地资源Uri、在线资源路径Url、文件对象File、assets资源的id,这里以参数为Url为例;
asDrawable().load(url)
返回了一个RequestBuilder对象,首先看看asDrawable方法干了什么;
// RequestManager.java public RequestBuilder<Drawable> asDrawable() { return as(Drawable.class); } public <ResourceType> RequestBuilder<ResourceType> as(@NonNull Class<ResourceType> resourceClass) { return new RequestBuilder<>(glide, this, resourceClass, context); }
asDrawable方法创建了RequestBuilder对象,然后调用RequestBuilder.java中的load方法;
// RequestBuilder.java // 传入的String类型的url将会被作为缓存的key public RequestBuilder<TranscodeType> load(@Nullable String string) { return loadGeneric(string); } // 这里返回了自身 private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { this.model = model; isModelSet = true; return this; }
load函数主要工作就是根据传入的资源类型,构造了一个相应的RequestBuilder对象。至此一切准备工作准备就绪,接下来就是最为重要的一步了-加载、展示文件,让我们来着看into(view)方法如何完成这些任务;
3.load(imageView)
拿到的是对应类型RequestBuilder实例,那么就看看该类里into方法的具体实现。同样into方法有into(@NonNull Y target)
和into(@NonNull ImageView )
两个重载函数(这两个函数最终都会走到同一个函数中),由于调用into方法时我们传入的参数是ImageView类型的;
// RequestBuilder.java public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { Util.assertMainThread(); BaseRequestOptions<?> requestOptions = this; // View's scale type. // 处理图片缩放,根据缩放类型来初始化对应的requestOptions对象 ...... return into( glideContext.buildImageViewTarget(view, transcodeClass), /*targetListener=*/ null, requestOptions, Executors.mainThreadExecutor() // 运行在主线程的handler ,利用Handler实现子线程到主线程的切换,因为最终将图片设置到ImageView是需要在主线程操作的); }
上面代码段首先处理图片缩放类型(裁剪、对齐方式等),并将生成的相关参数放入了requestOptions对象中,然后再将其作为参数传给了RequestBuilder.java类私有方法into。该方法定义的四个参数分别为:viewTarget、target回调监听器、请求参数、主线程的回调函数;
显然外部传入ImageView对象最终被转换成了ViewTarget对象,转换函数便是glideContext.buildImageViewTarget(view, transcodeClass);
无论传入参数是何种类型,最终都会转换成两种类型的ViewTarget :BitmapImageViewTarget
或者DrawableImageViewTarget
;具体取决于asBitmap()、asGif()和asDrawable()函数是否被调用,默认是调用的asDrawable(),所以默认返回的是DrawableImageViewTarget类型。
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {public DrawableImageViewTarget(ImageView view) {super(view);}/** @deprecated Use {@link #waitForLayout()} instead. */// Public API.@SuppressWarnings({"unused", "deprecation"})@Deprecatedpublic DrawableImageViewTarget(ImageView view, boolean waitForLayout) {super(view, waitForLayout);}@Overrideprotected void setResource(@Nullable Drawable resource) {view.setImageDrawable(resource); //显示图片}
}
至此ViewTarget创建完毕,我们再回到RequestBuilder.java私有into方法
// RequestBuilder.java` private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> options, Executor callbackExecutor) { // 注释1:创建request Request request = buildRequest(target, targetListener, options, callbackExecutor); // 获取前一个reqeust请求对象 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; }
顺着代码次序,来看看这个方法每一步都干了什么:
1.首先执行buildRequest方法创建一个新的Request请求req1;
2.获取当前ViewTarget上正在进行中的Request请求req2;
3.判断新建的请求req1与已有的请求req2是否相同,如果相同则判断是否跳过req2请求的缓存,两个条件都满足则开始执行begin()方法开始请求资源并停止往下执行,条件都不满足则继续执行第四步;
4.给ViewTarget设置最新的请求req1,然后执行track方法追踪req1。
5.执行into(view)方法首先获取到了Request请求,然后开始执行Request。如果是复用的Request则直接执行begin(),否则执行track(target, request),但最终仍然会执行begin();
当我们在常见的列表界面中(如 recycleview 实现的列表),使用上面的代码,在我们快速滑动中,glide 是如何实现正确加载图片,而没有导致图片内容的错位或者是不正确呢?
答案就在上面的代码中。执行上面的代码后,glide 要把最新的图片加载到正确的对象上,而取消对象之前关联的图片加载请求。
首先,在该方法中构建了一个 Request 并持有了 target 。这样便可以将结果通知给 target。Target 是 Glide 对我们要加载目标的一个封装和抽象。
判断Taget 中的之前的 Request 和最新构建的 Request 是否相同,如果相同回收最新的 Request ,让旧的 Request 继续运行。如果不同,就取消之前的 Request 和 target 的关联。
requestManager.clear(target);
最终会触发下面的代码
if (!isOwnedByUs && !glide.removeFromManagers(target) && target.getRequest() != null) {Request request = target.getRequest();target.setRequest(null);request.clear();}
可以看到, target 对应的 request 被置 null, 而旧的 request 被 “clear”。旧的 Request 被 clear 后,又是如何让资源没有去加载到关联的 Target 上的? 我们看其中 SingleRequest 的实现
# SingleRequest.java/*** Cancels the current load if it is in progress, clears any resources held onto by the request* and replaces the loaded resource if the load completed with the placeholder.** <p> Cleared requests can be restarted with a subsequent call to {@link #begin()} </p>** @see #cancel()*/@Overridepublic void clear() {Util.assertMainThread();assertNotCallingCallbacks();stateVerifier.throwIfRecycled();if (status == Status.CLEARED) {return;}cancel();// Resource must be released before canNotifyStatusChanged is called.if (resource != null) {releaseResource(resource);}if (canNotifyCleared()) {target.onLoadCleared(getPlaceholderDrawable());}// Must be after cancel().status = Status.CLEARED;}
可以看到 clear() 方法中先是 执行了 cancel(),该方法会取消加载资源请求与该 Request 的回调关联。
/*** Cancels the current load but does not release any resources held by the request and continues* to display the loaded resource if the load completed before the call to cancel.** <p> Cancelled requests can be restarted with a subsequent call to {@link #begin()}. </p>** @see #clear()*/void cancel() {assertNotCallingCallbacks();stateVerifier.throwIfRecycled();target.removeCallback(this);status = Status.CANCELLED;if (loadStatus != null) {loadStatus.cancel();loadStatus = null;}}
起关键作用代码为
loadStatus.cancel();
public static class LoadStatus {private final EngineJob<?> engineJob;private final ResourceCallback cb;LoadStatus(ResourceCallback cb, EngineJob<?> engineJob) {this.cb = cb;this.engineJob = engineJob;}public void cancel() {engineJob.removeCallback(cb);}}
LoadStatus 实际上只是持有了ResourceCallback回调(该回调可获取资源加载成功或失败)和 EngineJob。
EngineJob 是负责加载资源,并在加载成功后回调回去,这里 SingleRequest 实现了回调,所以它便可得知资源加载完成并获取到。所以 cancel() 调用后,即使旧的加载请求完成也不会回调到 Tareget 上。
target.setRequest(request);requestManager.track(target, request);
在该方法中,方法中,Target 持有了最新的 request , requestManager.track() 方法则触发了 request 的加载请求,实际是由内部 Engine 和 EngineJob 负责。当顺利加载成功后便回调到 Target 对象上,触发 target.onResourceReady(result, animation) 方法,图片便被正确显示出来了。
###########################################################
回到上面的流程,在into(view)
方法中,首先获取到了Request请求,然后开始执行Request。如果是复用的Request则直接执行begin(),否则执行track(target, request),但最终仍然会执行begin();
// ReqeustManager.java
synchronized void track(@NonNull Target<?> target, @NonNull Request request) { // 与lifecycle绑定 targetTracker.track(target); // 启动reqeust requestTracker.runRequest(request); }
// RequestTracker.java public void runRequest(@NonNull Request request) { requests.add(request); if (!isPaused) { request.begin(); // 立即开始加载 } else { //防止从以前的请求中加载任何位图,释放该请求所拥有的任何资源,显示当前占位符(如果提供了该占位符),并将该请求标记为已取消。 // request.java( Interface ) request.clear(); pendingRequests.add(request); // 加入队列等待执行 } }
Request类是interface类型,begin()是它的抽象方法,要想弄清楚begin()的具体实现,那就要先找到Request的实现类,SingleRequest
,我们直接去SingleReqeust类里面 看看begin方法如何实现的;
// SingleReqeust.java
public void begin() { if (status == Status.COMPLETE) { // 资源已下载,直接回调 // 执行动画 onResourceReady(resource, DataSource.MEMORY_CACHE); return; } // 计算尺寸 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()); } }
进入begin方法后首先判断如果资源已经过加载好了则直接回调onResourceReady显示图片并缓存,否则测量出图片尺寸后再开始加载图片(onSizeReady()中执行加载任务)并同时显示占位图:
接着再看onSizeReady()测量完图片尺寸后如何加载图片的:
// SingleRequest.java
@Override public void onSizeReady(int width, int height) { if (status != Status.WAITING_FOR_SIZE) { return; } status = Status.RUNNING; // 获取图片尺寸 float sizeMultiplier = requestOptions.getSizeMultiplier(); 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, callbackExecutor); }
可以看到真正的下载任务是在Engine类的load方法中实现的,其中也涉及到了图片缓存逻辑;
最终通过Handler机制,Glide从工作线程切换到主线程,并最终将Drawable对象显示到ImageView上;
具体再分析一下线程是如何切换的。
线程切换的过程
回到SingleRequest.onSizeReady()
# SingleRequest
@Overridepublic void onSizeReady(int width, int height) {...// 省略不相关代码...
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
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 = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
//focus1 更新相应属性创建缓存key--EngineKey
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,resourceClass, transcodeClass, options);
//尝试从活跃的资源内存缓存中中获取
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {cb.onResourceReady(active, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Loaded resource from active resources", startTime, key);}return null;
}
//尝试从内存缓存中获取
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {cb.onResourceReady(cached, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Loaded resource from cache", startTime, key);}return null;
}
//判断当前请求是在在队列中
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {current.addCallback(cb);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);
}
//创建请求job
EngineJob<R> engineJob =engineJobFactory.build(key,isMemoryCacheable,useUnlimitedSourceExecutorPool,useAnimationPool,onlyRetrieveFromCache);
//创建解码job
DecodeJob<R> decodeJob =decodeJobFactory.build(glideContext,model,key,signature,width,height,resourceClass,transcodeClass,priority,diskCacheStrategy,transformations,isTransformationRequired,isScaleOnlyOrNoTransform,onlyRetrieveFromCache,options,engineJob);
//保存当前请求job
jobs.put(key, engineJob);engineJob.addCallback(cb);
//开始请求
engineJob.start(decodeJob);return new LoadStatus(cb, engineJob);
}
注意focus1处的代码
假设一个ImageView的宽高是300300,另一个ImageView的宽高是900900,则在这两个ImageView中加载同一张图片资源会缓存几次?
创建图片缓存Key和图片的model(图片链接Url)、图片资源的宽高都有关系,因此两个不同宽高的ImageView加载同一图片会缓存两次。
1.尝试从活跃的资源内存缓存中中获取资源,取到直接回调返回,没取到执行第二步
2.尝试从内存缓存中获取资源,取到直接回调返回,没取到执行第三步
3.创建请求engineJob
4.执行请求
#EngineJob
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
// focus1
GlideExecutor executor = decodeJob.willDecodeFromCache()? diskCacheExecutor: getActiveSourceExecutor();
executor.execute(decodeJob);
}
DecodeJob实现了Runnable接口,这里将其添加到线程池中,接下来看下run方法 。
#DecodeJob
@Override
public void run() {
GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);
DataFetcher<?> localFetcher = currentFetcher;
try {//判断是否已经被取消if (isCancelled) {notifyFailed();return;}runWrapped();
} catch (Throwable t) {if (stage != Stage.ENCODE) {throwables.add(t);notifyFailed();}if (!isCancelled) {throw t;}
} finally {if (localFetcher != null) {localFetcher.cleanup();}GlideTrace.endSection();
}}
private void runWrapped() {
switch (runReason) {case INITIALIZE:stage = getNextStage(Stage.INITIALIZE);//根据当前流程步骤获取相应获取数据类(刚开始肯定为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);
}
}
# DecodeJob.getNextGeneratorprivate 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);
}
}
默认的AUTOMATIC方式是允许解码缓存的RESOURCE,所以在 getNextStage 会先返回Stage.RESOURCE_CACHE,然后在start中会返回diskCacheExecutor,然后开始执行DecodeJob,也就是说这里会获得一个ResourceCacheGenerator
,
#DecodeJob.runGenerators()private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;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();
}}
在 runGenerators 中,会调用 startNext,目前currentGenerator是ResourceCacheGenerator, 那么就是调用它的startNext方法:
@Overridepublic boolean startNext() {...# 省略部分代码...// 这里会从registry中匹配相应的Modeloader来加载数据loadData.fetcher.loadData(helper.getPriority(), this);...return started;}
这里是loadData.fetcher是HttpUrlFetcher。
# HttpUrlFetcher.loadData()
@Override
public void loadData(@NonNull Priority priority,@NonNull DataCallback<? super InputStream> callback) {
// 省略部分代码//连接网络获取图片流InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());//成功回调callback.onDataReady(result);
}
请求成功之后回调了DataCallback方法。最终会回调到DecodeJob的onDataFetcherReady
方法,之后在进行解码操作:
解码成功之后,回调EngineJob中的onResourceReady方法:
# EngineJobpublic void onResourceReady(Resource<R> resource, DataSource dataSource) {synchronized (this) {this.resource = resource;this.dataSource = dataSource;}notifyCallbacksOfResult();}
在notifyCallbacksOfResult方法中:
# Engine.java
void notifyCallbacksOfResult() {// 省略部分代码engineJobListener.onEngineJobComplete(this, localKey, localResource);for (final ResourceCallbackAndExecutor entry : copy) {// focus entry.executor.execute(new CallResourceReady(entry.cb));}decrementPendingCallbacks();}
注意focus行的代码:
使用entry中的executor执行线程,而entry就是ResourceCallbackAndExecutor ,而它里面的executor正好是我们
Executors.mainThreadExecutor()
。
很明显它是在这里切换到了主线程。
补充说明1:
首先回顾下主线程的创建,在RequestBuilder.java中的into方法中:
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {Util.assertMainThread();...return into(glideContext.buildImageViewTarget(view, transcodeClass),/*targetListener=*/ null,requestOptions,Executors.mainThreadExecutor());}Executor.javaprivate static final Executor MAIN_THREAD_EXECUTOR =new Executor() {private final Handler handler = new Handler(Looper.getMainLooper());@Overridepublic void execute(@NonNull Runnable command) {handler.post(command);}};
它最终会一层一层传递到EngineJob中。engineJob.addCallback(cb, callbackExecutor); 它将被实例化为一个ResourceCallbackAndExecutor对象并add到cbs集合中。
EngineJob.javasynchronized void addCallback(final ResourceCallback cb, Executor callbackExecutor) {...cbs.add(cb, callbackExecutor);...}void add(ResourceCallback cb, Executor executor) {callbacksAndExecutors.add(new ResourceCallbackAndExecutor(cb, executor));}
补充说明2:
以上代码基于glide 4.11.0版本,可能代码有所不同,但是原理是一样。
参考:
Android源码进阶之Glide加载流程和源码详解
Glide图片加载流程浅析相关推荐
- Glide 4.9源码解析-图片加载流程
本文Glide源码基于4.9,版本下载地址如下:Glide 4.9 前言 由于Glide源码真的很复杂,因此本文只分析和贴出与图片加载流程相关的功能以及代码.另外本文Glide源码基于4.9,与3.x ...
- Android Glide图片加载框架(二)源码解析之into()
文章目录 一.前言 二.源码解析 1.into(ImageView) 2.GlideContext.buildImageViewTarget() 3.RequestBuilder.into(Targe ...
- 从源码分析Android的Glide库的图片加载流程及特点
转载:http://m.aspku.com/view-141093.html 这篇文章主要介绍了从源码分析Android的Glide库的图片加载流程及特点,Glide库是Android下一款人气很高的 ...
- Android Glide图片加载框架(三)缓存机制
文章目录 一.缓存简介 二.缓存用法 内存缓存方式 磁盘缓存方式 三.缓存KEY 四.内存缓存 内存缓存流程 五.磁盘缓存 磁盘缓存流程 Android Glide图片加载框架系列文章 Android ...
- Android Glide图片加载框架(二)源码解析之with()
文章目录 一.前言 二.如何阅读源码 三.源码解析 1.with() Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图 ...
- Glide图片加载框架的使用
1. 介绍 Glide是一个快速高效的Android图片加载库,注重于平滑的滚动.Glide提供了易用的API,高性能.可扩展的图片解码管道(decode pipeline),以及自动的资源池技术.G ...
- Android Glide图片加载框架(四)回调与监听
文章目录 Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源码解析之with() Android Gl ...
- Android Glide图片加载框架(二)源码解析之load()
文章目录 一.前言 二.源码分析 1.load() Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源 ...
- Android Glide图片加载框架(一)基本用法
文章目录 一.前言 二.简介 三.基本用法 第一步:调用 Glide.with() 方法创建加载图片的实例 第二步:调用 load() 方法指定待加载的图片资源 第三步:调用 into() 方法绑定显 ...
最新文章
- jdbc mysql查询显示图片_JDBC【向数据库中存入读取图片】
- php 检测是否是微信浏览器,php判断是否是微信浏览器和是否是移动端代码
- 使用ffmpeg实现转码样例(代码实现)
- 指针:调用自定义排序函数sort,对输入的n个数进行从小到大输出。
- 如何删除 Windows.old 文件夹
- 115. 不同的子序列(JavaScript)
- couchdb 视图操作_CouchDB 教程
- 淘宝API签名异常,如何正确计算SIGN参数?(error code:25 Invalid Signature)
- 全新的服务器debian/ubuntu---校准时间、更新apt,设置ssh远程访问
- 网络连接正常,浏览器确没有网?
- android 强制竖屏
- 阿里巴巴-数据平台事业部-数据产品部:寻找真爱
- POBPM集成-数据过滤
- 如何通过自学,成为数据挖掘“高手”
- 信用评分卡 (part 5 of 7)
- 【思维进阶】新手小白如何练习写作
- 速联2.0 实现SCADA软件远程无线监控环保设备
- 京东面经 10.10
- 浅析镜头机身跑焦原因与解决办法
- 【渝粤教育】国家开放大学2018年春季 4972T农业项目投资 参考试题