使用应用程序上下文加载Glide图像

86
我在我的Android应用中使用Glide进行图像加载,为避免崩溃,我正在使用应用程序上下文加载图像。这对应用程序的性能和内存有什么影响?
2个回答

301
这对应用程序性能和内存会有什么影响?
Glide提供了许多`.with()`方法,原因在于它遵循生命周期。想象一个动态添加到Activity的Fragment,在其`onCreateView`方法中启动了一个3MB图像的Glide加载。现在,如果用户按下返回按钮并删除该片段,或者关闭整个活动呢?
如果你使用`with(getActivity().getApplicationContext())`,什么都不会发生,所有的3MB数据都被下载、解码、缓存,可能甚至设置到ImageView中,然后被垃圾回收,因为它的唯一引用来自Glide内部。
如果您使用`with((Fragment)this)`,Glide将订阅Fragment的生命周期事件,并在Fragment停止时暂停任何未完成的请求;当销毁时,清除所有未决的请求。这意味着图像下载将在中途停止,并且该死亡片段不会再使用任何资源。
如果你使用`with(getActivity())`,Glide将订阅Activity的生命周期事件,发生的事情与上面类似,但仅在Activity停止或销毁时发生。
如果您使用`with(view)`,Glide将执行与上述Activity相同的操作,这相当于上面的情况。
因此,最佳实践是使用最接近的上下文/片段,以避免未使用的请求完成!(还有一种手动停止加载的方法:`Glide.clear(ImageView|Target)`。)
为了在实践中应用这一点,请尽可能使用`with(this)`,但是当不可能时,例如在适配器或集中式图像加载方法中,将一个`RequestManager glide`作为参数传递,并使用`glide.load(...`,例如:
static loadImage(RequestManager glide, String url, ImageView view) {
    glide.load(url).into(view);
}

或者在适配器中:

class MyAdapter extends WhichEveryOneYouUse {
    private final RequestManager glide;
    MyAdapter(RequestManager glide, ...) {
        this.glide = glide;
        ...
    }
    void getView/onBindViewHolder(... int position) {
        // ... holder magic, and get current item for position
        glide.load... or even loadImage(glide, item.url, holder.image);
    }
}

并从Activity / Fragment中使用它们:

loadImage(Glide.with(this), url, findViewById(R.id.image));
// or
list.setAdapter(new MyAdapter(Glide.with(this), data));

16
太棒了的解释!它帮我省去了大量时间去调查经常发生OOM异常的主要原因。谢谢! - user1774316
2
注意:对于出现在该活动上的任何视图,调用Glide.with(view.getContext())实际上等同于Glide.with(this),因为RequestManagerRetriever.get(Context context)会检查上下文是否是Activity的实例并适当地进行转换,例如get((Activity)context)。因此,无论哪种方式,它最终都将使用相同的get(Activity)方法。 - Lorne Laliberte
1
@WindRider 不确定,可以尝试像答案中所示将 RequestManager 传递给您的适配器;或者在 GitHub 上打开一个 Glide 问题以获取明确的答案:我猜它不会清除。 - TWiStErRob
2
在 Glide 开发者修复“您无法为已销毁的活动启动加载”抛出异常之前,我建议您使用 ApplicationContext。 - OMArikan
3
@Nurseyit,我是 Coil 的作者。和 Glide 类似,如果你在 Fragment 中启动一个 load,Coil 也会使用 Activity 的生命周期。但是,Coil 和 Glide 都会响应 View.onDetach 事件,当一个 Fragment 被移动到后台时会触发该事件。此外,由于 Coil 使用了 AndroidX 生命周期组件,所以在销毁的 Activity 内部发起的任何请求都将立即被取消。 - Colin White
显示剩余13条评论

2

一个通用的解决方案,用于将Glide请求与所有者的生命周期同步。可以从任何地方调用:Activity、Fragment、RV Adapter、Custom View等。

private fun RequestManager.syncWithLifecycleOwner(view: View): RequestManager {

val syncRequest = object : DefaultLifecycleObserver {
    override fun onStart(owner: LifecycleOwner) = onStart()
    override fun onStop(owner: LifecycleOwner) = onStop()
    override fun onDestroy(owner: LifecycleOwner) {
        onDestroy()
        owner.lifecycle.removeObserver(this)
    }
}

view.findViewTreeLifecycleOwner()?.lifecycle?.addObserver(syncRequest)

return this

然后,您可以创建一个简单的扩展函数,如下所示:

fun ImageView.loadUrl(url: String) {
   Glide
      .with(context.applicationContext)
      .syncWithLifecycleOwner(this)
      .load(url)
      .into(this) 
}

findViewTreeLifecycleOwner() 函数在 AndroidX 生命周期库中。它提供了 Activity 或 Fragment 中的 View 生命周期(viewLifecycleOwner),这个特定的 ImageView 依附于其中。你需要传递来自视图内部的应用程序上下文,以确保 Glide 库不会自行调用回调函数。


这看起来很有趣,但那些Elvises很奇怪...如果它找不到生命周期,它将什么也不做,你永远不会知道所有的图像都在静态上下文中加载。 - TWiStErRob
出于简洁起见省略了错误日志记录。现在会使用 Coil。它支持协程,更适合 Android 的生命周期。 - Rvb84
这似乎是在4.15.0中实现的 https://github.com/bumptech/glide/commit/18bba927a5e5fe7d07ada9667e9e503b9f0596a2 - TWiStErRob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接