目标

关于Target

在Glide中,[Target][1] 是介于请求和请求者之间的中介者的角色。Target 负责展示占位符,加载资源,并为每个请求决定合适的尺寸。被使用得最频繁的是 [ImageViewTargets][2] ,它用于在 ImageView 上展示占位符、Drawable 和 Bitmap 。用户还可以实现自己的 Target ,或者从任何可用的基类派生子类。

指定目标

[into(Target)][3] 方法不仅仅用于启动每个请求,它同时也指定了接收请求结果的 Target:

  1. Target<Drawable> target =
  2. Glide.with(fragment)
  3. .load(url)
  4. .into(new Target<Drawable>() {
  5. ...
  6. });

Glide 提供了一个辅助方法 [into(ImageView)][4] ,它接受一个 ImageView 参数并为其请求的资源类型包装了一个合适的 [ImageViewTarget][2]:

  1. Target<Drawable> target =
  2. Glide.with(fragment)
  3. .load(url)
  4. .into(imageView);

取消和重用

你可以注意到 [into(Target)][3] 和 [into(ImageView)][4] 都返回了一个 Target 实例。如果你重用这个 Target 来在将来开始一个新的加载,则之前开始的任何请求都会被取消,它们使用的资源将被释放:

  1. Target<Drawable> target =
  2. Glide.with(fragment)
  3. .load(url)
  4. .into(new Target<Drawable>() {
  5. ...
  6. });
  7. ...
  8. // Some time in the future:
  9. Glide.with(fragment)
  10. .load(newUrl)
  11. .into(target);

你也可以使用返回的 Target 来 [clear()][14] 之前的加载,这将在不需要开始新的加载的情况下释放掉任何相关资源:

  1. Target<Drawable> target =
  2. Glide.with(fragment)
  3. .load(url)
  4. .into(new Target<Drawable>() {
  5. ...
  6. });
  7. ...
  8. // Some time in the future:
  9. Glide.with(fragment).clear(target);

Glide 的 [ViewTarget][15] 子类使用了 Android Framework 的 [getTag()][8] 和 [setTag()][9] 方法来存储每个请求的相关信息,因此如果你在使用 [ViewTarget][15] 或在往 ImageView 中加载图片,你可以直接重用或清理这个 View:

  1. Glide.with(fragment)
  2. .load(url)
  3. .into(imageView);
  4. // Some time in the future:
  5. Glide.with(fragment).clear(imageView);
  6. // Or:
  7. Glide.with(fragment)
  8. .load(newUrl)
  9. .into(imageView);

此外,仅对[ViewTarget][15]而言,你可以在每次加载或清理调用时都传入一个新的实例,而 Glide 仍然可以从 View 的 tag 中取回之前一次加载的信息:

  1. Glide.with(fragment)
  2. .load(url)
  3. .into(new DrawableImageViewTarget(imageView));
  4. // Some time in the future:
  5. Glide.with(fragment)
  6. .load(newUrl)
  7. .into(new DrawableImageViewTarget(imageView));

注意,除非你的 Target 继承自 [ViewTarget][15],或实现了 [setRequest()][17] 和 [getRequest()][18]并允许你从新的 Target 实例中取回上一次加载的信息,否则这种使用方法将奏效。

清理

当你完成了对资源(BitmapDrawable 等)的使用时,及时清理(clear)你创建的这些 Target 是一个好的实践。即使你认为你的请求已经完成了,也应该使用 [clear()][14] 以使 Glide 可以重用被这次加载使用的任何资源 (特别是 Bitmap )。未调用 [clear()][14] 会浪费 CPU 和内存,阻塞更重要的加载,甚至如果你在同一个 surface (View, Notification, RPC 等) 上有两个 Target,可能会引发图片显示错误。对于像 [SimpleTarget][16]这种无法从一个新实例里跟踪前一个请求的 Target 来说,及时清理尤为重要。

尺寸 (Sizes and dimensions)

默认情况下,Glide 使用目标通过 [getSize][11] 方法提供的尺寸来作为请求的目标尺寸。这允许 Glide 选取合适的 URL,下采样,裁剪和变换合适的图片以减少内存占用,并确保加载尽可能快地完成。

View 目标

[ViewTarget][15] 通过检查 View 的属性和/或使用一个 [OnPreDrawListener][12] 在 View 绘制之前直接测量尺寸来实现 [getSize()][11] 方法。因此, Glide 可以自动调整大部分图片以匹配目标 View。加载更小的图片可使 Glide 更快地完成加载 (在缓存到磁盘以后),并使用更少的内存,在图片尺寸一致时还可以增加 Glide 的 BitmapPool 的命中率。

ViewTarget 使用以下逻辑:

  1. 如果 View 的布局参数尺寸 > 0 且 > padding,则使用该布局参数;
  2. 如果 View 尺寸 > 0 且 > padding,使用该实际尺寸;
  3. 如果 View 布局参数为 wrap_content 且至少已发生一次 layout ,则打印一行警告日志,建议使用 Target.SIZE_ORIGINAL 或通过 override() 指定其他固定尺寸,并使用屏幕尺寸为该请求尺寸;
  4. 其他情况下(布局参数为 match_parent0, 或 wrap_content 且没有发生过 layout ),则等待布局完成,然后回溯到步骤1。

有时在使用 RecyclerView时,View 可能被重用且保持了前一个位置的尺寸,但在当前位置会发生改变。为了处理这种场景,你可以创建一个新的 [ViewTarget][15] 并为 waitForLayout() 方法传入 true:

  1. @Override
  2. public void onBindViewHolder(VH holder, int position) {
  3. Glide.with(fragment)
  4. .load(urls.get(position))
  5. .into(new DrawableImageViewTarget(holder.imageView, /*waitForLayout=*/ true));
强大的尺寸管理

通常 Glide 在显式地为加载的 View 设置了 dp 尺寸时提供了最快且最可预测的结果。如果无法达到这一点,Glide 也通过 onPreDrawListener 提供了为 layout_weightmatch_parent 和其他相对尺寸的完备鲁棒的支持。最后,如果这些都无法达成,Glide 应该也为 wrap_content 提供了合理的行为。

后备方案

在任何情况下,如果 Glide 看起来获取了错误的 View 尺寸,你都可以手动覆盖来纠正它。你可以选择扩展 [ViewTarget][5] 实现你自己的逻辑,或者使用 RequestOption 里的 [override()][13]方法。

定制目标

如果你正在使用一个 Target 且你将要加载的不是可以允许你派生 [ViewTarget][15] 的 View, 你讲需要实现 [getSize()][11] 方法。

实现 getSize 可能最简单的方案是直接调用回调:

  1. @Override
  2. public void getSize(SizeReadyCallback cb) {
  3. cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
  4. }

使用 Target.SIZE_ORIGINAL 可能非常低效,或如果你的图片足够大可能引发 OOM 。作为替代方案,你也可以为你的 Target 的构造器传入一个尺寸,并把这些尺寸提供给回调:

  1. public class CustomTarget<T> implements Target<T> {
  2. private final int width;
  3. private final int height;
  4. public CustomTarget(int width, int height) {
  5. this.width = width;
  6. this.height = height;
  7. }
  8. ...
  9. @Override
  10. public void getSize(SizeReadyCallback cb) {
  11. cb.onSizeReady(width, height);
  12. }
  13. }

如果你的应用内使用一致的图片尺寸,或你确切地知道你需要的尺寸,你也可以传入一个特定尺寸的集合。如果你不知道所需的具体尺寸,但可以异步地得出结果,你也可以使用列表持有在 [getSize()][11] 中给出你的任何回调,然后执行你的异步过程并稍后在你得出尺寸之后通知你持有的这些回调。

如果你持有了这些回调,请确保同时实现 [removeCallback][20] 以避免内存泄露。

如果需要一个示例,请参考 [ViewTarget][21] 中的逻辑。

动画资源和定制目标

如果你只是要加载 GifDrawable,或任何其他资源类型到一个 View,你应该总是尽可能地使用 into(ImageView)。除了优雅的处理或新发起请求之外,Glide的大部分 ViewTarget 实现已经为您处理了 Drawable 动画。如果你确实必须使用定制的 ViewTarget,请确保继承自 ViewTarget 或在新请求开始之前和展示资源结束之后严格地清理从 into(Target) 返回的 Target

如果你并非往 View 中加载图片,而直接使用 ViewTarget 或使用了定制的 Target 比如 [SimpleTarget][16] 且你正在加载一个动画资源例如 [GifDrawable][22],你需要确保在 onResourceReady 中调用 [start()][23] 来启动这个动画:

  1. Glide.with(fragment)
  2. .asGif()
  3. .load(url)
  4. .into(new SimpleTarget<>() {
  5. @Override
  6. public void onResourceReady(GifDrawable resource, Transition<GifDrawable> transition) {
  7. resource.start();
  8. // Set the resource wherever you need to use it.
  9. }
  10. });

如果你加载的是 BitmapGifDrawable,你可以判断这个可绘制对象是否实现了 [Animatable][24]:

  1. Glide.with(fragment)
  2. .load(url)
  3. .into(new SimpleTarget<>() {
  4. @Override
  5. public void onResourceReady(Drawable resource, Transition<GifDrawable> transition) {
  6. if (resource instanceof Animatable) {
  7. resource.start();
  8. }
  9. // Set the resource wherever you need to use it.
  10. }
  11. });