Glide源码解析(三):回调监听

图片回调流程

前面的文章详细介绍了Glide加载图片的流程,这里我们重点回顾一下图片的的显示流程:DecodeJob完成图片的装载之后,会回调到notifyEncodeAndRelease()方法,之后的流程如下。

  1. EngineJob.onResourceReady
  2. EngineJob.notifyCallbacksOfResult
  3. EngineJob.callCallbackOnResourceReady
  4. ResourceCallback.onResourceReady
  5. SingleRequest.onResourceReady
  6. ImageViewTarget.onResourceReady
  7. ImageViewTarget.setResource
  8. view.setImageDrawable(resource);

上面的流程可以看到,图片展示的地方是在Target的onResourceReady()中执行的。这里的Target是哪个步骤构建的呢?回顾一下前面的文章流程中可以知道,在into()方法中,会构建Target,在Request的构建中,Target作为参数传进Request中。

自定义Target的原理和实现

面的图片回调流程基本上就介绍了Target的原理,自定义Target的时候,只需要实现onResourceReady()方法中展示图片即可,使用定义Target的用法如下:

1
2
3
4
5
6
7
8
SimpleTarget<GlideDrawable> simpleTarget = new SimpleTarget<GlideDrawable>() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {
imageView.setImageDrawable(resource);
}
};

Glide.with(this).load(url).into(simpleTarget);

以上是基本的自定义Target的用法,掌握了图片的显示流程就可以定义复杂的Target,实现复杂的图片加载需求。

Preload的功能和原理

preload()可以替换into()法的另外一个方法,和into()不同的是,preload()方法只加载图片,而不显示图片,是一种图片预加载的功能,使真正显示图片的时候不需要从网络获取,提高图片的显示速度。preload()方法是如何实现不加载图片的呢?通过前面的图片加载流程,我们知道,获取图片之后DecodeJob会执行图片的显示流程,而图片显示是Target完成的。之前的文章分析Glide加载图片的流程的时候我们知道into()方法,Glide内部逻辑中会构建一个Target,这个Target就是显示图片的对象。我们需要分析preload()内部的Target是如何不显示图片的。

1
2
3
4
5
6
7
public Target<TranscodeType> preload() {
return preload(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
public Target<TranscodeType> preload(int width, int height) {
final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(requestManager, width, height);
return into(target);
}

我们可以看到:preload()方法会先构建一个PreloadTarget对象,然后调用into(targe)。所以PreloadTarget类完成了图片不显示的逻辑。我们分析一下PreloadTarget的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
});
@Override
public void onResourceReady(Z resource, Transition<? super Z> transition) {
HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget();
}

可以看到PreloadTarget内部在onResourceReady()中仅仅是发送了一个message,并没有显示图片。需要注意的是preload()默认是全尺寸缓存图片的,使用into()显示preoload()已经预加载的图片时,需要指定缓存策略为DiskCacheStrategy.SOURCE,否则into()会找不到缓存,从网络加载图片。

downloadOnly的功能和原理

into()和preload()方法都是加载图片的操作,不提供图片的路径信息,开发者只关心图片显示的问题,对图片本身不关注,如果我们需要对图片本身的信息做处理的话,就需要知道图片的保存路径,Glide提供了两个方法可以获取图片的路径:

  • downloadOnly(int width, int height)
  • downloadOnly(Y target)

downloadOnly(int width, int height)方法,该方法主要完成图片加载不显示,与preload功能相似,同时他提供了一个获取图片缓存路径的方法,该方法是阻塞方法,如果图片没有下载成功,会阻塞,因此一般使用get()方法需要在子线程中调用,同时get()内部也会检查是否在子线程,否则抛异常。
downloadOnly(Y target)不同的是不需要再子线程中运行。

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
protected static final RequestOptions DOWNLOAD_ONLY_OPTIONS =
new RequestOptions().diskCacheStrategy(DiskCacheStrategy.DATA).priority(Priority.LOW) .skipMemoryCache(true);
public FutureTarget<File> downloadOnly(int width, int height) {
return getDownloadOnlyRequest().submit(width, height);
}
protected RequestBuilder<File> getDownloadOnlyRequest() {
return new RequestBuilder<>(File.class,this).apply(DOWNLOAD_ONLY_OPTIONS);
}
public FutureTarget<TranscodeType> submit(int width, int height) {
final RequestFutureTarget<TranscodeType> target =
new RequestFutureTarget<>(glideContext.getMainHandler(), width, height);

if (Util.isOnBackgroundThread()) {
glideContext.getMainHandler().post(new Runnable() {
@Override
public void run() {
if (!target.isCancelled()) {
into(target, target);
}
}
});
} else {
into(target, target);
}

return target;
}

可以看到downloadOnly(int width, int height)内部构建的Target是RequestFutureTarget,图片的不显示功能和get()图片缓存文件都是在这里提供的。

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
 public synchronized void onResourceReady(R resource, Transition<? super R> transition) {
// Ignored, synchronized for backwards compatibility.
}
public R get() throws InterruptedException, ExecutionException {
try {
return doGet(null);
} catch (TimeoutException e) {
throw new AssertionError(e);
}
}
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) {
waiter.waitForTimeout(this, timeoutMillis);
}

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

return resource;
}

RequestFutureTarget的onResourceReady内部没有做任何实现。而get()方法是个阻塞方法,如果图片还没有加载完成,get()的调用线程会被阻塞;同时get()内部也做了线程判断,如果不是在子线程,会抛异常。

Listener的功能和原理

listener()方法提供了一个功能:图片加载的状态,加载完成或者加载失败的结果。

1
2
3
4
boolean onLoadFailed(@Nullable GlideException e, Object model, Target<R> target,
boolean isFirstResource);
boolean onResourceReady(R resource, Object model, Target<R> target, DataSource dataSource,
boolean isFirstResource);

listener方法的参数RequestListener内部有两个方法

  1. onResourceReady 标识加载成功,以及图片资源resource,返回值标识是否处理了结果
  2. onLoadFailed 表示加载失败,以及失败的原因GlideException 。返回值标识是否处理了结果

listener方法的参数RequestListener,会在构建Request的时候,保存在request的属性requestListener。
在DecodeJob回调的流程中会在Request中调用onResourceReady或者onLoadFailed

您的支持是我原创的动力