Glide 中的资源包含很多东西,例如 ,byte[] 数组, int[] 数组,以及大量的 POJO 。无论什么时候,Glide 都会尝试重用这些资源,以限制你应用中的内存抖动数量。

好处

任何尺寸的对象的过多分配都会显著增加你应用中的垃圾回收 (GC)。虽然 Android 较新的 ART 运行时的 GC 惩罚比 Dalvik 运行时要低,但无论你使用什么设备,过多内存分配都会降低应用的性能。

Dalvik 设备 (Lollipop 之前)在过多分配时将不得不面对特别大的代价,值得在这里讨论一下。

Dalvik 有两种基本的 GC 模式, GC_CONCURRENT 和 GC_FOR_ALLOC ,这两种你都可以在 logcat 中看到。

  • GC_CONCURRENT 对于每次收集将阻塞主线程大约 5ms 。因为每个操作都比一帧(16ms)要小,GC_CONCURRENT 通常不会造成你的应用丢帧。
  • GC_FOR_ALLOC 是一种 stop-the-world 收集,可能会阻塞主线程达到 125ms 以上。GC_FOR_ALLOC 几乎每次都会造成你的应用丢失多个帧,导致视觉卡顿,特别是在滑动的时候。
    不幸的是,Dalvik 似乎甚至连适度的分配(例如一个 16kb 的缓冲区)都处理得不是很好。重复的中等分配,或即使单次大的分配(比如说一个 Bitmap ),将会导致 GC_FOR_ALLOC 。因此,你分配的内存越多,就会招来越多 stop-the-world 的 GC,而你的应用将有更多的丢帧。

通过复用中到大尺寸的资源, Glide 可以帮你尽可能地减少这种 GC,以保持应用的流畅。

Glide 采用较为宽容的办法来处理资源重用。Glide 会在它相信某个资源可以安全地复用时才这么做,但它并不要求调用者在每次请求之后都回收资源。除非某个调用者显式地表示它已经用完了某个资源(见下文),资源将不会被回收或重用。

引用计数

为决定某个资源是否正在被使用,以及什么时候可以安全地被重用,Glide 为每个资源保持了一个引用计数。

增加引用计数

每次调用 来加载一个资源,这个资源的引用计数会被加一。如果相同的资源被加载到两个不同的 Target,则在两个加载都完成后,它的引用计数将会为二。

减少引用计数

引用计数仅在调用者通过以下方式表示它们用完资源后会减少:

    • 在加载资源的 View 或 上调用 clear()
    • 在这个 或 Target 上调用对另一个资源请求的 方法。

释放资源

当引用计数到达 0 时,这个资源会被释放并被返回给 Glide 以重用。当资源被返回给 Glide 以重用以后,继续使用它是不安全的,因此以下行为是 不安全的

    • 使用 getImageDrawable 来取回 ImageView 中加载的 Bitmap 或 Drawable,并使用某种方式展示它( setImageDrawable,动画,或 TransitionDrawable 或其他任何方式 )。
    • 使用 来将一个资源加载到 View,但没有实现 方法并在其中将资源从 View 中移除。

在清理对应的 或 Target 之后还保持对资源的引用是不安全的,因为这个资源可能已经被销毁,或被重用于展示一个不同的图片,这可能导致未定义行为,图形损坏,或甚至导致继续使用该资源的应用崩溃。例如,在被释放回 Glide 之后, Bitmap 可能会被存储在一个 BitmapPool 中,并在未来的某个时刻被用重用于保存一张新图片的字节数据,或者它们已经被调用了 [recycle()]。在这两种情况下继续引用这个 Bitmap 并期待它们保持原始图像都是不安全的。

尽管 Glide 的大部分回收逻辑主要针对 Bitmap,但所有的 实现均可实现 recycle() 方法并将它们包含的任意可重用的数据池化。 可以返回开发者希望的任意 [Resource] API,因此开发者可以定制或提供额外的池化规则,只需要实现它们自己的 Resource 和 。

特别地,对于 Bitmap,Glide 提供了一个 BitmapPool 接口,以允许 获取和重用 [Bitmap] 对象。 Glide 的 [BitmapPool] 可以从任意的 Context 中使用 Glide 的单例获取到:

类似地,希望为 Bitmap 池化施加更多控制的用户可以直接实现他们自己的 BitmapPool,然后可以通过 GlideModule 的方式提供给 Glide。参见.

然而,允许池化让保证用户不会误用资源或Bitmap变得很困难。 Glide 会在可能的地方尝试添加一些断言,但是因为我们并不持有底层的 Bitmap,我们无法保证调用者在告诉我们 clear() 或一个新请求之后,会立即停用这些资源。

资源重用错误的征兆

有多种迹象可能暗示 Bitmap 或其他在 Glide 中被池化的资源出了问题。我们列出了一些最常见的现象,但这不是一个完备的列表。

Cannot draw a recycled Bitmap

Can’t call reconfigure() on a recycled bitmap

资源将在它们不再被使用时被返回到 Glide 的 BitmapPool 中。这里的内部实现基于 Request(它控制着Resource) 的生命周期管理。如果在这些 Bitmap 上调用了 ),但它们仍然在池中,就会使 Glide 无法重用它们而导致你的应用崩溃并抛出这个信息。这里的一个关键点是,这个崩溃很可能发生在未来的某个点,而不在这个违例代码的执行处!

View 在图片之间闪烁或相同的图像在多个 View 中展示

如果一个 Bitmap 被多次返回到 BitmapPool 中,或它已被返回到池中单仍然被一个 持有,另一个图片可能会被解码到这个 Bitmap 对象中。如果这种情况发生,就会使得 Bitmap 的内容会被替换为新的图片。 在这个过程中,View 可能仍然试图绘制这个 Bitmap,而这将导致原始的 View 展示一张新的图片。

一些常见的重用错误原因已被列在下面。就像上面的征兆一样,要列出全面的列表是很困难的,但是在尝试调试应用程序中的重用错误时,这些是您应该考虑的一些事情。

尝试往相同的 Target 加载两个不同的资源

在 Glide 中没有安全的办法来加载多个资源到单一的 Target 中。用户可以使用 API 来加载一系列资源到一个 Target,但也仅仅在下一个 调用之前才可以安全地引用早前的一个资源。

通常一个更好的答案是使用第二个 View 并将第二章图片加载到这第二个 View 上。 可以很好地允许你在两个单独请求的不同图片之间做交叉淡入效果 (cross fade)。你可以仅添加一个 ViewSwitcher 在你的布局中,使用两个 ImageView 作为其子控件,然后使用两次 into(ImageView)方法,每次一个子控件,来加载两张图片。

对于绝对要求将多个资源加载到相同 的用户,可以使用两个单独的 Target。为确保每个加载都不会取消另一个,用户还需要避免使用 子类,或使用一个自定义的 [ViewTarget] 子类并复写(override)其 和 以使得它们不使用 View 的 tag 来存储 。这属于高级用法,一般不推荐。

往Target中加载资源,清除或重用Target,并继续引用该资源

最简单的比较这个错误的办法是确保所有对资源的引用都在 调用时置空。通常,加载一个 Bitmap 然后对 Target 解引用,并且不要再次调用 into() 或 ,这样是安全的。然而,加载了一个 Bitmap,清除这个 Target,并在之后继续持有 Bitmap 引用是不安全的。类似地,加载资源到一个 View 上然后从 View 中获取这个资源 (通过 getImageDrawable() 或任何其他手段),并在其他某个地方继续引用它,也是不安全的。

在 Transformation<Bitmap> 中回收原始Bitmap

正如在 的 JavaDoc 中所说,传入 transform() 的原始 Bitmap 将会自动被回收,只要这个 Transformation 返回的 Bitmap 和原始传入 的不是同一个实例。这是和其他加载库很重要的一个不同,例如 Picasso。 BitmapTransformation 提供了 Glide 的资源创建的模板,但它的回收是在内部完成的,所以不管是 Transformation 还是 BitmapTransformation 都不要回收传入的 BitmapResource

另外值得注意的是,任何定制的 BitmapTransformationBitmapPool 中创建、但没有从 返回的中间 Bitmap,都会被返回到 BitmapPool 或被回收,但不会两种情况同时发生。你永远都不应该 ) 从 Glide 中创建的 Bitmap