Ahab's Studio.

Glide 源码分析 - 缩略图使用及原理

字数统计: 926阅读时长: 4 min
2019/02/17 Share

缩略图的使用可参考官方文档

若使用简化方式设置缩略图:

1
Glide.with(context).load(imgUrl).thumbnail(0.2f).into(imageView);

发起缩略图请求的关键逻辑位于 RequestBuilder 的 buildThumbnailRequestRecursive 方法中:

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
// Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);

Request fullRequest =
obtainRequest(
target,
targetListener,
requestOptions,
coordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight);

RequestOptions thumbnailOptions = requestOptions.clone()
.sizeMultiplier(thumbSizeMultiplier);
Request thumbnailRequest =
obtainRequest(
target,
targetListener,
thumbnailOptions,
coordinator,
transitionOptions,
getThumbnailPriority(priority),
overrideWidth,
overrideHeight);

coordinator.setRequests(fullRequest, thumbnailRequest);
return coordinator;

其中 fullRequest、thumbnailRequest 分别为原图、缩略图请求,ThumbnailRequestCoordinator 就是字面意思,专门用于协调原图、缩略图的请求,并合并成一个请求,可以看到在构建缩略图请求时,为了尽量让缩略图比原图加载的更快一点,调用 getThumbnailPriority 方法调整了请求优先级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@NonNull
private Priority getThumbnailPriority(@NonNull Priority current) {
switch (current) {
case LOW:
return Priority.NORMAL;
case NORMAL:
return Priority.HIGH;
case HIGH:
case IMMEDIATE:
return Priority.IMMEDIATE;
default:
throw new IllegalArgumentException("unknown priority: " + requestOptions.getPriority());
}
}

加载完数据后,Glide 会分别解码缩略图、原图两种尺寸的图片,具体解码实现位于 Downsampler 的 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
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
Options options, DecodeCallbacks callbacks) throws IOException {
Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
+ " mark()");

byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
bitmapFactoryOptions.inTempStorage = bytesForOptions;

DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
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(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks);
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}

解码完后会调用到 DecodeJob 的 notifyComplete 方法,进一步调用到 EngineJob 的 onResourceReady 方法,在 onResourceReady 方法中通过 handle 转到主线程,然后调用到 EngineJob 的 handleResultOnMainThread 方法,接着调用 SingleRequest 的 onResourceReady 方法:

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
private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
// We must call isFirstReadyResource before setting status.
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;

if (glideContext.getLogLevel() <= Log.DEBUG) {
Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
+ dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
+ LogTime.getElapsedMillis(startTime) + " ms");
}

isCallingCallbacks = true;
try {
boolean anyListenerHandledUpdatingTarget = false;
if (requestListeners != null) {
for (RequestListener<R> listener : requestListeners) {
anyListenerHandledUpdatingTarget |=
listener.onResourceReady(result, model, target, dataSource, isFirstResource);
}
}
anyListenerHandledUpdatingTarget |=
targetListener != null
&& targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

if (!anyListenerHandledUpdatingTarget) {
Transition<? super R> animation =
animationFactory.build(dataSource, isFirstResource);
target.onResourceRReady(result, animation);
}
} finally {
isCallingCallbacks = false;
}

notifyLoadSuccess();
}

可以看到此方法中终于将加载资源回调给 Target 了,调用栈大致如下:

以上不止是设置缩略图时的加载流程,没有缩略图时走的也是这个流程。我们知道图片缩略图不过是通过 BitmapFactory.Options 解码一张尺寸较小、质量较差的图罢了,而 Glide 除了支持配置图片缩略图,还支持配置本地视频缩略图。取视频缩略图的关键逻辑位于 VideoDecoder 的 decodeFrame 方法:

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
@Nullable
private static Bitmap decodeFrame(
MediaMetadataRetriever mediaMetadataRetriever,
long frameTimeMicros,
int frameOption,
int outWidth,
int outHeight,
DownsampleStrategy strategy) {
Bitmap result = null;
// Arguably we should handle the case where just width or just height is set to
// Target.SIZE_ORIGINAL. Up to and including OMR1, MediaMetadataRetriever defaults to setting
// the dimensions to the display width and height if they aren't specified (ie
// getScaledFrameAtTime is not used). Given that this is an optimization only if
// Target.SIZE_ORIGINAL is not used and not using getScaledFrameAtTime ever would match the
// behavior of Glide in all versions of Android prior to OMR1, it's probably fine for now.
if (Build.VERSION.SDK_INT >= VERSION_CODES.O_MR1
&& outWidth != Target.SIZE_ORIGINAL
&& outHeight != Target.SIZE_ORIGINAL
&& strategy != DownsampleStrategy.NONE) {
result =
decodeScaledFrame(
mediaMetadataRetriever, frameTimeMicros, frameOption, outWidth, outHeight, strategy);
}

if (result == null) {
result = decodeOriginalFrame(mediaMetadataRetriever, frameTimeMicros, frameOption);
}

return result;
}

可以看到在 Android 8.1 系统上支持直接获取缩放的视频缩略图,8.1 以下则直接获取帧原图,分别通过 MediaMetadataRetriever 的 getScaledFrameAtTime 、getFrameAtTime 获取。MediaMetadataRetriever 是 Android 提供的类,用来获取本地和网络 Media 文件信息,提供了用于从输入媒体文件检索帧和元数据的统一接口。

原文作者:Ahab

原文链接:http://yhaowa.gitee.io/2455213e/

发表日期:February 17th 2019, 4:25:28 pm

更新日期:May 23rd 2020, 10:57:30 am

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG