最近比较无聊,为了找点事干,就花了两天时间把Glide的源码大概看了一下。刚开始看Glide的源码头脑还是比较乱的,因为作者引入了几个概念,又大量用了泛型,如果不了解这些概念读起代码来就比较痛苦,我也没有详细看各种实现细节的东西,只是了解了下这个框架的大概样子,在这篇文章里,我会介绍下Glide中的一些关键概念,并走一遍图片加载流程,如果你要阅读Glide源码的话,应该多少会有点帮助。
基本概念
首先是三个最基本的概念:Model
, Data
和Resource
。
想一下,我们加载图片需要什么?一般是一个url,但url并不是所有情况,还有资源ID,文件等等,甚至可以是Feed流中的一条Feed,虽然一般我们会从Feed中取出图片的url来转换为从url中加载的情况,Glide把这些抽像为了一个概念,就是Model
,所以Model
就是数据地址的最初来源。
Model
并不能直接解析为图片,比如一个url,是要转换为网络流的InputStream才能被解析为图片的,Model
需要进行一次转换才能做为数据解析的数据源,这些转换后的东西就叫做Data
,Glide并没有一个Data类,但有很多和它相关的概念,如dataClase,DataFetcher等。
那么Resource
呢,其实它就是一个包装类,一个wrapper,它wrap一个对象,使这个对象可以通过对象池进行缓存与重用。
这三个基本概念介绍完了,接下来看一下Glide基本框架。
做为一个图片加载框架,肯定会包含缓存部分。
可以从网上很容易的了解到,Glide的磁盘缓存可以缓存原始数据,也可以缓存处理过的数据。什么意思呢,就是你有一张1000x1000的图片,但你是在列表中展示的,比如是200x200,那么缓存时可以直接将整个网络流缓存下来,即1000x1000的图片,要展示的时候再缩放,但这就降低了展示效率,所以Glide也可以把处理过的200x200的图片缓存起来,增加了缓存大小,但优化了展示速度。
至于怎么把数据缓存到磁盘,就引入了一个叫Encoder
的概念,Encoder
是用来持久化数据的。
但看源码时你会发现,Glide中有一个类叫Registry
,可以注册多个Encoder
,但你会发现它还可以注册ResourceEncoder
。这两个Encoder
很容易引起混淆,而其实ResouseEncoder
继承自Encoder
。Encoder
是用来持久化Data
的,ResourceEncoder
是用来持久化Resource
的。看Glide默认注册的Encoder
就知道了,默认注册的Encoder
为ByteBuffer
和InputStream
,而ResourceEncoder
是Bitmap
、BitmapDrawable
和GifDrawable
,也就是一个持久化原始数据,一个持久化处理过的数据。我感觉把Encoder
做为一个上级的抽象,引入一个和ResourceEncoder
同级的DataEncoder
就好理解了,正好和前面的基本概念Data
和Resource
对应。
有Encoder
就有Decoder
,对应的类叫ResourceDecoder
,用来将数据(InputStream等)解析为Resource
。
图片加载出来后还可能会应用各种变换,如圆角图片,圆形图片,处理这部分工作的叫Transformation
基础概念介绍的差不多了,加载流程也差不多出来了:
但我们发现前面的介绍中少了一环,即:Glide是怎么把Model
转换为Data
的。这就引入另一个概念,ModelLoader
,就是把Model
转换成Data
的,为了方便说明,直接把这个类的代码贴上来了,去掉了一些注释。
/**
* A factory interface for translating an arbitrarily complex data model into a concrete data type
* that can be used by an {@link DataFetcher} to obtain the data for a resource represented by the
* model.
*
* @param <Model> The type of the model.
* @param <Data> The type of the data that can be used by a
* {@link com.bumptech.glide.load.ResourceDecoder} to decode a resource.
*/
public interface ModelLoader<Model, Data> {
/**
* Contains a set of {@link com.bumptech.glide.load.Key Keys} identifying the source of the load,
* alternate cache keys pointing to equivalent data, and a
* {@link com.bumptech.glide.load.data.DataFetcher} that can be used to fetch data not found in
* cache.
*
* @param <Data> The type of data that well be loaded.
*/
class LoadData<Data> {
public final Key sourceKey;
public final List<Key> alternateKeys;
public final DataFetcher<Data> fetcher;
public LoadData(Key sourceKey, DataFetcher<Data> fetcher) {
this(sourceKey, Collections.<Key>emptyList(), fetcher);
}
public LoadData(Key sourceKey, List<Key> alternateKeys, DataFetcher<Data> fetcher) {
this.sourceKey = Preconditions.checkNotNull(sourceKey);
this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
this.fetcher = Preconditions.checkNotNull(fetcher);
}
}
LoadData<Data> buildLoadData(Model model, int width, int height, Options options);
boolean handles(Model model);
}
ModelLoader
有两个方法,一个handles
表示是否可以处理这个类型的Model
,如果可以的话就可以通过buildLoadData
生成一个LoadData
,而LoadData
包含了要用来做缓存的key,及用来获取数据的DataFetcher
。
到这里,整个加载流程就清楚了:
基本加载流程
接下来要做的就是根据我们的使用方法走一遍流程,调用如下:
Glide.with(mContext)
.load(url)
.apply(RequestOptions.placeholderOf(R.drawable.loading))
.into(myImageView);
一步步看,先是Glide.with(mContext)
:
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
通过RequestManagerRetriever
获取到了一个RequestManager
,至于为什么还需要一个RequestManagerRetriever
并有各种重载方法,主要是因为Glide通过SupportRequestManagerFragment
和RequestManagerFragment
关联了Activity或Fragment的生命周期,用来做pauseRequests
等操作。
然后是load
:
public RequestBuilder<Drawable> load(@Nullable Object model) {
return asDrawable().load(model);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class).transition(new DrawableTransitionOptions());
}
public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide.getGlideContext(), this, resourceClass);
}
是asDrawable.load(model)
的缩写,就是说这个Model我是要加载为Drawable的,最终返回一个RequestBuilder
,看名字就知道是做什么了,不过这个类主要是设置Thumbnail Request,Transition等个别设置(旧版本中placeHolder等也是在这里设置的),大部分设置在RequestOptions
里,这就是下面这一句:
apply(RequestOptions.placeholderOf(R.drawable.loading))
应用一个RequestOptions
,RequestOptions
可以设置各种请求相关的选项,如占位图片,加载失败的图片,缓存策略等。RequestOptions
继承自BaseRequestOptions
,但全是工厂方法生成各种RequestOptions。
最后就是into
了,把图片加载到一个Target
中。
public Target<TranscodeType> into(ImageView view) {
...
return into(context.buildImageViewTarget(view, transcodeClass));
}
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request previous = target.getRequest();
if (previous != null) {
requestManager.clear(target);
}
requestOptions.lock();
Request request = buildRequest(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
Target
是要加载到的目标,比如ImageViewTarget
,AppWidgetTarget
,在这里我们传进来了一个ImageView
,内部生成了一个DrawableImageViewTarget
。这里最主要的操作是buildRequest
然后交给RequestManager
去track
。
void track(Target<?> target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
// RequestTracker
public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
TargetTracker
主要就是记录一下所有正在加载的图片的Target
,所以加载行为是在RequestTracker.runRequest
中的,runRequest
先判断是否是pause状态(RequestManager设置),如果不是就直接调用Request.begin
触发加载,否则就回到pending队列里等待resume。
除了设置缩略图的情景,使用的Request
都是SingleRequest
,看一下它的begin
方法:
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));
}
}
加载逻辑是这几行:
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
判断下是否知道Target
的大小,如果大小已知就调用onSizeReady
,否则就调用target.getSize
获取它的大小,当成功获取到大小后,会通过回调继续调用onSizeReady
,所以整个加载方法都是在onSizeReady
里的。至于Target
怎么获取它的大小,那要看它的实现了,对于ImageViewTarget
,是通过ViewTreeObserver.OnPreDrawListener
等到View要测绘的时候就知道它的大小了。
onSizeReady
就是把操作转移到了Engine.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,
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,
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);
}
在Engine.load
中,先loadFromCache
,如果缓存没有命中就再loadFromActiveResources
,这是两级内存缓存,第一级是LruCache,第二级是ActiveCache,主要作用是,有可能一个图片很早就被加载了,可能已经从LruCache被移除掉了,但这个图片可能还在被某一个地方引用着,也就是还是Active的,那它就可能在将来仍被引用到,所以就把它保留在二级的ActiveCache中,ActiveCache中是以弱引用引用图片的,并通过ReferenceQueue
监测弱引用的回收,然后用Handler.IdleHandler
在CPU空闲时被被回收的引用项从ActiveCache中移除。
接下来看对应的Key是否已经正在加载,如果是的话,就addCallback
,这样如果有多个地方同时请求同一张图片的话,只会生成一个加载任务,并都能收到回调,这点是比Universal-Image-Loader好的地方。
正常的加载流程是生成一个EngineJob
和一个DecodeJob
,通过engineJob.start(decodeJob)
来进行实际的加载。
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
EngineJob.start
直接将DecodeJob
交给Executor去执行了(DecodeJob
实现了Runnable
接口)。DecodeJob
的加载操作放到了runWrapped
中
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
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);
}
}
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);
}
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
主要加载逻辑就在这三个函数中了:
- 先获取当前的Stage
- 根据当前的Stage获取相应的Generator,
- 执行Generator
一共有三种Generator:
-
ResourceCacheGenerator
:从处理过的缓存加载数据 -
DataCacheGenerator
:从原始缓存加载数据 -
SourceGenerator
:从数据源请求数据,如网络请求
前面说过,Glide的磁盘缓存可以选择缓存原始图片,缓存处理过的图片(如列表中显示缩略图时缩放后的图片),这三个Generator就分别对应处理过的图片缓存,原始图片缓存,和数据源加载。
在上面的第三步执行Generator时主要就是调用了Generator,其实就是执行Generator的startNext
方法,这里以SourceGenerator
为例。
public boolean startNext() {
if (dataToCache != null) {
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;
}
先忽略函数开始时dataToCache
和sourceCacheGenerator
相关的代码,第一次加载时这两个一定是null的。剩下的流程就是获取一个LoadData
,调用LoadData.fetcher.loadData
加载数据。看一下LoadData
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
int size = modelLoaders.size();
for (int i = 0; i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}
在getLoadData
中通过获取所有提前注册过的能处理Model
类型的ModelLoader
,调用它的buildLoadData
生成LoadData
,最终返回一个LoadData
列表。
前面说过LoadData
包含了用来获取数据的DataFetcher
。SourceGenerator.startNext
就调用了loadData.fetcher.loadData
来进行加载数据,并传进去一个Callback,就是当前的SourceGenerator
,如果加载成功,会调用onDataReady
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
数据加载成功后,如果设置了要进行磁盘缓存,会设置成员变量dataToCache
,并调用Callback的reschedule
,结果就是会再次调用当前Generator的startNext
,startNext
的前半部分实现就起作用了,会进行写缓存的操作。
当rescheudle
后写了缓存后,或不缓存的情况下,会调用onDataFetcherReady
,这个Callback就是前面的DecodeJob
,在onDataFetcherReady
中会调用decodeFromRetrievedData
decode数据,最终调用到decodeFromFetcher
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
return runLoadPath(data, dataSource, path);
}
获取LoadPath
,并调用它的load
方法。LoadPath
就是封装了多个DecodePath
,DecodePath
用于decode and Transform数据,如InputStream->Bitmap->BitmapDrawable,DecodePath
中会获取预先注册的Decoder
来decode获取到的数据,decode成功后通过回调调用DecodeJob
的onResourceDecoded
方法。
public Resource<Z> onResourceDecoded(Resource<Z> decoded) {
Class<Z> resourceSubClass = getResourceClass(decoded);
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
transformed = appliedTransformation.transform(decoded, width, height); ////////////////////////// 1
}
// TODO: Make this the responsibility of the Transformation.
if (!decoded.equals(transformed)) {
decoded.recycle();
}
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
encoder = decodeHelper.getResultEncoder(transformed);
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource<Z> result = transformed;
boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
encodeStrategy)) {
if (encoder == null) {
throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
}
final Key key;
if (encodeStrategy == EncodeStrategy.SOURCE) {
key = new DataCacheKey(currentSourceKey, signature);
} else if (encodeStrategy == EncodeStrategy.TRANSFORMED) {
key = new ResourceCacheKey(currentSourceKey, signature, width, height,
appliedTransformation, resourceSubClass, options);
} else {
throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
}
LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
deferredEncodeManager.init(key, encoder, lockedResult); ////////////////////////// 2
result = lockedResult;
}
return result;
}
在上述代码的注释1处对加载成功的资源应用Transformation,然后在注释2处根据缓存策略初始化DeferredEncodeManager
,在前面的decodeFromRetrievedData
中,如果有必要会把transform过的资源写缓存。
private void decodeFromRetrievedData() {
...
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
notifyEncodeAndRelease
中处理了对处理过的图片的缓存操作。当缓存完成后(如果有需要的话)就通过回调告诉外面加载完成了。至此,整个加载过程完成。
Glide配置
Glide允许我们进行一定程度的自定义,比如设置自定义的Executor,设置缓存池,设置Log等级等,完成这个任务的类叫GlideBuilder
,Glide类在工程中是作为单例使用的,看一下代码:
public static Glide get(Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
glide = builder.createGlide();
for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide.registry);
}
}
}
}
return glide;
}
通过GlideBuilder
生成了一个Glide
实例,我们是没有办法直接配置GlideBuilder
的,但我们发现Glide.get
解析了Manifest,获取了一个GlideModule
的列表,并调用了它的applyOptions
和registerComponents
方法。以项目中OkHttp的配置为例
public class OkHttpGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// Do nothing.
}
@Override
public void registerComponents(Context context, Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
GlideModule
有两个方法,applyOptions
,有一个GlideBuilder
参数,在这里我们就可以配置Glide了。还有一个registerComponents
方法,并有一个Registry
参数,通过这个类的实例我们就可以注册我们自定义的ModelLoader
,Encoder
等基础组件了。
自定义GlideModule
是通过Manifest的meta-data标签配置的
<meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule"/>
参考资料
http://www.lightskystreet.com/2015/10/12/glide_source_analysis/