Glide
MoMo Lv5

基本执行流程

1
Glide.with(context).load("xxx").into(imageView);

执行上面代码会执行以下流程

image

各个模块介绍

生命周期模块

生命周期模块UML图

image

各个部分介绍

  1. LifecycleListener 生命周期接口类,当Gldie所依附的活动生命周期发生改变时,就会回调实现 LifecycleListener 接口类中的onStart(),onStop(),onDestroy()。根据UML可知,RequestManager实现了 LifecycleListener,所以RequestManager可以根据生命周期变化来控制请求的暂停,恢复以及取消。
  2. Lifecycle 管理生命周期接口类 即管理实现了 LifecycleListener 接口的类,也就是添加和移除生命周期观察。
  3. ActivityFragmentLifecycle Lifecycle的实现类,用来统一分发生命周期,通过调用 addListener(LifecycleListener listener)将实现 LifecycleListener的实例添加到集合中,通过removeListener(LifecycleListener listener)从集合中移除,通过内部定义的onStart(),onStop(),onDestory() 遍历集合来统一分发生命周期。
  4. SupportRequestManagerFragment 生命周期发射源,即当Glide在 FragmentActivityandroidx.fragment.app.Fragment(如果是androidx以前则是supportv4 包下的Fragment) 子类中加载时,会为当前活动添加一个透明的Fragment,该Fragment就是 SupportRequestManagerFragment;目的是为了感知生命周期变化,用来暂停,恢复以及取消请求。
  5. Request 请求类的顶层接口,其中定义了请求开始,暂停,取消等操作。
  6. RequestTracker 用于跟踪、取消和重新启动的请求以及正在进行、已完成和失败的请求的类。
  7. Target 所有不同方式的加载最终都通过Target#onResourceReady(@NonNull R resource, @Nullable Transition transition),进行回调成功结果,而不同的Target有不同展示资源的方式,比如加载bitmap和加载drawable,展现方式就不同即通过setImageBitmap(resource)和setImageDrawable(resource),加载失败回调也是同理;由此可知Target设计是为了将 不同资源展示到不同目标或者相同目标的一个接口设计。 其中Target的生命周期事件如下:
  • onLoadStarted 开始加载时调用
  • onResourceReady 资源加载成功时调用
  • onLoadCleared 在取消加载并释放其资源时调用
  • onLoadFailed 资源加载失败时调用

典型的生命周期是onLoadStarted ->onResourceReady或onLoadFailed ->onLoadCleared;然而,这并不能保证。如果资源在内存中,则不能调用onLoadStarted如果由于模型对象为空而导致加载失败。类似地,onLoadCleared也可能永远不会被调用。

  1. RequestManager 从UML图上可知,RequestManager聚合了LifeCycle、RequestTracker、TargetTracker,以及定义了load(xxx)一些列方法,asBitmap(),asDrawable()等,说明RequestManager它的定义是为了管理Target以及Request,以及在生命周期变化时对Target和Request可以统一的进行通知,更重要的是它是调用层获取一系列api的入口即GlideBuilder。

2.1.3 生命周期模块总结

由于整个生命周期模块代码比较简单,所以这里不会对代码分析,只是说明下与该模块相关的各个类发挥的作用。整个生命周期模块其实就是从Glide.with(context)加载完毕后会创建一个透明的Fragment用来观察活动的生命周期变化,通过ActivityFragmentLifecycle将生命周期变化分发给RequestManager,而RequestManager则可以在不同的生命周期回调方法内对请求智能地停止、和重新请求。

2.2 请求初始化模块

2.2.1 请求初始化模块代码分析

请求初始化是从into函数的调用开始,第一个参数target,结合文章开头加载图片方式这里的target为 DrawableImageViewTarget,targetListener这里没设置则为null,options就是调用当前into方法的实例对象,即 RequestBuilder(RequestBuilder是BaseRequestOptions子类),callbackExecutor主线程执行器(封装了handler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private extends Target> Y into(
Y target,
RequestListener targetListener,
BaseRequestOptions options,
Executor callbackExecutor) {

Request request = buildRequest(target, targetListener, options, callbackExecutor);
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;
}

从上面代码分析可知,在执行请求之前会先判断是否可以重用旧的请求,如果可以重用则根据状态判断是否需要重新执行请求;如果不能重用则从target中清除旧请求绑定,然后绑定新的request,最后在requestManager#track(target, request)中执行请求。从这点可以验证前面我们在生命周期模块中描述RequestManager职责时说 RequestManager 它的定义是为了管理Target以及Request。

requestManager.track(target, request) 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
synchronized void track( Target target,  Request request) {

targetTracker.track(target);

requestTracker.runRequest(request);
}

public void runRequest( Request request) {
requests.add(request);
if (!isPaused) {

request.begin();
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}

在分析 into方法实现时已说明request实例为 SingleRequest ,所以接下来看SingleRequest#begin()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void begin() {
synchronized (requestLock) {
if (model == null) {
...

onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
if (status == Status.COMPLETE) {

onResourceReady(resource, DataSource.MEMORY_CACHE);
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());
}
}
}

public void onSizeReady(int width, int height) {
synchronized (requestLock) {
...

engine.load(xxx 省略大量参数);
...

}
}

简单说下Engine是干嘛的,它是负责启动加载以及管理活动缓存与内存缓存

执行流程如下:

  1. 检查当前使用的活动缓存,如果存在,则返回活动资源
  2. 检查内存缓存,如果存在,并提供缓存资源,并将内存缓存资源移动到活动缓存中
  3. 检查当前正在进行的加载,并将加载结果回调添加到正在进行的加载
  4. 开始新的加载。

ps:缓存相关源码在后续缓存模块会进行分析

engine#load()伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  LoadStatus load(...) {
EngineKey key =...

EngineResource memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {

return waitForExistingOrStartNewJob(...);
}
}

cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}

那么按照Engine执行流程,前三个是已存在加载好的资源或者已经存在可利用的请求任务情况,那么这里我们直接看第四步,开始新的加载。

那么这里直接从waitForExistingOrStartNewJob开始分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private  LoadStatus waitForExistingOrStartNewJob(ResourceCallback cb,
boolean onlyRetrieveFromCache,
Executor callbackExecutor,...//省略大量参数) {
EngineJob current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {

current.addCallback(cb, callbackExecutor);
return new LoadStatus(cb, current);
}

EngineJob engineJob =EngineJob();

DecodeJob decodeJob =new DecodeJob(DecodeJob.Callback callback)

jobs.put(key, engineJob);

engineJob.addCallback(cb, callbackExecutor);

engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}

从上面代码分析可知,EngineJob它是任务执行完成回调到Target的桥梁,同时它也负责执行DecodeJob,DecodeJob是具体的任务,以及任务执行完后进行解码工作。在创建DecodeJob时设置了 DecodeJob.Callback 监听, EngineJob实现了 DecodeJob.Callback,也就是请求解码完成后的结果回调给 EngineJob,而创建完EngineJob后为它设置了ResourceCallback监听以及主线程执行器,也就是说从解码完成后回调给EngineJob,通过设置给EngineJob的主线程执行器,将结果在主线程中回调给ResourceCallback,这样结果从EngineJob回调到SingleRequest,从SingleRequest再回调到Target,而这里的Target为 DrawableImageViewTarget,在 DrawableImageViewTarget回调结果中会对ImageView进行setImageDrawable(resource)。

2.2.3 请求初始化总结

总结一下请求初始化模块所干的事情,从调用into函数开始,创建对应的Target与Request,创建完后将Target与Request进行双向绑定,然后开始执行请求,如果指定了加载的图片宽高,则开始加载;否则对ImageView进行监听,等获取到真实大小后再开始加载。加载流程如下:

  1. 检查当前使用的活动缓存,如果存在,则返回活动资源
  2. 检查内存缓存,如果存在,并提供缓存资源,并将内存缓存资源移动到活动缓存中
  3. 检查当前正在进行的加载,并将加载结果回调添加到正在进行的加载
  4. 开始新的加载。

其中第1,2,3步骤属于内存缓存的资源获取和对正在加载的请求进行监听;第4步通过EngineJob来执行DecodeJob,等执行完后回调给EngineJob,再从EngineJob->SingleRequest->DrawableImageViewTarget->setImageDrawable(resource)。

2.3 数据请求模块

数据请求模块分为从磁盘缓存中请求和网络中请求。磁盘中没有,则从网络中获取。不管哪种请求方式,最终输出都是一样,那么针对 不同的请求方式的不同或相同参数输入,如何转换为相同的输出,这就是Glide需要对数据的输入与输出模型进行更抽象化的设计,即组件化设计。

2.3.1 Glide组件化设计

在创建Glide时会创建Registry对象,并将具体的输入->输出 模型注册到Registry相应的模块中。 Registry中按功能模块分为:

  • ModelLoaderRegistry :数据加载模块
  • EncoderRegistry:编码存储模块,提供将数据持久化存储到磁盘文件中的功能
  • ResourceDecoderRegistry:解码模块,能够将各种类型数据,例如文件、byte数组等数据解码成bitmap或者drawable等资源
  • ResourceEncoderRegistry:编码存储模块,提供将bitmap或者drawable等资源文件进行持久化存储的功能
  • DataRewinderRegistry :数据流重定向模块,例如重定向ByteBuffer中的position或者stream中的指针位置等
  • TranscoderRegistry: 转码模块,提供将不同资源类型进行转码能力,例如将bitmap转成drawable等
  • ImageHeaderParserRegistry 图片头解析模块
1
2
3
4
5
6
7
Glide(...) {
registry = new Registry();

registry.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
glideContext = new GlideContext(registry,...);
}

那么这里与我们数据请求模块息息相关的模块有ModelLoaderRegistry,EncoderRegistry,ResourceDecoderRegistry,TranscoderRegistry,即获取数据和对数据进行编码缓存到磁盘上然后将编码后的产物解码,解码后进行转码最后进行展示。

整个Glide组件化设计思想都相同,这里仅以ModelLoaderRegistry和EncoderRegistry进行分析。

  • ModelLoaderRegistry 分析

当调用registry.append时会代理给ModelLoaderRegistry,而ModelLoaderRegistry又代理给MultiModelLoaderFactory,所以最终append的数据在MultiModelLoaderFactory中存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
synchronized  void append(
Class modelClass,
Class dataClass,
ModelLoaderFactory factory) {
add(modelClass, dataClass, factory, true);
}
private void add(
Class modelClass,
Class dataClass,
ModelLoaderFactory factory,
boolean append) {
Entry entry = new Entry<>(modelClass, dataClass, factory);
entries.add(append ? entries.size() : 0, entry);
}

将数据封装到Entry中并保存到entries集合中,那么数据获取最终也就是从entries获取。

往Entry中封装的是什么数据?modelClass是什么,dataClass又是什么?还有factory?他们有什么用?为什么要保存它们?

这就要说到Glide在数据加载时进行抽象化的概念,

当Gldie被创建时会往ModelLoaderRegistry中添加多组数据,比如从网络中获取,从本地路径中获取,以及从assets输入流中获取,不同的输入,有不同的结果与不同的处理方式,所以这里需要告诉Glide输入的是什么类型,输出的是什么类型,以及处理方式是什么,当Glide进行加载时会对输入,输出类型匹配,来选择不同的处理方式,比如这里以网络获取资源举例:

1
registry.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())

其中GlideUrl是对网络url的包装,那么输入就是GlideUrl,输出为InputStream,处理方式是HttpGlideUrlLoader。

那么理清组件化概念后,我们详细看下数据加载的类图关系即ModelLoader以及与它相关的类关系。

image

点击查看原图

这里仅仅是以HttpGlideUrlLoader举例,不同的ModelLoader创建方式都是通过ModelLoaderFactory#build创建,创建完后通过ModelLoader#buildLoadData创建包含具体加载方式的LoadData对象,再通过LoadData中DataFetcher对数据进行加载将加载结果通过DataCallback回调给调用层。

所以到这里ModelLoaderRegistry数据加载模块分析完毕,通过多态机制,定义抽象模型,存储具体类型,在运行时根据输入,输出类型查找具体的处理模型,来完成请求到响应的过程。

  • EncoderRegistry 分析
1
2
3
registry
.append(ByteBuffer.class, new ByteBufferEncoder())
.append(InputStream.class, new StreamEncoder(arrayPool))

在Gldie创建时会注册两个编码组件,分别针对ByteBuffer和InputStream类型的输入进行编码缓存到磁盘上。

UML图如下:

image

点击查看原图

最终append的数据被保存到EncoderRegistry#encoders 集合中,获取也是遍历encoders根据输入类型选择对应的编码器,然后将输入数据写入到文件中。

2.3.2 数据请求模块源码分析

在数据加载模块中我们分析到EngineJob启动DecodeJob,DecodeJob实现了Runnable,EngineJob封装了线程池执行的方法,所以调用engineJob.start(decodeJob)时,就是往线程池提交了一个任务,DecodeJob中run()被调用。

整个run方法执行逻辑完全可以使用下面伪代码体现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void run() {
try {
if (isCancelled) {
GlideException e = new GlideException("Failed to load resource", new ArrayList<>(throwables));
callback.onLoadFailed(e);
return;
}

Object data = findResourceCache();
if(data==null){

data = findDataCache();
if(data==null){

Object sourceData = requestData();

if(canCache){
DataCacheGenerator.saveCache(sourceData);
data = findDataCache();
}else{
data = sourceData;
}
}
}

decodeFromRetrievedData(data);
} catch (Throwable t) {
callback.onLoadFailed(e);
throw t;
}
}

无论从哪里获取资源,都是从ModelLoaderRegistry 模块中根据输入和输出类型匹配相应的ModelLoader,通过ModelLoader#buildLoadData创建包含具体加载方式的LoadData对象,再通过LoadData中DataFetcher对数据进行加载将加载结果通过DataCallback回调给调用层。

整个数据请求流程用文字描述如下:

在默认的缓存策略下,即diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)

  1. 从解码变换后的磁盘缓存中获取,如果存在则返回进行解码
  2. 从源文件磁盘缓存中获取,如果存在则返回进行解码
  3. 从源头获取,获取到将数据缓存到源文件缓存中,并返回进行解码

2.4 解码与转码模块

在分析之前先要了解Glide中解码解的是什么?转码转的是什么?

在Glide创建时会注册一些解码组件和转码组件到ResourceDecoderRegistry和TranscoderRegistry中

Q1:解码解的是什么?解码后是什么?

具体解码器描述ByteBufferGifDecoder将ByteBuffer解码为GifDrawableByteBufferBitmapDecoder将ByteBuffer解码为BitmapResourceDrawableDecoder将资源Uri解码为DrawableResourceBitmapDecoder将资源ID解码为BitmapBitmapDrawableDecoder将数据类型解码为BitmapDrawableStreamBitmapDecoder将InputStreams解码为BitmapStreamGifDecoder将InputStream数据转换为BtyeBufferr,再解码为GifDrawableGifFrameResourceDecoder解码gif帧FileDecoder包装File成为FileResourceUnitDrawableDecoder将Drawable包装为DrawableResourceUnitBitmapDecoder包装Bitmap成为BitmapResourceVideoDecoder将本地视频文件解码为BitmapInputStreamBitmapImageDecoderResourceDecoder将InputStream解码为Bitmap(api 28)

Q2:转码转的是什么?转码后是什么?

具体转码器描述BitmapDrawableTranscoder将Bitmap转码为BitmapDrawableBitmapBytesTranscoder将Bitmap转码为Byte arraysDrawableBytesTranscoder将BitmapDrawable转码为Byte arraysGifDrawableBytesTranscoder将GifDrawable转码为Byte arraysUnitTranscoder不转换,输入什么,输出就是什么

刚才我们分析了数据请求的流程,那么最终数据获取成功后回调到DecodeJob#onDataFetcherReady中,那这个模块的分析就从DecodeJob#onDataFetcherReady开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void onDataFetcherReady(
Key sourceKey, Object data, DataFetcher fetcher, DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;
this.currentData = data;
this.currentFetcher = fetcher;
this.currentDataSource = dataSource;
this.currentAttemptingKey = attemptedKey;
...

decodeFromRetrievedData();
}
private void decodeFromRetrievedData() {

Resource resource = decodeFromData(currentFetcher, currentData, currentDataSource);
...

notifyEncodeAndRelease(resource, currentDataSource);
}

private Resource decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {

LoadPath path = decodeHelper.getLoadPath((Class) data.getClass());

return runLoadPath(data, dataSource, path);
}

由上面代码调用链可知,执行LoadPath#load(…)来完成解码工作。

先来看LoadPath的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public  LoadPath getLoadPath(
Class dataClass,
Class resourceClass,
Class transcodeClass) {
...

List> decodePaths =
getDecodePaths(dataClass, resourceClass, transcodeClass);

return new LoadPath<>(dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool)

}
private List> getDecodePaths(
Class dataClass,
Class resourceClass,
Class transcodeClass) {
List> decodePaths = new ArrayList<>();

List> registeredResourceClasses =
decoderRegistry.getResourceClasses(dataClass, resourceClass);

for (Class registeredResourceClass : registeredResourceClasses) {

List> registeredTranscodeClasses =
transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);

for (Class registeredTranscodeClass : registeredTranscodeClasses) {

List> decoders =
decoderRegistry.getDecoders(dataClass, registeredResourceClass);

ResourceTranscoder transcoder =
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);

DecodePath path =
new DecodePath<>(
dataClass,
registeredResourceClass,
registeredTranscodeClass,
decoders,
transcoder,
throwableListPool);
decodePaths.add(path);
}
}
return decodePaths;
}

上面两层for循环只是为了在Glide的解码模块和转码模块中筛选出 符合条件且配对的解码器和转码器,筛选出来的产物保存在LoadPath#decodePaths中

那么根据 Glide.with(context).load("xxx").into(imageView);这种方式加载图片,最终会筛选出来三个DecodePath,并将每个DecodePath保存到decodePaths中,每个DecodePath内容如下:

从数据类型到解码类型再到转码类型如下:

数据类型解码类型|解码器转码类型|转码器ByteBufferGifDrawable|ByteBufferGifDecoderDrawable|UnitTranscoderByteBufferBitmap|ByteBufferBitmapDecoderDrawable|BitmapDrawableTranscoderByteBufferBitmapDrawable|BitmapDrawableDecoderDrawable|UnitTranscoder

既然筛选出来符合条件的数据类型,解码类型|解码器,转码类型|转码器了,那么接下来就是应用了,也就是先将数据解码,再将解码后的产物转码,那么这里就是调用LoadPath#runLoadPath来应用筛选的结果了。

LoadPath#runLoadPath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private  Resource runLoadPath(
Data data, DataSource dataSource, LoadPath path)
throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);

DataRewinder rewinder = glideContext.getRegistry().getRewinder(data);
try {

return path.load(
rewinder, options, width, height, new DecodeCallback(dataSource));
} finally {
rewinder.cleanup();
}
}

private Resource loadWithExceptionList(
DataRewinder rewinder,
Options options,
int width,
int height,
DecodePath.DecodeCallback decodeCallback,
List exceptions)
throws GlideException {
Resource result = null;

for (int i = 0, size = decodePaths.size(); i < size; i++) {
DecodePath path = decodePaths.get(i);
try {
result = path.decode(rewinder, width, height, options, decodeCallback);
} catch (GlideException e) {
exceptions.add(e);
}
if (result != null) {
break;
}
}
if (result == null) {
throw new GlideException(failureMessage, new ArrayList<>(exceptions));
}
return result;
}

public Resource decode(
DataRewinder rewinder,
int width,
int height,
Options options,
DecodeCallback callback)
throws GlideException {

Resource decoded = decodeResource(rewinder, width, height, options);

Resource transformed = callback.onResourceDecoded(decoded);

return transcoder.transcode(transformed, options);
}

等转码完成,最终通过一层一层返回将转码后的资源回调到DrawableImageViewTarget。

DecodeJob->EngineJob->SingleRequest->DrawableImageViewTarget。

2.5 缓存模块

Glide 缓存分为两类,一种是内存缓存,一种是磁盘缓存。在数据请求模块我们已经分析,数据的请求先从解码变换后的磁盘缓存中获取,没获取到再从源文件磁盘缓存中获取,如果还没获取到再从源头获取。那本小节我们只分析内存缓存,内存缓存分析完后,那么整个缓存模块也就一目了然,最后通过一个缓存查找的流程描述整个Glide缓存机制。

内存缓存:

前面我们在分析请求初始化模块时说到当engine#load时先从内存缓存中查找,找不到再出发数据请求,那么这里就从engine#load开始分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public  LoadStatus load(...) {
EngineKey key =...

EngineResource memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {

return waitForExistingOrStartNewJob(...);
}
}

cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}
private EngineResource loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}

EngineResource active = loadFromActiveResources(key);
if (active != null) {
return active;
}

EngineResource cached = loadFromCache(key);
if (cached != null) {
return cached;
}
return null;
}

整个内存缓存分为两个阶段

  1. 活动缓存:Map结构的弱引用存储
  2. 内存缓存:Lru结构的存储

活动缓存是什么?

活动缓存顾名思义就是缓存当前页面上的资源,当前页面销毁后活动缓存中的资源会被移除添加到内存缓存中,它存在的意义就是尽可能的减少列表中查找资源从磁盘缓存中查找。因为内存缓存大小有限制,使用的算法为Lru算法,旧的被淘汰,如果在一个列表中,多次来回滑动,就会造成滑出屏幕的被移除,如果此时再滑进屏幕,就需要从新去磁盘中查找。那么活动缓存这里使用Map结构的弱引用存储,防止了当前列表中旧的资源被回收,除非触发垃圾回收。减少了从磁盘缓存中查找的操作,提高了资源查找的性能。

内存缓存是什么?

内存缓存使用的是Lru结构的存储资源,那根据Lru特性,最近最少使用算法。即当缓存满时,移除最近没有使用的资源。从而避免缓存无限制的增长,从而产生OOM。

解释完活动和内存缓存的定义后那我们来看下代码实现,先从活动缓存分析

活动缓存中Map存储的是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
final class ActiveResources {
private final Executor monitorClearedResourcesExecutor;
final Map activeEngineResources = new HashMap<>();
private final ReferenceQueue> resourceReferenceQueue = new ReferenceQueue<>();
private volatile boolean isShutdown;
...

ActiveResources(...,Executor monitorClearedResourcesExecutor) {
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {

public void run() {
cleanReferenceQueue();
}
});
}
}

synchronized void activate(Key key, EngineResource resource) {

ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}

static final class ResourceWeakReference extends WeakReference> {
final Key key;
final boolean isCacheable;
Resource resource;
ResourceWeakReference(
Key key,
EngineResource referent,
ReferenceQueuesuper EngineResource> queue,
boolean isActiveResourceRetentionAllowed) {
super(referent, queue);
this.key = Preconditions.checkNotNull(key);
this.resource =
referent.isMemoryCacheable() && isActiveResourceRetentionAllowed
? Preconditions.checkNotNull(referent.getResource())
: null;
isCacheable = referent.isMemoryCacheable();
}
void reset() {
resource = null;
clear();
}
}

由上面代码片段可知,它存储的是EngineResource弱引用包装类ResourceWeakReference,为什么要这样设计?为什么不直接存储EngineResource?这就要说到java引用关系,如果Map中直接存储EngineResource那就是强引用,除非主动clear(),不然垃圾回收时不会回收,所以这里采用弱引用。还有一件更有意思的在创建ResourceWeakReference时,也就是往Map中put时即调用ActiveResources#activate()时,传递了一个引用队列,这个引用队列的存在就是为了检测什么时候回收。

在ActiveResources构造方法中启动了一个检测线程,这个线程中调用了cleanReferenceQueue(),在cleanReferenceQueue()中会不停的循环,除非手动将isShutdown改为true,而在循环中resourceReferenceQueue#remove是阻塞式的,即没有元素时阻塞,有元素时会返回,那什么时候有元素呢?就是垃圾回收时,将回收的元素放入到ReferenceQueue中,此时remove返回回收的元素,然后将元素从活动缓存中移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void cleanReferenceQueue() {
while (!isShutdown) {
try {
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();

cleanupActiveReference(ref);
DequeuedResourceCallback current = cb;
if (current != null) {
current.onResourceDequeued();
}

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

那么再分析完活动缓存结构后,我们再看下从活动缓存中查找缓存逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private EngineResource loadFromActiveResources(Key key) {

EngineResource active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}

synchronized EngineResource get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource active = activeRef.get();

if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}

如果活动缓存中未查找到,则从内存缓存中查找

1
2
3
4
5
6
7
8
9
private EngineResource loadFromCache(Key key) {

EngineResource cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}

那么整个内存缓存就分析完了,总结下,如果Glide中缓存策略使用默认的缓存策略,那么它获取资源的流程如下:

  1. 从活动缓存中查找,如果存在返回
  2. 从内存缓存中查找,如果存在放到活动缓存中,并返回
  3. 从解码变换后的磁盘缓存中查找,如果存在解码转码返回
  4. 从源文件磁盘缓存中查找,如果存在解码-变换-转码返回
  5. 从源头获取,成功后将资源缓存到源文件缓存中,解码-变换-转码返回

3 Glide解码详细分析

3.1 Bitmap基础知识

3.1.1 Bitmap内存占用计算

计算一个bitmap占用的内存有两种方式

  • getByteCount()
  • getAllocationByteCount()

两者区别如下:

  1. 两者一般情况下相等
  2. 通过复用Bitmap来解码图片,如果被复用的Bitmap的内存比待分配内存的Bitmap大,那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),getAllocationByteCount()表示被复用Bitmap真实占用的内存大小。
  3. Android4.4(API 19)之后,通过复用解码的Bitmap,getByteCount()总是小于或等于getAllocationByteCount()

从res下不同drawable解码一张图片,占用内存计算

width×height×targetDensity÷inDensity×targetDensity÷inDensity×一个像素所占的内存width \times height\times targetDensity \div inDensity \times targetDensity \div inDensity \times 一个像素所占的内存w i d t h ×h e i g h t ×t a r g e t De n s i t y ÷in De n s i t y ×t a r g e t De n s i t y ÷in De n s i t y ×一个像素所占的内存

  • targetDensity 手机像素密度
  • inDensity 不同drawable下的像素密度
  • 一个像素所占的内存
    1. ALPHA_8 – (1B)
    2. RGB_565 – (2B)
    3. ARGB_4444 – (2B)
    4. ARGB_8888 – (4B)
    5. RGBA_F16 – (8B)

比如在一个像素密度为480手机上加载一个100*100的图片,如果使用默认编码即ARGB_8888

  • 当图片放在xxhdpi下,解码出来的图片占用内存为 100×100×480÷480×480÷480×4=40000B≈39K100\times100\times480\div480\times480\div480\times4=40000B\approx39K 100 ×100 ×480 ÷480 ×480 ÷480 ×4 =40000 B ≈39 K
  • 当图片放在xhdpi下,解码出来的图片占用内存为 100×100×480÷320×480÷320×4=90000B≈88K100\times100\times480\div320\times480\div320\times4=90000B\approx88K 100 ×100 ×480 ÷320 ×480 ÷320 ×4 =90000 B ≈88 K

其他 BitmapFactory.decodeXxx占用的内存为:

width×height×一个像素所占的内存width \times height \times 一个像素所占的内存w i d t h ×h e i g h t ×一个像素所占的内存

3.1.2 Bitmap内存模型

Android 2.3.3(API10)之前,Bitmap的像素数据存放在Native内存,而Bitmap对象本身则存放在Dalvik Heap中。而在Android3.0之后,Bitmap的像素数据也被放在了Dalvik Heap中。而在Android 8.0 之后,Bitmap的像素数据又被放到 Native 内存中。

3.1.3 Bitmap内存管理

Android 2.3.3及更低版本上,建议使用recycle()

只有当您确定位图已不再使用时才应该使用 recycle()。如果您调用 recycle() 并在稍后尝试绘制位图,则会收到错误: "Canvas: trying to use a recycled bitmap"

Android 3.0及更高版本上管理内存

Android3.0(API 11之后)引入了BitmapFactory.Options.inBitmap字段,设置此字段之后解码方法会尝试复用inBitmap字段设置的Bitmap内存。这意味着Bitmap的内存被复用,避免了内存的回收及申请过程,显然性能表现更佳。不过,使用这个字段有几点限制:

inBitmap的限制

  • 声明可被复用的Bitmap必须设置inMutable为true;
  • Android4.4(API 19)之前只有格式为jpg、png,同等宽高(要求苛刻),inSampleSize为1的Bitmap才可以复用
  • Android4.4(API 19)之前被复用的Bitmap的inPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig
  • Android4.4(API 19)之后被复用的Bitmap的内存必须大于需要申请内存的Bitmap的内存
  • Android4.4(API 19)之前待加载Bitmap的Options.inSampleSize必须明确指定为1

3.1.4 Bitmap基础知识参考资料

www.jianshu.com/p/3c5ac5fdb…

www.jianshu.com/p/e49ec7d05…

3.2 解码详细分析

无论是加载本地图片,还是网络图片,基本都由Downsampler中5个参数的decode()完成,那么接下来会详细分析decode中的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
private Resource decode(
ImageReader imageReader,//用来获取图片类型,以及方向;以及bitmap解码
int requestedWidth,//view宽
int requestedHeight,//view高
Options options,
DecodeCallbacks callbacks)
throws IOException {
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
bitmapFactoryOptions.inTempStorage = bytesForOptions;

DecodeFormat decodeFormat = options.get(DECODE_FORMAT);

PreferredColorSpace preferredColorSpace = options.get(PREFERRED_COLOR_SPACE);

DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);

boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);

boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

try {

Bitmap result =
decodeFromWrappedStreams(
imageReader,
bitmapFactoryOptions,
downsampleStrategy,
decodeFormat,
preferredColorSpace,
isHardwareConfigAllowed,
requestedWidth,
requestedHeight,
fixBitmapToRequestedDimensions,
callbacks);
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}

private Bitmap decodeFromWrappedStreams(
ImageReader imageReader,
BitmapFactory.Options options,
DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat,
PreferredColorSpace preferredColorSpace,
boolean isHardwareConfigAllowed,
int requestedWidth,
int requestedHeight,
boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks)
throws IOException {

int[] sourceDimensions = getDimensions(imageReader, options, callbacks, bitmapPool);

int sourceWidth = sourceDimensions[0];

int sourceHeight = sourceDimensions[1];

String sourceMimeType = options.outMimeType;

...

int orientation = imageReader.getImageOrientation();

int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);

int targetWidth =
requestedWidth == Target.SIZE_ORIGINAL
? (isRotationRequired(degreesToRotate) ? sourceHeight : sourceWidth)
: requestedWidth;
int targetHeight =
requestedHeight == Target.SIZE_ORIGINAL
? (isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight)
: requestedHeight;

ImageType imageType = imageReader.getImageType();

calculateScaling(
imageType,
imageReader,
callbacks,
bitmapPool,
downsampleStrategy,
degreesToRotate,
sourceWidth,
sourceHeight,
targetWidth,
targetHeight,
options);

calculateConfig(
imageReader,
decodeFormat,
isHardwareConfigAllowed,
isExifOrientationRequired,
options,
targetWidth,
targetHeight);

boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
int expectedWidth;
int expectedHeight;
if (sourceWidth >= 0
&& sourceHeight >= 0
&& fixBitmapToRequestedDimensions
&& isKitKatOrGreater) {
expectedWidth = targetWidth;
expectedHeight = targetHeight;
} else {

float densityMultiplier =
isScaling(options) ? (float) options.inTargetDensity / options.inDensity : 1f;
int sampleSize = options.inSampleSize;

int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);

int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);

expectedWidth = Math.round(downsampledWidth * densityMultiplier);

expectedHeight = Math.round(downsampledHeight * densityMultiplier);
}

if (expectedWidth > 0 && expectedHeight > 0) {

setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
boolean isP3Eligible =
preferredColorSpace == PreferredColorSpace.DISPLAY_P3
&& options.outColorSpace != null
&& options.outColorSpace.isWideGamut();
options.inPreferredColorSpace =
ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}

Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool);

callbacks.onDecodeComplete(bitmapPool, downsampled);
...

return rotated;
}

calculateScaling 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
private static void calculateScaling(
ImageType imageType,
ImageReader imageReader,
DecodeCallbacks decodeCallbacks,
BitmapPool bitmapPool,
DownsampleStrategy downsampleStrategy,
int degreesToRotate,
int sourceWidth,
int sourceHeight,
int targetWidth,
int targetHeight,
BitmapFactory.Options options)
throws IOException {

int orientedSourceWidth = sourceWidth;
int orientedSourceHeight = sourceHeight;

final float exactScaleFactor =
downsampleStrategy.getScaleFactor(
orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight);

SampleSizeRounding rounding =
downsampleStrategy.getSampleSizeRounding(
orientedSourceWidth, orientedSourceHeight, targetWidth, targetHeight);

int outWidth = round(exactScaleFactor * orientedSourceWidth);

int outHeight = round(exactScaleFactor * orientedSourceHeight);

int widthScaleFactor = orientedSourceWidth / outWidth;

int heightScaleFactor = orientedSourceHeight / outHeight;

int scaleFactor =
rounding == SampleSizeRounding.MEMORY
? Math.max(widthScaleFactor, heightScaleFactor)
: Math.min(widthScaleFactor, heightScaleFactor);

int powerOfTwoSampleSize;

if (Build.VERSION.SDK_INT 23
&& NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
powerOfTwoSampleSize = 1;
} else {

powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
if (rounding == SampleSizeRounding.MEMORY
&& powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
powerOfTwoSampleSize = powerOfTwoSampleSize << 1;
}
}

options.inSampleSize = powerOfTwoSampleSize;
int powerOfTwoWidth;
int powerOfTwoHeight;

if (imageType == ImageType.JPEG) {

int nativeScaling = Math.min(powerOfTwoSampleSize, 8);

powerOfTwoWidth = (int) Math.ceil(orientedSourceWidth / (float) nativeScaling);

powerOfTwoHeight = (int) Math.ceil(orientedSourceHeight / (float) nativeScaling);

int secondaryScaling = powerOfTwoSampleSize / 8;
if (secondaryScaling > 0) {
powerOfTwoWidth = powerOfTwoWidth / secondaryScaling;
powerOfTwoHeight = powerOfTwoHeight / secondaryScaling;
}
} else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {
powerOfTwoWidth = (int) Math.floor(orientedSourceWidth / (float) powerOfTwoSampleSize);
powerOfTwoHeight = (int) Math.floor(orientedSourceHeight / (float) powerOfTwoSampleSize);
}
...

double adjustedScaleFactor =
downsampleStrategy.getScaleFactor(
powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);

options.inDensity = getDensityMultiplier(adjustedScaleFactor);
}
if (isScaling(options)) {
options.inScaled = true;
} else {

options.inDensity = options.inTargetDensity = 0;
}
}

绕了一大圈,主要还是使用系统提供的inSampleSize来改变原图的尺寸以减小bitmap加载到内存的大小。

那么分析完Glide解码代码后,个人认为Glide对图片解码做了以下优化

  1. inSampleSize来改变原图的尺寸以减小bitmap加载到内存的大小
  2. 合理的inPreferredConfig设置。 根据外部设置的inPreferredConfig,即(builder.setDecodeFormat(DecodeFormat.PREFER_RGB_565))合理判断是否使用设置的值,并不是你设置什么值,Glide就去解码什么格式。举例,比如设置RGB_565,去加载一张带透明度的图片,Glide会为你自动修改为Bitmap.Config.ARGB_8888。至于为什么请看 inPreferredConfig说明:blog.csdn.net/ccpat/artic…
  3. 通过inBitmap来复用已存在不使用的Bitmap,减少内存的申请与释放
  4. 通过ArrayPool来复用字节数组用于图片从InputStream中解码时的临时空间,用于减少内存的申请与释放
  5. 根据版本判断(Android8.0以上)以及是否启用硬件位图 来决定是否使用硬件位图,注意启用硬件位图,意味着bitmap不能复用。 muyangmin.github.io/glide-docs-… 是否启动硬件位图代码判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Options getOptionsWithHardwareConfig(DataSource dataSource) {
Options options = this.options;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return options;
}

boolean isHardwareConfigSafe =
dataSource == DataSource.RESOURCE_DISK_CACHE || decodeHelper.isScaleOnlyOrNoTransform();
Boolean isHardwareConfigAllowed = options.get(Downsampler.ALLOW_HARDWARE_CONFIG);

if (isHardwareConfigAllowed != null && (!isHardwareConfigAllowed || isHardwareConfigSafe)) {
return options;
}

options = new Options();
options.putAll(this.options);
options.set(Downsampler.ALLOW_HARDWARE_CONFIG, isHardwareConfigSafe);

return options;
}

4 transform(变换)流程分析

在前面分析Glide解码与转码模块时,当资源被解码后通过 callback.onResourceDecoded(decoded);进行回调,最终回调到 DecodeJob#onResourceDecoded来完成 transform。那这里就详细分析下 transform过程。

资源解码后进行回调代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Resource decode(
DataRewinder rewinder,
int width,
int height,
Options options,
DecodeCallback callback)
throws GlideException {

Resource decoded = decodeResource(rewinder, width, height, options);

Resource transformed = callback.onResourceDecoded(decoded);

return transcoder.transcode(transformed, options);
}

DecodeJob#onResourceDecoded代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Resource onResourceDecoded(DataSource dataSource,  Resource decoded) {
Class resourceSubClass = (Class) decoded.get().getClass();
Transformation appliedTransformation = null;
Resource transformed = decoded;

if (dataSource != DataSource.RESOURCE_DISK_CACHE) {

appliedTransformation = decodeHelper.getTransformation(resourceSubClass);

transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}

if (!decoded.equals(transformed)) {
decoded.recycle();
}

final EncodeStrategy encodeStrategy;
final ResourceEncoder encoder;

if (decodeHelper.isResourceEncoderAvailable(transformed)) {

encoder = decodeHelper.getResultEncoder(transformed);

encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource 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;
switch (encodeStrategy) {
case SOURCE:
key = new DataCacheKey(currentSourceKey, signature);
break;
case TRANSFORMED:

key =
new ResourceCacheKey(
decodeHelper.getArrayPool(),
currentSourceKey,
signature,
width,
height,
appliedTransformation,
resourceSubClass,
options);
break;
default:
throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
}

LockedResource lockedResult = LockedResource.obtain(transformed);

deferredEncodeManager.init(key, encoder, lockedResult);
result = lockedResult;
}
return result;
}

啰嗦了这么多,重要的就这两行代码完成 变换器的查找和 &#x89E3;&#x7801;&#x540E;&#x7684;&#x8D44;&#x6E90; 变换为 &#x53D8;&#x6362;&#x540E;&#x7684;&#x8D44;&#x6E90;

1
2
3
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);

transformed = appliedTransformation.transform(glideContext, decoded, width, height);

变换器的查找逻辑代码就别贴了,DecodeHelper中一个为 transformations的Map中查找,即根据key,找value的过程,匹配到就返回,那么transformations中存储的这些变换器在哪进行添加的呢?

通过追踪代码调用,是从RequestBuilder中一层一层传递过来的,具体添加过程是当我们调用into时,根据ImageView#scaleType判断需要添加哪些变换操作,ImageView默认的scaleType为FIT_CENTER,所以这里触发了optionalFitCenter(),经过层层调用最终调用 BaseRequestOptions#transform()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
T transform( Transformation transformation, boolean isRequired) {
if (isAutoCloneEnabled) {
return clone().transform(transformation, isRequired);
}

DrawableTransformation drawableTransformation =
new DrawableTransformation(transformation, isRequired);

transform(Bitmap.class, transformation, isRequired);
transform(Drawable.class, drawableTransformation, isRequired);
transform(BitmapDrawable.class, drawableTransformation.asBitmapDrawable(), isRequired);
transform(GifDrawable.class, new GifDrawableTransformation(transformation), isRequired);
return selfOrThrowIfLocked();
}

根据上面代码分析,当调用into时,Glide自动注册了4个transform。

回到刚才查找变换器入口代码

1
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);

假如这里resourceSubClass类型为Bitmap,那么查找到的就是FitCenter,那么变换就是调用FitCenter#transform。

FitCenter继承体系如下

image

如果你只需要变换 Bitmap,最好是从继承 BitmapTransformationBitmapTransformation 为我们处理了一些基础的东西,例如,如果你的变换返回了一个新修改的 Bitmap , BitmapTransformation将负责提取和回收原始的 Bitmap

具体如何自定义Transformation,请参考Glide中文文档:

muyangmin.github.io/glide-docs-…

Powered by Hexo & Theme Keep
Unique Visitor Page View