Glide 源码分析

Glide 作为一个出色的图片加载框架,对其剖析的文章数不胜数。而若像大多数文章一样常规的去分析源码就没什么新意了,本文旨在发掘一些新的或相对陌生的知识点,以完善对 Glide 的认知,涉及源码基于 v4.8.0。

主要内容:

1.磁盘缓存
2.内存缓存
3.网络请求
4.图片转换
5.感知生命周期
6.下载及预加载
7.加载图片到通知栏和应用小部件中
8.图片格式及内存优化
9.请求优先级及原理
10.缩略图使用及原理
11.展示 gif 原理
12.自定义模块及延伸
13.兼容3.x写法

1.磁盘缓存

Glide 提供了五种磁盘缓存策略:

  • DiskCacheStrategy.AUTOMATIC
  • DiskCacheStrategy.ALL
  • DiskCacheStrategy.RESOURCE
  • DiskCacheStrategy.DATA
  • DiskCacheStrategy.NONE

默认为 AUTOMATIC 策略,即自动采用最佳策略,它会针对本地和远程图片使用不同的策略。当你加载远程数据时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如变换、裁剪)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。即:

本地数据 -> DiskCacheStrategy.RESOURCE
远程数据 -> DiskCacheStrategy.DATA

源码中是在何处实现这一逻辑的呢?

2.内存缓存

3.网络请求

4.图片转换

5.感知生命周期

发起一个图片加载请求后,我们期望当该请求所处的界面 onStop 时请求也随之停止,再次 onStart 时请求能够随之继续, onDestroy 时请求能够随之销毁。这就需要能够感知当前 Activity 的生命周期变化,由于 Fragment 在 onAttach 之后与 Activity 有相同的生命周期,glide 利用这一点,通过给 Activity 添加一个无界面的 Fragment 实现感知。

发起请求时通过 with 方法传入上下文,此方法会返回一个 RequestManager,RequestManager 用于管理和启动图片加载请求,可以感知外部 Activity 的生命周期,从而管理请求随之启动、停止和重启。

先来分析一个较为简单的流程:with 方法传入 Activity,会调用到 RequestManagerRetriever 的 get 方法:

1
2
3
4
5
6
7
8
9
10
11
@NonNull
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}

其中调用 fragmentGet 方法去新建 RequestManager :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@NonNull
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
//这里新建了一个无界面的 Fragment,并添加到该界面
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
requestManager =
//这里新建了一个 requestManager,并将无界面 Fragment 的生命周期暴露给 requestManager
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}

当无界面 Fragment 生命周期变化时,通过接口回调出去给 requestManager,这样 requestManager 就实现了随外部生命周期变化自动启动、停止和重启请求。with 方法若传入其他参数,流程上也是大同小异,都是找到当前 Activity 或 Fragment ,给其添加一个无界面 Fragment 罢了。

而不管传入何参数,都有这样一个逻辑:

1
2
3
if (Util.isOnBackgroundThread()) {
return get(view.getContext().getApplicationContext());
}

若当前处于非主线程,则一律基于应用生命周期请求,不再关心所在 Fragment 或 Activity 的生命周期,这是因为子线程中执行的任务本身就是跟所在界面生命周期无关的。

在分析这一块时涉及 ContextWrapper 相关的逻辑,若不太熟悉,可参考:ContextWrapper

Glide 推出时谷歌还未发布 Architecture Components,而现在若要实现一个可感知生命周期的逻辑,大可不必像 Glide 一样添加一个 Fragment ,直接使用 Architecture Components 中的 Lifecycle 组件就可以很方便的实现了。

6.下载及预加载

下载的标准写法如下,也是官方示例写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@WorkerThread
private void downloadFile() {
FutureTarget<File> target = null;
try {
target = Glide.with(context)
.downloadOnly()
.load(imgUrl)
.submit();
final File cacheFile = target.get();
/*
* 默认会下载到磁盘缓存中,理论上不应对缓存文件进行编辑、删除
*/
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "download: ", e);
} finally {
// 这里要调用cancel方法取消等待操作并释放资源
if (target != null) {
target.cancel(true); // 若传true则允许中断操作
}
}
}

此方式要自行开子线程,你可能会觉得稍显麻烦,直接调用 listener 方法监听 onResourceReady 回调岂不是更简单?其实不是的,由于要拿到 FutureTarget 调用其 cancel 方法,若监听 onResourceReady 代码逻辑会更复杂。

对于 FutureTarget.get() 方法,并不是调用时才会去加载数据,调用 submit 方法后就已经开始去加载数据了,get 方法最终会调用到 RequestFutureTarget 的 doGet 方法如下:

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 synchronized R doGet(Long timeoutMillis)
throws ExecutionException, InterruptedException, TimeoutException {
if (assertBackgroundThread && !isDone()) {
Util.assertBackgroundThread();
}

if (isCancelled) {
throw new CancellationException();
} else if (loadFailed) {
throw new ExecutionException(exception);
} else if (resultReceived) {
return resource;
}

if (timeoutMillis == null) {
waiter.waitForTimeout(this, 0);
} else if (timeoutMillis > 0) {
long now = System.currentTimeMillis();
long deadline = now + timeoutMillis;
while (!isDone() && now < deadline) {
waiter.waitForTimeout(this, deadline - now);
now = System.currentTimeMillis();
}
}

if (Thread.interrupted()) {
throw new InterruptedException();
} else if (loadFailed) {
throw new ExecutionException(exception);
} else if (isCancelled) {
throw new CancellationException();
} else if (!resultReceived) {
throw new TimeoutException();
}
return resource;
}

可以看到 get 方法内部并没有加载数据的逻辑, RequestFutureTarget 内部通过锁实现了 get 方法的阻塞调用,当资源加载完毕后 onResourceReady 中会解除阻塞:

1
2
3
4
5
6
7
8
9
@Override
public synchronized boolean onResourceReady(
R resource, Object model, Target<R> target, DataSource dataSource, boolean isFirstResource) {
// We might get a null result.
resultReceived = true;
this.resource = resource;
waiter.notifyAll(this);
return false;
}

除了下载 File 类型以外,还可以指定下载类型,比如下载 Bitmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WorkerThread
private void downloadBitmap() {
RequestOptions DOWNLOAD_ONLY_OPTIONS = RequestOptions
.diskCacheStrategyOf(DiskCacheStrategy.DATA) //这边其实可以根据业务场景配置,如果是网络图片一般需要缓存
.priority(Priority.LOW) // 设置优先级
.skipMemoryCache(true);
FutureTarget<Bitmap> target = null;
try {
target = Glide.with(context)
.asBitmap()
.apply(DOWNLOAD_ONLY_OPTIONS)
.load(imgUrl)
.submit();
final Bitmap bitmap = target.get();

} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "download: ", e);
} finally {
// 这里要调用cancel方法取消等待操作并释放资源
if (target != null) {
target.cancel(true); // 若传true则允许中断操作
}
}
}

这里的 DOWNLOAD_ONLY_OPTIONS 配置其实就是 downloadOnly 方法应用的配置。

实现预加载十分简单:

1
Glide.with(context).load(imgUrl).preload();

关键代码位于 PreloadTarget 中:

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
public final class PreloadTarget<Z> extends SimpleTarget<Z> {
private static final int MESSAGE_CLEAR = 1;
private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Callback() {
@Override
public boolean handleMessage(Message message) {
if (message.what == MESSAGE_CLEAR) {
((PreloadTarget<?>) message.obj).clear();
return true;
}
return false;
}
});

private final RequestManager requestManager;

public static <Z> PreloadTarget<Z> obtain(RequestManager requestManager, int width, int height) {
return new PreloadTarget<>(requestManager, width, height);
}

private PreloadTarget(RequestManager requestManager, int width, int height) {
super(width, height);
this.requestManager = requestManager;
}

@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget();
}

@SuppressWarnings("WeakerAccess")
@Synthetic void clear() {
requestManager.clear(this);
}
}

相比于 RequestFutureTarget,PreloadTarget 里的逻辑就简单多了,可以看到加载资源结束后只是把此次请求释放掉了,不用像其他 Target 一样做额外的操作。

7.加载图片到通知栏和应用小部件中

上面说到的下载、预加载主要通过 RequestFutureTarget、PreloadTarget 实现,平时使用 Glide 直接加载图片到 ImageView 的方式则是通过 ImageViewTarget,Glide 中还提供了 NotificationTarget 和 AppWidgetTarget 来实现加载图片到通知栏和应用小部件中。使用方法十分简单,下面列出加载图片到通知栏的实现示例:

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
/**
* 加载图片到通知栏
*/
private void loadNotificationImg() {
//构建一个通知栏
final int NOTIFICATION_ID = 1;
final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.remoteview_notification);
rv.setImageViewResource(R.id.iv, R.mipmap.ic_launcher);
rv.setTextViewText(R.id.tv, "Short Message");
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(context, "channel_id")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Content Title")
.setContentText("Content Text")
.setContent(rv)
.setPriority(NotificationCompat.PRIORITY_HIGH);
final Notification notification = mBuilder.build();
notification.bigContentView = rv;
NotificationManager service = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
service.notify(NOTIFICATION_ID, notification);

//加载图片到通知栏
NotificationTarget notificationTarget = new NotificationTarget(
context,
R.id.iv,
rv,
notification,
NOTIFICATION_ID);
Glide.with(context).asBitmap().load(imgUrl).into(notificationTarget);
}

实际的更新方法封装于 NotificationTarget 中:

1
2
3
4
5
6
7
8
9
/**
* Updates the Notification after the Bitmap resource is loaded.
*/
private void update() {
NotificationManager manager =
(NotificationManager) this.context.getSystemService(Context.NOTIFICATION_SERVICE);
Preconditions.checkNotNull(manager)
.notify(this.notificationTag, this.notificationId, this.notification);
}

通过 AppWidgetTarget 加载图片到应用小部件中与此类似,这些均继承自 Target 接口,了解原理后,我们可以自定义 Target 来随意的定制功能了。

8.图片格式及内存优化

记得刚接触 Glide 时,总会看到这个描述:

Glide 默认的 Bitmap 格式是 RGB_565,相比于 Picasso,加载的图片质量略差,但比 ARGB_8888 格式的内存开销要小一半。

而现在再讲这个特性就不对了,因为在 Glide v4 中,默认的 Bitmap 格式改为了 ARGB_8888。准确来说是默认的解码格式由 PREFER_RGB_565 改为了 PREFER_ARGB_8888,具体可参考 官方文档

Glide 中可配置的解码格式只提供了 PREFER_RGB_565 和 PREFER_ARGB_8888 两个选项,而 Android Bitmap Config 中提供了 RGB_565、ARGB_8888、ARGB_4444 以及 HARDWARE 等 7 种格式,这让我们在使用层面上有一定程度的简化, Glide 内部自行适配了其他解码格式,比如若配置为 PREFER_ARGB_8888,在 Android 8.0 系统上就会尝试开启硬件位图编码格式,对应代码于 DecodeJob 中:

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
@NonNull
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 allow hardware config is defined, we can use it if it's set to false or if it's safe to
// use the hardware config for the request.
if (isHardwareConfigAllowed != null && (!isHardwareConfigAllowed || isHardwareConfigSafe)) {
return options;
}

// If allow hardware config is undefined or is set to true but it's unsafe for us to use the
// hardware config for this request, we need to override the config.
options = new Options();
options.putAll(this.options);
options.set(Downsampler.ALLOW_HARDWARE_CONFIG, isHardwareConfigSafe);

return options;
}

官方文档关于 硬件位图 的介绍也比较清晰,就不过多描述了。另外 Android 端涉及到图片内存,必须了解的一个问题就是:你的 Bitmap 究竟占多大内存?

9.请求优先级及原理

若一个界面中需要展示多张图片,我们可能会期望某张图片优先加载,这就需要设置 Glide 的请求优先级, Glide 中提供四种优先级:

  • Priority.LOW
  • Priority.NORMAL
  • Priority.HIGH
  • Priority.IMMEDIATE

使用十分简单:

1
2
RequestOptions options = new RequestOptions().priority(Priority.HIGH);
Glide.with(context).load(imgUrl).apply(options).into(imageView);

下面来分析一下我们配置的 Priority.HIGH 到底是如何生效的,跟踪发现优先级参数 priority 会被传入到 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
private Request buildThumbnailRequestRecursive(
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
RequestOptions requestOptions) {
if (thumbnailBuilder != null) {
// 缩略图相关,先忽略
} else if (thumbSizeMultiplier != null) {
// 缩略图相关,先忽略
} else {
// Base case: no thumbnail.
return obtainRequest(
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight);
}
}

由于并未设置 thumbnail,先忽略缩略图相关逻辑,此方法中会调用到 obtainRequest 方法,继续跟踪,发现我们配置的 priority 参数在 SingleRequest 中的 onSizeReady 方法中被传入到 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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) {

// ...省略其他逻辑

DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);

jobs.put(key, engineJob);

engineJob.addCallback(cb);
engineJob.start(decodeJob);

if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}

这里在构建 DecodeJob 时将优先级配置传入,最终传入到 DecodeJob 和 DecodeHelper 中,其中 DecodeHelper 被 DecodeJob 持有。

接下来分析 priority 参数分别在什么时候被用到,首先看 DecodeJob 中持有的 priority,其仅在实现 Comparable 接口时用到,这个比较容易理解,可以通过对 DecodeJob 排序来实现优先级的调整。DecodeHelper 中持有的 priority 在 DataFetcher 的 loadData 方法中被传入:

1
void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);

DataFetch 用于加载数据,其实现有很多:

但并不是所有方法都能应用优先级的,这取决与具体的业务组件,比如 OkHttp 不支持请求优先级设置,直接忽略了 priority 参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void loadData(@NonNull Priority priority,
@NonNull final DataCallback<? super InputStream> callback) {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
this.callback = callback;

call = client.newCall(request);
call.enqueue(this);
}

而 Volley 就支持请求优先级:

1
2
3
4
5
6
7
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
request = requestFactory.create(url.toStringUrl(), callback, glideToVolleyPriority(priority),
url.getHeaders());
requestQueue.add(request);
}

由此也可以得出设置请求优先级并不是必然生效的。

10.缩略图使用及原理

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

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

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 文件信息,提供了用于从输入媒体文件检索帧和元数据的统一接口。

11.展示 gif 原理

在展示 gif 时,即使不调用 asGif 方法,Glide 也能识别出 gif 类型并正常展示。解码逻辑位于 Downsampler 的 decode 方法中,我们先从这里开始,看看 Glide 是如何识别 gif 类型的。decode 方法中调用了 decodeFromWrappedStreams 方法开始实际解码逻辑:

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
private Bitmap decodeFromWrappedStreams(InputStream is,
BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
int requestedHeight, boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks) throws IOException {
long startTime = LogTime.getLogTime();

int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
int sourceWidth = sourceDimensions[0];
int sourceHeight = sourceDimensions[1];
String sourceMimeType = options.outMimeType;

// If we failed to obtain the image dimensions, we may end up with an incorrectly sized Bitmap,
// so we want to use a mutable Bitmap type. One way this can happen is if the image header is so
// large (10mb+) that our attempt to use inJustDecodeBounds fails and we're forced to decode the
// full size image.
if (sourceWidth == -1 || sourceHeight == -1) {
isHardwareConfigAllowed = false;
}

int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);

int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;

ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);

// ... 省略下面的解码逻辑代码

return rotated;
}

可以看到解码开始前,准备了图片角度、输出尺寸的信息,并通过 ImageHeaderParserUtils 获取了图片类型:

1
ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);

Glide 中定义了以下图片类型:

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
/**
* The format of the image data including whether or not the image may include transparent
* pixels.
*/
enum ImageType {
GIF(true),
JPEG(false),
RAW(false),
/** PNG type with alpha. */
PNG_A(true),
/** PNG type without alpha. */
PNG(false),
/** WebP type with alpha. */
WEBP_A(true),
/** WebP type without alpha. */
WEBP(false),
/** Unrecognized type. */
UNKNOWN(false);

private final boolean hasAlpha;

ImageType(boolean hasAlpha) {
this.hasAlpha = hasAlpha;
}

public boolean hasAlpha() {
return hasAlpha;
}
}

而实际获取图片类型的逻辑位于 DefaultImageHeaderParser 中:

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
@NonNull
private ImageType getType(Reader reader) throws IOException {
final int firstTwoBytes = reader.getUInt16();

// JPEG.
if (firstTwoBytes == EXIF_MAGIC_NUMBER) {
return JPEG;
}

final int firstFourBytes = (firstTwoBytes << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
// PNG.
if (firstFourBytes == PNG_HEADER) {
// See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha
// -color-type
reader.skip(25 - 4);
int alpha = reader.getByte();
// A RGB indexed PNG can also have transparency. Better safe than sorry!
return alpha >= 3 ? PNG_A : PNG;
}

// GIF from first 3 bytes.
if (firstFourBytes >> 8 == GIF_HEADER) {
return GIF;
}

// WebP (reads up to 21 bytes). See https://developers.google.com/speed/webp/docs/riff_container
// for details.
if (firstFourBytes != RIFF_HEADER) {
return UNKNOWN;
}
// Bytes 4 - 7 contain length information. Skip these.
reader.skip(4);
final int thirdFourBytes =
(reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
if (thirdFourBytes != WEBP_HEADER) {
return UNKNOWN;
}
final int fourthFourBytes =
(reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
if ((fourthFourBytes & VP8_HEADER_MASK) != VP8_HEADER) {
return UNKNOWN;
}
if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_EXTENDED) {
// Skip some more length bytes and check for transparency/alpha flag.
reader.skip(4);
return (reader.getByte() & WEBP_EXTENDED_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;
}
if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_LOSSLESS) {
// See chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt
// for more info.
reader.skip(4);
return (reader.getByte() & WEBP_LOSSLESS_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;
}
return ImageType.WEBP;
}

可以看到这里是通过文件头标示来获取图片类型的,而 gif 文件头如下:

查看代码,Glide 中便是通过 0x474946 文件头来判断的。

Glide 中将 gif 类型图片封装成了 GifDrawable,在 ByteBufferGifDecoder 中可以看到 GifDrawable 的生成逻辑:

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
@Nullable
private GifDrawableResource decode(
ByteBuffer byteBuffer, int width, int height, GifHeaderParser parser, Options options) {
long startTime = LogTime.getLogTime();
try {
final GifHeader header = parser.parseHeader();
if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) {
// If we couldn't decode the GIF, we will end up with a frame count of 0.
return null;
}

Bitmap.Config config = options.get(GifOptions.DECODE_FORMAT) == DecodeFormat.PREFER_RGB_565
? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

int sampleSize = getSampleSize(header, width, height);
GifDecoder gifDecoder = gifDecoderFactory.build(provider, header, byteBuffer, sampleSize);
gifDecoder.setDefaultBitmapConfig(config);
gifDecoder.advance();
Bitmap firstFrame = gifDecoder.getNextFrame();
if (firstFrame == null) {
return null;
}

Transformation<Bitmap> unitTransformation = UnitTransformation.get();

GifDrawable gifDrawable =
new GifDrawable(context, gifDecoder, unitTransformation, width, height, firstFrame);

return new GifDrawableResource(gifDrawable);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Decoded GIF from stream in " + LogTime.getElapsedMillis(startTime));
}
}
}

至此已经获取到 GifDrawable ,GifDrawable 中持有一个 GifFrameLoader,而 GifFrameLoader 中持有了 gif 解码器 StandardGifDecoder 。由此可以得出 gif 的展示逻辑就封装于 GifDrawable 中,主要通过 GifFrameLoader 实现。

12.自定义模块及延伸

13.兼容3.x写法

参考文章:
官方文档 https://muyangmin.github.io/glide-docs-cn/
源码分析 https://blog.csdn.net/sinyu890807/column/info/15318