作为Android平台最受欢迎的图片加载库之一,Glide快速、高效、使用简单易上手,广受欢迎且受Google官方推荐。其官方描述为:Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。
本文就Glide的使用谈起,逐步探索其主线源码实现流程及逻辑。
简单使用
添加依赖:
dependencies {
implementation "com.github.bumptech.glide:glide:4.12.0"
}
简单使用:
Glide.with(context)
.load(url)
.into(imageView)
即:
//常规使用方式
Glide.with(context).load("图片路径").into("view组件");
//通过thumbnail控制缩略比例来显示不同比例大小的缩略图
Glide.with(context).load("图片路径").thumbnail("缩略比例").into("view组件");
//加载GIF动画
Glide.with(context).load("图片路径");
//加载视频
Glide.with(context).load("视频路径");
//多参数控制
Glide.with(context)
.load("图片路径")
.placeholder(R.mipmap.ic_launcher)//指定图片未成功加载前显示的图片
.error(R.mipmap.ic_launcher)//指定图片加载失败显示的图片
.override(300,300)//指定图片的尺寸
.fitCenter()//指定图片缩放类型
.centerCrop()//指定图片缩放类型
.skipMemoryCache(true)//是否跳过内存缓存
.diskCacheStrategy(DiskCacheStrategy.NONE)//不缓存任何内容
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)//缓存转换过后的图片
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)//根据图片资源智能地选择使用哪一种缓存策略(默认选项)
.diskCacheStrategy(DiskCacheStrategy.ALL)//既缓存原始图片,也缓存转换过后的图片
.diskCacheStrategy(DiskCacheStrategy.DATA)//缓存原始图片
.priority(Priority.HIGH)//指定优先级
.into(imageView);//指定显示图片的ImageView
同样,取消加载也很简单:
Glide.with(context).clear("view组件");
流程
从日常使用中,会发现Glide无论如何封装修改参数等,有三个方法是会一直有的,with(),load(),into()。而这三个方法也是主线流程关键线索,其大体作用如下:
而这也恰巧是源码观察的切入点。
with()
with()是Glide中的一组静态方法,其重载方法如下:
继续跟踪getRetriever():
方法体内的判空逻辑之后,就是return。搜索Glide.get(context)不难发现Glide在这里是个单例:
在观察getRequestManagerRetriever()之前我们先看Glide.get():
此方法较长,但不难看出其逻辑是进行判断Glide对象的初始化:
@GuardedBy("Glide.class")
@SuppressWarnings("deprecation")
private static void initializeGlide(
@NonNull Context context,
@NonNull GlideBuilder builder,
@Nullable GeneratedAppGlideModule annotationGeneratedModule) {
Context applicationContext = context.getApplicationContext();
List<GlideModule> manifestModules = Collections.emptyList();
if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
manifestModules = new ManifestParser(applicationContext).parse();
}
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
Set<Class<?>> excludedModuleClasses = annotationGeneratedModule.getExcludedModuleClasses();
Iterator<GlideModule> iterator = manifestModules.iterator();
while (iterator.hasNext()) {
GlideModule current = iterator.next();
if (!excludedModuleClasses.contains(current.getClass())) {
continue;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "AppGlideModule excludes manifest GlideModule: " + current);
}
iterator.remove();
}
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
for (GlideModule glideModule : manifestModules) {
Log.d(TAG, "Discovered GlideModule from manifest: " + glideModule.getClass());
}
}
RequestManagerRetriever.RequestManagerFactory factory =
annotationGeneratedModule != null
? annotationGeneratedModule.getRequestManagerFactory()
: null;
builder.setRequestManagerFactory(factory);
for (GlideModule module : manifestModules) {
module.applyOptions(applicationContext, builder);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.applyOptions(applicationContext, builder);
}
Glide glide = builder.build(applicationContext, manifestModules, annotationGeneratedModule);
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;
}
这里还有个build():
此方法内则是进行大量的初始化:
@NonNull
Glide build(
@NonNull Context context,
List<GlideModule> manifestModules,
AppGlideModule annotationGeneratedGlideModule) {
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
if (arrayPool == null) {
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
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);
}
GlideExperiments experiments = glideExperimentsBuilder.build();
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory, experiments);
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptionsFactory,
defaultTransitionOptions,
defaultRequestListeners,
manifestModules,
annotationGeneratedGlideModule,
experiments);
}
最终返回初始化好后的Glide对象和requestManagerRetriever对象:
这里我们就找到Glide的真实构造方法了:
现在,我们要暂停下继续深入,回到我们最初的入口来:
Glide.get()获取到的是Glide单例对象和初始化好的requestManagerRetriever对象等;
而后续的getRequestManagerRetriever()也只是返回我们之前就声明的requestManagerRetriever对象;
到这里,我们在with()中的getRetriever(context)就结束了,重点看后面.get()了,也就是requestManagerRetriever.get()了。
根据方法返回设计,不难看出这里的.get()是返回的一个RequestManager对象。重点观察其内部实现:
这里经过线程判断、异常判断抛出等逻辑,最终执行:
这里首先判断是否已经存在RequestManager对象,没有则通过工厂对象和Glide单例对象创建requestManager。然后通过SupportRequestManagerFragment对其进行绑定,然后返回此requestManager对象。观察其requestManager对象会发现:
注释即提示Glide的这五个重载方法都会返回RequestManager对象,而观察其不同重载方法的调用流程会发现其最终都会调用到supportFragmentGet()或fragmentGet(),整体流程一致。此时,我们可以说其with()都是返回一个RequestManager对象。这里观察RequestManager,会发现其也有onStart()、onStop()、onDestoy()的生命周期回调,类似Activity。
无界面Fragment
在supportFragmentGet()中还有获取Fragment的操作:
在这里可看出通过TAG来查找有没有这个SupportRequestManagerFragment,有就复用。没有就出初始化,所以我们继续跟踪这个Fragment:
可见,此Fragment无界面,同时注册了ActivityFragmentLifecycle在onStart、onStop、onDestroy的回调中进行了监听回调(注意这里,很重要)。同时在这里,RequestManager创建时:
我们传入了当前这个无界面Fragment创建的glide的生命周期对象(current.getGlideLifecycle()),这时候观察RequestManager构造方法会发现:
其构造方法中的Lifecycle接口如下: 传进来的lifecycle对象是无界面fragment中创建的lifecycle(current.getGlideLifecycle()), 这段代码含义是在主线程情况下,addListener(this)中this就是requestManger,即requestManger实现了LifecycleListener的接口。
可见,LifecycleListener接口有onstart,onstop,ondestory方法。总结下,在这里,这个无界面Fragment中持有生命周期拥有者ActivityFragmentLifecycle,该类实现了Lifecycle接口,Lifecycle接口中存在onstart,onstop,ondestory方法。在无界面Fragment生命周期发生变化时,该生命周期拥有者ActivityFragmentLifecycle 会调用它自身的onStart()、onStop()、onDestory(),而其方法内部实现则会调用LifecycleListener的onstart,onstop,ondestory方法。
对应的,监听者LifecycleListener则会调用它自身的onStart,onStop等方法,而RequestManager实现了LifecycleListener接口,也就是Fragment生命周期变化的时候会回调RequestManager内部实现的LifecycleListener接口的onStart(),onStop(),onDestory()。
注意,这里的生命周期对应的只有onStart(),onStop(),onDestory()三个方法。
总结下with()的意义:Glide.with(this)绑定了Activity(或Fragment等)的生命周期。在获取Glide对象时,通过其中build()方法进行大量参数的初始化。同时在Activity(或Fragment)内新建了一个无界面的Fragment,这个Fragment持有一个Lifecycle接口,通过Lifecycle接口在Fragment关键生命周期回调RequestManager的相应方法进行相关从操作。例如在生命周期onStart时继续加载,onStop时暂停加载,onDestory时停止加载任务和清除操作。
load()
与with()类似,load()也有多个重载方法。
我们跟踪这两个方法的实现。
这里的两个方法返回的是RequestBuilder对象,load()的实现在RequestBuilder中:
可见,load()中都调用了asDrawable(),然后再调用对应的load()(感觉像是废话,但其实不是)。我们先看asDrawable()的最终调用:
这里会看到我们传入的load()参数(file、bitmap或uri等对象)resourceClass,对应其构造方法:
在这里保存为transcodeClass对象。asDrawable()最后还是返回了RequestBuilder对象。先看到这里,我们再来看asDrawable()调用对应的各自load(),即RequestBuilder的load()。
所做操作不多,将isModelSet设置为true,传入model对象。
我们继续看其构造方法,里面有个apply():
最终调用父类BaseRequestOptions的apply():
可见,这里全是配置信息,一大堆变量,包括但不限于:
·diskCacheStrategy :磁盘缓存策略;
·errorPlaceholder :出错时的占位图;
·placeholderDrawable :加载时候的占位图;
·overrideWidth、overrideHeight :加载图片固定宽高;
...
总结下,load()的操作就是返回RequestBuilder对象,同时内部进行一些默认配置属性的初始化操作。
into()
Glide源码中最复杂最核心的地方来了,该方法也是Glide代码执行的终点,这仿佛也是在告诉我们年轻人做事要有始有终。
一样的三个重载方法,在这里我们重点关注into(ImageView view)即可。
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
BaseRequestOptions<?> requestOptions = this;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions = requestOptions.clone().optionalFitCenter();
break;
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
}
前面两行代码,一个判断必须指定是主线程,一个判断传入view是否为空。都是常规检查。 后续requestOptions则是获取用户设置的一些属性,如果用户没有设置,则会执行默认逻辑:
对这一块,我们不做过多介绍,只关注主线流程。我们继续看下面代码:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> 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();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
方法体内最初两行判断非空,我们可以先不分析。
这里Request是个接口,我们重点关注buildRequest()的实现。
代码流程有点绕,但最终还是让我们找到了,最终返回的是个SingleRequest对象。我们回到最初,看接下来代码:
这段代码逻辑是判断上一个request是否被处理,没有,则开始处理。此处逻辑不属于主线逻辑,不过多讲述,重点观察:
我们刚刚获取到的request对象,最终调入了RequestManager的track()中:
可见,这里代码作用是记录request,并启动request,重点关注runRequest():
此方法在RequestTracker中,而在RequestTracker中,request是个执行队列,pendingRequests是等待队列:
代码中逻辑则是用该队列先记录请求(requests.add(request)),如果不是isPaused状态,则request.begin()启动队列,如果是isPaused状态,则则记录加载request,等状态恢复时,进行重新启动(pendingRequests.add(request);)。所以,我们这里重点关注begin():
是个接口方法,既然是个接口,我们就要找其派生类,上面已经推导是SingleRequest,因此我们看SingleRequest类即可:
方法内代码太多,除去安全检查等非必要代码。简化为:
public void begin() {
synchronized (requestLock) {
...
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
if (status == Status.COMPLETE) {
onResourceReady(
resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return;
}
experimentalNotifyRequestStarted(model);
cookie = GlideTrace.beginSectionAsync(TAG);
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));
}
}
}
如果处于正在运行状态,则抛出对应异常,如果是完成状态,则调用onResourceReady(),直接从请求中缓存的 Resource 返回,回调给 ViewTarget 显示资源。这里可以看到重点关注地方:
这里如果用户没有设置宽高,则会通过target.getSize()去测量宽高,然后重新onSizeReady():
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
synchronized (requestLock) {
......
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);
// This is a hack that's only useful for testing right now where loads complete synchronously
// even though under any executor running on any thread but the main thread, the load would
// have completed asynchronously.
if (status != Status.RUNNING) {
loadStatus = null;
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
}
最终走入了engine.load(),这里的engine在Glide创建时就构造了。在load()中有一个EngineKey:
可以理解为图片的唯一标识符,通过图片的宽高签名数据名称路径等信息生产唯一标识符,用此标识符作为key来进行三级缓存提取的依据,以此来判断是需要访问内存缓存还是活跃缓存还是磁盘缓存等。
在构建key后,我们马上查找活跃缓存和内存缓存。
我们观察下这几个方法:
加载内存缓存方法中,缓存活跃资源,通过弱引用方式保存到 Map 中。
如果有活跃缓存和内存缓存,则会return出去,最终执行:
将内存中加载的资源回调给 ViewTarget 显示。
如果缓存中没有,则执行waitForExistingOrStartNewJon();
一进入方法体会发现,EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);这里就开始查找有没有正在运行的任务:
可以看出Enginejob此类包含很多线程池,
而对应的后续DecodeJob则是对应的执行的任务,
这里可以看到start()内部是典型的线程池,执行DecodeJob的线程池:
DecodeJob能被线程池执行,说明此类实现了Runnable接口:
则对应有run()重写:
也就是说,start(DecodeJob)则最终还是执行的DecodeJob的run(),在run()中,我们重点关注runWarpped():
在runWarpped()中,我们可以看到两个关键方法getNextStage()和getNextGenerator(),
可以看到,这里有点像递归一样:
1、先从RESOURCE_CACHE 中去找 , 这个缓存拿到的图片已经解码过可以直接给ImageView使用, 当前currentGenerator对应的是 ResourceCacheGenerator;
2、如果 RESOURCE_CACHE 缓存中没有 , 则从 DATA_CACHE 中查找 , DATA_CACHE 缓存中缓存的是未经解码的源数据 , 需要解码才能给ImageView使用 , 当前currentGenerator对应的是DataCacheGenerator;
3、如果RESOURCE_CACHE 和 DATA_CACHE 中都没有找到 , 则从源(服务器或者本地)去找到图片数据 , 当前currentGenerator对应的是 SourceGenerator。
首次请求没有缓存所以会返回 SourceGenerator对象,即currentGenerator对应的是 SourceGenerator。则对应的runWrapped()中的runGenerators()中的currentGenerator.startNext()是SourceGenerator中的startNext():
在这里开始加载数据,(SourceGenerator即ResourceCacheGenerator,DataCacheGenerator,SourceGenerator对象)我们观察默认情况下,SourceGenerator对象的startNext()。
public boolean startNext() {
...
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;
startNextLoad(loadData);
}
}
return started;
}
可见,这里观察的重点变成了loadData.fetcher的loadData()。查看loadData方法,最后来到DataFetcher的接口中。
很明显,这里不是最终调用的地方。这里,我们回到我们上一级代码,观察loadData对象的产生:
在这里的modelLoaders对象是获取所有能处理此 model的 ModelLoaders ,然后对其进行遍历,可以看出这里重点方法是
这里就引出Glide的注册机制了。
注册机制
Registry是Glide中非常重要的知识,可以把它理解成连结各个核心功能模块的集中营。 在 Glide 的构造方法中,会把所有的编码、解码和数据加载等逻辑通过 Registry 的 append() 方法登记到 Registry 中,我们可以在 AppGlideModule 的 registerComponents() 方法中获取到 registry 实例,通过这个实例就可以替换掉对应的实现。
Registry是一个组件管理类,它的主要用途是扩展和替换Glide组件,这些组件包括加载,编码,解码等逻辑;当然,在这里Register不会承担所有模块的注册工作,而是把各模块分配到不同的Registry当中,这些主要模块功能如下:
·ModelLoaderRegistry ://数据加载模块注册
·EncoderRegistry://所有对数据进行编码模块的注册
·ResourceDecoderRegistry://处理过的解码模块注册
·ResourceEncoderRegistry://处理过的编码模块注册
·DataRewinderRegistry : //数据流重置起点模块注册
·TranscoderRegistry: // Resource进行转换模块注册
·ImageHeaderParserRegistry //图片头解析模块注册
模块太多,这里不一一列举,仅对ModelLoader做分析,其余皆可举一反三。ModelLoader是通过ModelLoaderRegistry进行管理,ModelLoader需要接受两个泛型类型<Model,Data>:
ModelLoader本身是一个工厂接口,主要工作是经过内部类LoadData将复杂数据模型通过DataFetcher转换成需要的Data,LoadData是ModelLoader的内部类,是对DataFetcher和Key的封装实体,ModelLoader的创建用ModelLoaderFactory:
这里看一下append():
append() 方法会用来源类型(Model)、原始数据类型(Data)和 ModelLoaderFactory 来创建不同类型的 Entry ,这些 Entry 会保存在 MultiModelLoaderFactory 工厂中。
可以看到,getModelLoaders()->getModelLoadersForClass(),又最终是从MultiModelLoaderFactory 这个工厂中去取数据的,还记得我们上面跟到哪了吗?
这里的glideContext.getRegistry().getModelLoaders(model)就相当于通过ModelLoaderRegister.getModelLoaders()中的multiModelLoaderFactory获取到的ModelLoader对象。(getModelLoaders()中,其入参类型为,返回类型为<Model,?>,具体类型就是我们调用Glide.with().load(model)时load()传入的类型,返回类型<?>是我们在Registry中注册的所有符合输入的类型,比如InputStream或者ByteBuffer)一个ModelLoader对象有来源类型(Model)、原始数据类型(Data)。其对应数据关系在Glide初始化即提供了:
这里可以看出,registry.append()最终添加了对应我们在调用load方法时传入的不同参数类型,所对应的加载器。而我们传入的是网址,说明含有HTTP请求,在此代码中搜“http”可看到:
只有这一个符合条件,事实上也确实如此(打个断点,执行一下就能确定了),接下来着重观察HttpGlideUrlLoader中的HttpUrlFetcher.loadData():
可以看到,Glide是使用HttpURLConnection进行网络数据加载的,看到这里应该明白为什么导入Glide要添加网络权限了吧。其方法内逻辑不难理解,就是从InputStream解析出来Resource的流程,InputStream流加载成功之后会通过callback.onDataReady(result)回调到SourceGenerator:
然后层层回调传入到DecodeJob.onDataFetcherReady():
这里path.load()将宽度、高度、回调等参数全部传出:
而在此方法中,可以看到,这里正在将我们的inputStream对象转换成bitmap。
继续跟踪decodeResource():
可以看到decoder也是实现了ResourceDecoder接口:
而此接口的实现太多了。
在不知道实现方式的情况下,可以打断点的方式来确定,在这里最后确认是StreamBitmapDecoder。
观察其方法入口泛型可知,我们传入时,就已经转换成了bitmap对象。其核心代码在于:
解析出的Resource绘制到对应的ImageView,最终经过重重回调,执行到ImageViewTarget.onResourceReady():
这是个抽象方法,我们寻找其子类实现即可:
至此,画面出来了,into()流程结束。
总结,Glide 框架主要分为三个部分:
第一个部分: with 阶段,注册编解码器,初始化变量(Glide,RequestManager,Engine等)和绑定页面生命周期等操作,用于管理请求和监听生命周期事件。
第二个部分:load 阶段,为每个请求配置单独的 Option,比如:设置 width,height,DiskCacheStrategy,Transaction等。
第三个部分:into 阶段,最复杂的阶段,启动请求,开始加载数据,对数据进行解码和转码操作,缓存解码数据或者原始数据,显示视图。
这里,我们可以得出更详细的流程图如下:
这里发散一下思维,经过上述源码主流程分析,我们可以得知使用Glide加载图片时,Glide会默认根据View视图对图片进行压缩和转换,而不显示原始图。(说句题外话,这也是Glide加载速度高于Picasso的原因)
图片缓存
Glide源码主线流程中除了感知生命周期外,另外一个重要的就是其图片缓存设计了。在开始介绍其缓存流程之前,我们可以先了解下引入缓存的目的:
1、Bitmap的创建/销毁操作较消耗内存,会导致频繁的GC;使用缓存可以更加高效加载Bitmap,减少卡顿;
2、对重复图片可以从缓存中读取,这样能减少流量消耗,加快页面响应速度。
二级缓存 or 三级缓存
Glide缓存分为内存缓存和磁盘缓存,其中内存缓存是由弱引用(前文中的活跃缓存) +LruCache组成。(关于这块,面试的时候经常会听到说三级缓存、也有说二级缓存的,其实二级缓存、三级缓存在这都一样。)
内存缓存:防止重复将图片读入到内存,造成内存资源浪费,只缓存转换后的图片,而不是原始图片;
磁盘缓存:防止重复从网络或其他地方下载和读取数据,可缓存原始图片和转换过后的图片,用户自行设置。
Glide中,内存缓存和磁盘缓存相互不影响,独立配置,其中内存缓存是默认开启的。当然,你也可以设置跳过开启(关闭):
skipMemoryCache(true)
缓存的读取顺序为:弱引用 –> LruCache –> 磁盘缓存 –> 网络
数据的写入顺序为:网络 –> 磁盘缓存 –>LruCache –> 弱引用
弱引用(活跃缓存)
弱引用通过HashMap存储,这里的Key是缓存的Key,经过EngineKey对其url、宽高等属性进行唯一标识符算法,确定图片对象的唯一性。这个Map的value则是图片资源对象的弱引用形式。看到这里的ActiveResources后,有人管这叫活跃缓存,为了避免麻烦,本文后续统一称为弱引用。
LruCache
Lru(Least Recently Used):最近最少使用,它的核心思想是,当缓存满的时候,会优先淘汰最近最少使用的缓存对象。
使用LinkedHashMap来缓存资源(强引用),并设定一个缓存的大小。如果有资源被访问到,首先会在链表中删除该节点,然后再添加到链表头,这样就可以保证链表头部的节点是最近访问过的。而当缓存的数量达最大值的时候,就会将链表尾部(最近最少使用)的数据移除。(这里不介绍Lru算法的细节)
取缓存(数据)
在内存缓存中有一个变量(EngineResource中的acquired),其作用是记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1。
获取图片资源是先从弱引用取缓存对象,成功获取后,引用计数+1;没有成功的话从LruCache中获取缓存,获取成功的话,引用计数也是+1,同时把图片从LruCache缓存转移到弱应用缓存池中;再没有获取到的话就通过EngineJob开启线程池去加载图片,这时候获取成功的话,引用计数也是+1,会把图片放到弱引用。
存数据
在加载图片之后。通过EngineJob开启线程池加载图片,获取到数据之后,回调到主线程,把图片存到弱引用中。当图片不再使用的时候,比如说暂停请求或者加载完毕或者清除资源时,就会将其从弱引用中转移到LruCache缓存池中。总结一下,就是正在使用中的图片使用弱引用来进行缓存,暂时不用的图片使用LruCache来进行缓存的功能。同一张图片只会出现在弱引用和LruCache中的一个。
为什么要在这里设计一个弱引用?
1、分压策略,减少Lrucache 中trimToSize的概率。如果正在remove的是张大图,lrucache正好处在临界点,此时remove操作,将延缓Lrucache的trimToSize操作;
2 提高效率:弱引用用的是HashMap,Lrucache用的是LinkedHashMap,从访问效率而言,肯定是HashMap更高。
DiskLruCache(磁盘缓存)
磁盘缓存策略共五种:
·DiskCacheStrategy.DATA:只缓存原始图片;
·DiskCacheStrategy.RESOURCE:只缓存转换过后的图片;
·DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换过后的图片;对于远程图片,缓存 DATA和 RESOURCE;对于本地图片,只缓存 RESOURCE;
·DiskCacheStrategy.NONE:不缓存任何内容;
·DiskCacheStrategy.AUTOMATIC:默认策略,尝试对本地和远程图片使用最佳的策略。当下载网络图片时,使用DATA(原因很简单,对本地图片的处理可比网络要容易得多);对于本地图片,使用RESOURCE。
如果在内存缓存中没获取到数据会通过EngineJob开启线程池去加载图片,这里有2个关键类:DecodeJob 和EngineJob。EngineJob 内部通过线程池来管理资源加载,当资源加载完毕时回调;DecodeJob是线程池中的一个任务(之前源码流程梳理中有说过)。
磁盘缓存通过DiskLruCache来实现,根据缓存策略,会有2种类型的图片,DATA(原始图片)和 RESOURCE(转换后的图片)。磁盘缓存依次通过ResourcesCacheGenerator、SourceGenerator、DataCacheGenerator来获取缓存数据。ResourcesCacheGenerator获取的是转换过的缓存数据;SourceGenerator获取的是未经转换的原始的缓存数据;DataCacheGenerator是通过网络获取图片数据再按照按照缓存策略的不同去缓存不同的图片到磁盘上。(上述源码流程梳理中有详细说明)
再梳理下,这段话可以这样概括:磁盘缓存通过DiskLruCache实现,根据缓存策略的不同会获取到不同类型的缓存图片。其逻辑为:先从转换后的缓存中去获取;没获取到的话再从原始的(没有转换过的)缓存中去获取数据;再没有获取到的话就从网络加载图片数据,获取到数据之后,再依次缓存到磁盘和弱引用。